Ignore:
Timestamp:
Jul 21, 2009 2:23:40 PM (15 years ago)
Author:
Ted Faber <faber@…>
Branches:
axis_example, compt_changes, info-ops, master, version-1.30, version-2.00, version-3.01, version-3.02
Children:
55c074c
Parents:
8780cbec
Message:

whitespace conversion

File:
1 edited

Legend:

Unmodified
Added
Removed
  • fedd/federation/access.py

    r8780cbec r866c983  
    4141
    4242    def __init__(self, config=None, auth=None):
    43         """
    44         Initializer.  Pulls parameters out of the ConfigParser's access section.
    45         """
    46 
    47         # Make sure that the configuration is in place
    48         if not config:
    49             raise RunTimeError("No config to fedd.access")
    50 
    51         self.project_priority = config.getboolean("access", "project_priority")
    52         self.allow_proxy = config.getboolean("access", "allow_proxy")
    53 
    54         self.boss = config.get("access", "boss")
    55         self.ops = config.get("access", "ops")
    56         self.domain = config.get("access", "domain")
    57         self.fileserver = config.get("access", "fileserver")
    58         self.eventserver = config.get("access", "eventserver")
    59 
    60         self.attrs = { }
    61         self.access = { }
    62         self.restricted = [ ]
    63         self.projects = { }
    64         self.keys = { }
    65         self.types = { }
    66         self.allocation = { }
    67         self.state = {
    68             'projects': self.projects,
    69             'allocation' : self.allocation,
    70             'keys' : self.keys,
    71             'types': self.types
    72         }
    73         self.log = logging.getLogger("fedd.access")
    74         set_log_level(config, "access", self.log)
    75         self.state_lock = Lock()
    76 
    77         if auth: self.auth = auth
    78         else:
    79             self.log.error(\
    80                     "[access]: No authorizer initialized, creating local one.")
    81             auth = authorizer()
    82 
    83         tb = config.get('access', 'testbed')
    84         if tb: self.testbed = [ t.strip() for t in tb.split(',') ]
    85         else: self.testbed = [ ]
    86 
    87         if config.has_option("access", "accessdb"):
    88             self.read_access(config.get("access", "accessdb"))
    89 
    90         self.state_filename = config.get("access", "access_state")
    91         self.read_state()
    92 
    93         # Keep cert_file and cert_pwd coming from the same place
    94         self.cert_file = config.get("access", "cert_file")
    95         if self.cert_file:
    96             self.sert_pwd = config.get("access", "cert_pw")
    97         else:
    98             self.cert_file = config.get("globals", "cert_file")
    99             self.sert_pwd = config.get("globals", "cert_pw")
    100 
    101         self.trusted_certs = config.get("access", "trusted_certs") or \
    102                 config.get("globals", "trusted_certs")
    103 
    104         self.soap_services = {\
    105             'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
    106             'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
    107             }
    108         self.xmlrpc_services =  {\
    109             'RequestAccess': xmlrpc_handler('RequestAccess',
    110                 self.RequestAccess),
    111             'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
    112                 self.ReleaseAccess),
    113             }
    114 
    115 
    116         if not config.has_option("allocate", "uri"):
    117             self.allocate_project = \
    118                 allocate_project_local(config, auth)
    119         else:
    120             self.allocate_project = \
    121                 allocate_project_remote(config, auth)
    122 
    123         # If the project allocator exports services, put them in this object's
    124         # maps so that classes that instantiate this can call the services.
    125         self.soap_services.update(self.allocate_project.soap_services)
    126         self.xmlrpc_services.update(self.allocate_project.xmlrpc_services)
     43        """
     44        Initializer.  Pulls parameters out of the ConfigParser's access section.
     45        """
     46
     47        # Make sure that the configuration is in place
     48        if not config:
     49            raise RunTimeError("No config to fedd.access")
     50
     51        self.project_priority = config.getboolean("access", "project_priority")
     52        self.allow_proxy = config.getboolean("access", "allow_proxy")
     53
     54        self.boss = config.get("access", "boss")
     55        self.ops = config.get("access", "ops")
     56        self.domain = config.get("access", "domain")
     57        self.fileserver = config.get("access", "fileserver")
     58        self.eventserver = config.get("access", "eventserver")
     59
     60        self.attrs = { }
     61        self.access = { }
     62        self.restricted = [ ]
     63        self.projects = { }
     64        self.keys = { }
     65        self.types = { }
     66        self.allocation = { }
     67        self.state = {
     68            'projects': self.projects,
     69            'allocation' : self.allocation,
     70            'keys' : self.keys,
     71            'types': self.types
     72        }
     73        self.log = logging.getLogger("fedd.access")
     74        set_log_level(config, "access", self.log)
     75        self.state_lock = Lock()
     76
     77        if auth: self.auth = auth
     78        else:
     79            self.log.error(\
     80                    "[access]: No authorizer initialized, creating local one.")
     81            auth = authorizer()
     82
     83        tb = config.get('access', 'testbed')
     84        if tb: self.testbed = [ t.strip() for t in tb.split(',') ]
     85        else: self.testbed = [ ]
     86
     87        if config.has_option("access", "accessdb"):
     88            self.read_access(config.get("access", "accessdb"))
     89
     90        self.state_filename = config.get("access", "access_state")
     91        self.read_state()
     92
     93        # Keep cert_file and cert_pwd coming from the same place
     94        self.cert_file = config.get("access", "cert_file")
     95        if self.cert_file:
     96            self.sert_pwd = config.get("access", "cert_pw")
     97        else:
     98            self.cert_file = config.get("globals", "cert_file")
     99            self.sert_pwd = config.get("globals", "cert_pw")
     100
     101        self.trusted_certs = config.get("access", "trusted_certs") or \
     102                config.get("globals", "trusted_certs")
     103
     104        self.soap_services = {\
     105            'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
     106            'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
     107            }
     108        self.xmlrpc_services =  {\
     109            'RequestAccess': xmlrpc_handler('RequestAccess',
     110                self.RequestAccess),
     111            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
     112                self.ReleaseAccess),
     113            }
     114
     115
     116        if not config.has_option("allocate", "uri"):
     117            self.allocate_project = \
     118                allocate_project_local(config, auth)
     119        else:
     120            self.allocate_project = \
     121                allocate_project_remote(config, auth)
     122
     123        # If the project allocator exports services, put them in this object's
     124        # maps so that classes that instantiate this can call the services.
     125        self.soap_services.update(self.allocate_project.soap_services)
     126        self.xmlrpc_services.update(self.allocate_project.xmlrpc_services)
    127127
    128128
    129129    def read_access(self, config):
    130         """
    131         Read a configuration file and set internal parameters.
    132 
    133         The format is more complex than one might hope.  The basic format is
    134         attribute value pairs separated by colons(:) on a signle line.  The
    135         attributes in bool_attrs, emulab_attrs and id_attrs can all be set
    136         directly using the name: value syntax.  E.g.
    137         boss: hostname
    138         sets self.boss to hostname.  In addition, there are access lines of the
    139         form (tb, proj, user) -> (aproj, auser) that map the first tuple of
    140         names to the second for access purposes.  Names in the key (left side)
    141         can include "<NONE> or <ANY>" to act as wildcards or to require the
    142         fields to be empty.  Similarly aproj or auser can be <SAME> or
    143         <DYNAMIC> indicating that either the matching key is to be used or a
    144         dynamic user or project will be created.  These names can also be
    145         federated IDs (fedid's) if prefixed with fedid:.  Finally, the aproj
    146         can be followed with a colon-separated list of node types to which that
    147         project has access (or will have access if dynamic).
    148         Testbed attributes outside the forms above can be given using the
    149         format attribute: name value: value.  The name is a single word and the
    150         value continues to the end of the line.  Empty lines and lines startin
    151         with a # are ignored.
    152 
    153         Parsing errors result in a self.parse_error exception being raised.
    154         """
    155         lineno=0
    156         name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
    157         fedid_expr = "fedid:[" + string.hexdigits + "]+"
    158         key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
    159         access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \
    160                 "<SAME>" + "(?::" + name_expr + ")*|" + \
    161                 fedid_expr + "(?::" + name_expr + ")*|" + \
    162                 name_expr + "(?::" + name_expr + ")*)"
    163         access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")"
    164 
    165         restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE)
    166         attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
    167                 re.IGNORECASE)
    168         access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
    169                 key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' +
    170                 access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE)
    171 
    172         def parse_name(n):
    173             if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])
    174             else: return n
    175        
    176         def auth_name(n):
    177             if isinstance(n, basestring):
    178                 if n =='<any>' or n =='<none>': return None
    179                 else: return unicode(n)
    180             else:
    181                 return n
    182 
    183         f = open(config, "r");
    184         for line in f:
    185             lineno += 1
    186             line = line.strip();
    187             if len(line) == 0 or line.startswith('#'):
    188                 continue
    189 
    190             # Extended (attribute: x value: y) attribute line
    191             m = attr_re.match(line)
    192             if m != None:
    193                 attr, val = m.group(1,2)
    194                 self.attrs[attr] = val
    195                 continue
    196 
    197             # Restricted entry
    198             m = restricted_re.match(line)
    199             if m != None:
    200                 val = m.group(1)
    201                 self.restricted.append(val)
    202                 continue
    203 
    204             # Access line (t, p, u) -> (ap, cu, su) line
    205             m = access_re.match(line)
    206             if m != None:
    207                 access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
    208                 auth_key = tuple([ auth_name(x) for x in access_key])
    209                 aps = m.group(4).split(":");
    210                 if aps[0] == 'fedid:':
    211                     del aps[0]
    212                     aps[0] = fedid(hexstr=aps[0])
    213 
    214                 cu = parse_name(m.group(5))
    215                 su = parse_name(m.group(6))
    216 
    217                 access_val = (access_project(aps[0], aps[1:]),
    218                         parse_name(m.group(5)), parse_name(m.group(6)))
    219 
    220                 self.access[access_key] = access_val
    221                 self.auth.set_attribute(auth_key, "access")
    222                 continue
    223 
    224             # Nothing matched to here: unknown line - raise exception
    225             f.close()
    226             raise self.parse_error("Unknown statement at line %d of %s" % \
    227                     (lineno, config))
    228         f.close()
     130        """
     131        Read a configuration file and set internal parameters.
     132
     133        The format is more complex than one might hope.  The basic format is
     134        attribute value pairs separated by colons(:) on a signle line.  The
     135        attributes in bool_attrs, emulab_attrs and id_attrs can all be set
     136        directly using the name: value syntax.  E.g.
     137        boss: hostname
     138        sets self.boss to hostname.  In addition, there are access lines of the
     139        form (tb, proj, user) -> (aproj, auser) that map the first tuple of
     140        names to the second for access purposes.  Names in the key (left side)
     141        can include "<NONE> or <ANY>" to act as wildcards or to require the
     142        fields to be empty.  Similarly aproj or auser can be <SAME> or
     143        <DYNAMIC> indicating that either the matching key is to be used or a
     144        dynamic user or project will be created.  These names can also be
     145        federated IDs (fedid's) if prefixed with fedid:.  Finally, the aproj
     146        can be followed with a colon-separated list of node types to which that
     147        project has access (or will have access if dynamic).
     148        Testbed attributes outside the forms above can be given using the
     149        format attribute: name value: value.  The name is a single word and the
     150        value continues to the end of the line.  Empty lines and lines startin
     151        with a # are ignored.
     152
     153        Parsing errors result in a self.parse_error exception being raised.
     154        """
     155        lineno=0
     156        name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
     157        fedid_expr = "fedid:[" + string.hexdigits + "]+"
     158        key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
     159        access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \
     160                "<SAME>" + "(?::" + name_expr + ")*|" + \
     161                fedid_expr + "(?::" + name_expr + ")*|" + \
     162                name_expr + "(?::" + name_expr + ")*)"
     163        access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")"
     164
     165        restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE)
     166        attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
     167                re.IGNORECASE)
     168        access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
     169                key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' +
     170                access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE)
     171
     172        def parse_name(n):
     173            if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])
     174            else: return n
     175       
     176        def auth_name(n):
     177            if isinstance(n, basestring):
     178                if n =='<any>' or n =='<none>': return None
     179                else: return unicode(n)
     180            else:
     181                return n
     182
     183        f = open(config, "r");
     184        for line in f:
     185            lineno += 1
     186            line = line.strip();
     187            if len(line) == 0 or line.startswith('#'):
     188                continue
     189
     190            # Extended (attribute: x value: y) attribute line
     191            m = attr_re.match(line)
     192            if m != None:
     193                attr, val = m.group(1,2)
     194                self.attrs[attr] = val
     195                continue
     196
     197            # Restricted entry
     198            m = restricted_re.match(line)
     199            if m != None:
     200                val = m.group(1)
     201                self.restricted.append(val)
     202                continue
     203
     204            # Access line (t, p, u) -> (ap, cu, su) line
     205            m = access_re.match(line)
     206            if m != None:
     207                access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
     208                auth_key = tuple([ auth_name(x) for x in access_key])
     209                aps = m.group(4).split(":");
     210                if aps[0] == 'fedid:':
     211                    del aps[0]
     212                    aps[0] = fedid(hexstr=aps[0])
     213
     214                cu = parse_name(m.group(5))
     215                su = parse_name(m.group(6))
     216
     217                access_val = (access_project(aps[0], aps[1:]),
     218                        parse_name(m.group(5)), parse_name(m.group(6)))
     219
     220                self.access[access_key] = access_val
     221                self.auth.set_attribute(auth_key, "access")
     222                continue
     223
     224            # Nothing matched to here: unknown line - raise exception
     225            f.close()
     226            raise self.parse_error("Unknown statement at line %d of %s" % \
     227                    (lineno, config))
     228        f.close()
    229229
    230230    def get_users(self, obj):
    231         """
    232         Return a list of the IDs of the users in dict
    233         """
    234         if obj.has_key('user'):
    235             return [ unpack_id(u['userID']) \
    236                     for u in obj['user'] if u.has_key('userID') ]
    237         else:
    238             return None
     231        """
     232        Return a list of the IDs of the users in dict
     233        """
     234        if obj.has_key('user'):
     235            return [ unpack_id(u['userID']) \
     236                    for u in obj['user'] if u.has_key('userID') ]
     237        else:
     238            return None
    239239
    240240    def write_state(self):
    241         if self.state_filename:
    242             try:
    243                 f = open(self.state_filename, 'w')
    244                 pickle.dump(self.state, f)
    245             except IOError, e:
    246                 self.log.error("Can't write file %s: %s" % \
    247                         (self.state_filename, e))
    248             except pickle.PicklingError, e:
    249                 self.log.error("Pickling problem: %s" % e)
    250             except TypeError, e:
    251                 self.log.error("Pickling problem (TypeError): %s" % e)
     241        if self.state_filename:
     242            try:
     243                f = open(self.state_filename, 'w')
     244                pickle.dump(self.state, f)
     245            except IOError, e:
     246                self.log.error("Can't write file %s: %s" % \
     247                        (self.state_filename, e))
     248            except pickle.PicklingError, e:
     249                self.log.error("Pickling problem: %s" % e)
     250            except TypeError, e:
     251                self.log.error("Pickling problem (TypeError): %s" % e)
    252252
    253253
    254254    def read_state(self):
    255         """
    256         Read a new copy of access state.  Old state is overwritten.
    257 
    258         State format is a simple pickling of the state dictionary.
    259         """
    260         if self.state_filename:
    261             try:
    262                 f = open(self.state_filename, "r")
    263                 self.state = pickle.load(f)
    264 
    265                 self.allocation = self.state['allocation']
    266                 self.projects = self.state['projects']
    267                 self.keys = self.state['keys']
    268                 self.types = self.state['types']
    269 
    270                 self.log.debug("[read_state]: Read state from %s" % \
    271                         self.state_filename)
    272             except IOError, e:
    273                 self.log.warning(("[read_state]: No saved state: " +\
    274                         "Can't open %s: %s") % (self.state_filename, e))
    275             except EOFError, e:
    276                 self.log.warning(("[read_state]: " +\
    277                         "Empty or damaged state file: %s:") % \
    278                         self.state_filename)
    279             except pickle.UnpicklingError, e:
    280                 self.log.warning(("[read_state]: No saved state: " + \
    281                         "Unpickling failed: %s") % e)
    282 
    283             # Add the ownership attributes to the authorizer.  Note that the
    284             # indices of the allocation dict are strings, but the attributes are
    285             # fedids, so there is a conversion.
    286             for k in self.allocation.keys():
    287                 for o in self.allocation[k].get('owners', []):
    288                     self.auth.set_attribute(o, fedid(hexstr=k))
     255        """
     256        Read a new copy of access state.  Old state is overwritten.
     257
     258        State format is a simple pickling of the state dictionary.
     259        """
     260        if self.state_filename:
     261            try:
     262                f = open(self.state_filename, "r")
     263                self.state = pickle.load(f)
     264
     265                self.allocation = self.state['allocation']
     266                self.projects = self.state['projects']
     267                self.keys = self.state['keys']
     268                self.types = self.state['types']
     269
     270                self.log.debug("[read_state]: Read state from %s" % \
     271                        self.state_filename)
     272            except IOError, e:
     273                self.log.warning(("[read_state]: No saved state: " +\
     274                        "Can't open %s: %s") % (self.state_filename, e))
     275            except EOFError, e:
     276                self.log.warning(("[read_state]: " +\
     277                        "Empty or damaged state file: %s:") % \
     278                        self.state_filename)
     279            except pickle.UnpicklingError, e:
     280                self.log.warning(("[read_state]: No saved state: " + \
     281                        "Unpickling failed: %s") % e)
     282
     283            # Add the ownership attributes to the authorizer.  Note that the
     284            # indices of the allocation dict are strings, but the attributes are
     285            # fedids, so there is a conversion.
     286            for k in self.allocation.keys():
     287                for o in self.allocation[k].get('owners', []):
     288                    self.auth.set_attribute(o, fedid(hexstr=k))
    289289
    290290
    291291    def permute_wildcards(self, a, p):
    292         """Return a copy of a with various fields wildcarded.
    293 
    294         The bits of p control the wildcards.  A set bit is a wildcard
    295         replacement with the lowest bit being user then project then testbed.
    296         """
    297         if p & 1: user = ["<any>"]
    298         else: user = a[2]
    299         if p & 2: proj = "<any>"
    300         else: proj = a[1]
    301         if p & 4: tb = "<any>"
    302         else: tb = a[0]
    303 
    304         return (tb, proj, user)
     292        """Return a copy of a with various fields wildcarded.
     293
     294        The bits of p control the wildcards.  A set bit is a wildcard
     295        replacement with the lowest bit being user then project then testbed.
     296        """
     297        if p & 1: user = ["<any>"]
     298        else: user = a[2]
     299        if p & 2: proj = "<any>"
     300        else: proj = a[1]
     301        if p & 4: tb = "<any>"
     302        else: tb = a[0]
     303
     304        return (tb, proj, user)
    305305
    306306    def find_access(self, search):
    307         """
    308         Search the access DB for a match on this tuple.  Return the matching
    309         access tuple and the user that matched.
    310        
    311         NB, if the initial tuple fails to match we start inserting wildcards in
    312         an order determined by self.project_priority.  Try the list of users in
    313         order (when wildcarded, there's only one user in the list).
    314         """
    315         if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
    316         else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
    317 
    318         for p in perm:
    319             s = self.permute_wildcards(search, p)
    320             # s[2] is None on an anonymous, unwildcarded request
    321             if s[2] != None:
    322                 for u in s[2]:
    323                     if self.access.has_key((s[0], s[1], u)):
    324                         return (self.access[(s[0], s[1], u)], u)
    325             else:
    326                 if self.access.has_key(s):
    327                     return (self.access[s], None)
    328         return None, None
     307        """
     308        Search the access DB for a match on this tuple.  Return the matching
     309        access tuple and the user that matched.
     310       
     311        NB, if the initial tuple fails to match we start inserting wildcards in
     312        an order determined by self.project_priority.  Try the list of users in
     313        order (when wildcarded, there's only one user in the list).
     314        """
     315        if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
     316        else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
     317
     318        for p in perm:
     319            s = self.permute_wildcards(search, p)
     320            # s[2] is None on an anonymous, unwildcarded request
     321            if s[2] != None:
     322                for u in s[2]:
     323                    if self.access.has_key((s[0], s[1], u)):
     324                        return (self.access[(s[0], s[1], u)], u)
     325            else:
     326                if self.access.has_key(s):
     327                    return (self.access[s], None)
     328        return None, None
    329329
    330330    def lookup_access(self, req, fid):
    331         """
    332         Determine the allowed access for this request.  Return the access and
    333         which fields are dynamic.
    334 
    335         The fedid is needed to construct the request
    336         """
    337         # Search keys
    338         tb = None
    339         project = None
    340         user = None
    341         # Return values
    342         rp = access_project(None, ())
    343         ru = None
    344 
    345         if req.has_key('project'):
    346             p = req['project']
    347             if p.has_key('name'):
    348                 project = unpack_id(p['name'])
    349             user = self.get_users(p)
    350         else:
    351             user = self.get_users(req)
    352 
    353         user_fedids = [ u for u in user if isinstance(u, fedid)]
    354         # Determine how the caller is representing itself.  If its fedid shows
    355         # up as a project or a singleton user, let that stand.  If neither the
    356         # usernames nor the project name is a fedid, the caller is a testbed.
    357         if project and isinstance(project, fedid):
    358             if project == fid:
    359                 # The caller is the project (which is already in the tuple
    360                 # passed in to the authorizer)
    361                 owners = user_fedids
    362                 owners.append(project)
    363             else:
    364                 raise service_error(service_error.req,
    365                         "Project asserting different fedid")
    366         else:
    367             if fid not in user_fedids:
    368                 tb = fid
    369                 owners = user_fedids
    370                 owners.append(fid)
    371             else:
    372                 if len(fedids) > 1:
    373                     raise service_error(service_error.req,
    374                             "User asserting different fedid")
    375                 else:
    376                     # Which is a singleton
    377                     owners = user_fedids
    378         # Confirm authorization
    379 
    380         for u in user:
    381             self.log.debug("[lookup_access] Checking access for %s" % \
    382                     ((tb, project, u),))
    383             if self.auth.check_attribute((tb, project, u), 'access'):
    384                 self.log.debug("[lookup_access] Access granted")
    385                 break
    386             else:
    387                 self.log.debug("[lookup_access] Access Denied")
    388         else:
    389             raise service_error(service_error.access, "Access denied")
    390 
    391         # This maps a valid user to the Emulab projects and users to use
    392         found, user_match = self.find_access((tb, project, user))
    393        
    394         if found == None:
    395             raise service_error(service_error.access,
    396                     "Access denied - cannot map access")
    397 
    398         # resolve <dynamic> and <same> in found
    399         dyn_proj = False
    400         dyn_create_user = False
    401         dyn_service_user = False
    402 
    403         if found[0].name == "<same>":
    404             if project != None:
    405                 rp.name = project
    406             else :
    407                 raise service_error(\
    408                         service_error.server_config,
    409                         "Project matched <same> when no project given")
    410         elif found[0].name == "<dynamic>":
    411             rp.name = None
    412             dyn_proj = True
    413         else:
    414             rp.name = found[0].name
    415         rp.node_types = found[0].node_types;
    416 
    417         if found[1] == "<same>":
    418             if user_match == "<any>":
    419                 if user != None: rcu = user[0]
    420                 else: raise service_error(\
    421                         service_error.server_config,
    422                         "Matched <same> on anonymous request")
    423             else:
    424                 rcu = user_match
    425         elif found[1] == "<dynamic>":
    426             rcu = None
    427             dyn_create_user = True
    428         else:
    429             rcu = found[1]
    430        
    431         if found[2] == "<same>":
    432             if user_match == "<any>":
    433                 if user != None: rsu = user[0]
    434                 else: raise service_error(\
    435                         service_error.server_config,
    436                         "Matched <same> on anonymous request")
    437             else:
    438                 rsu = user_match
    439         elif found[2] == "<dynamic>":
    440             rsu = None
    441             dyn_service_user = True
    442         else:
    443             rsu = found[2]
    444 
    445         return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\
    446                 owners
     331        """
     332        Determine the allowed access for this request.  Return the access and
     333        which fields are dynamic.
     334
     335        The fedid is needed to construct the request
     336        """
     337        # Search keys
     338        tb = None
     339        project = None
     340        user = None
     341        # Return values
     342        rp = access_project(None, ())
     343        ru = None
     344
     345        if req.has_key('project'):
     346            p = req['project']
     347            if p.has_key('name'):
     348                project = unpack_id(p['name'])
     349            user = self.get_users(p)
     350        else:
     351            user = self.get_users(req)
     352
     353        user_fedids = [ u for u in user if isinstance(u, fedid)]
     354        # Determine how the caller is representing itself.  If its fedid shows
     355        # up as a project or a singleton user, let that stand.  If neither the
     356        # usernames nor the project name is a fedid, the caller is a testbed.
     357        if project and isinstance(project, fedid):
     358            if project == fid:
     359                # The caller is the project (which is already in the tuple
     360                # passed in to the authorizer)
     361                owners = user_fedids
     362                owners.append(project)
     363            else:
     364                raise service_error(service_error.req,
     365                        "Project asserting different fedid")
     366        else:
     367            if fid not in user_fedids:
     368                tb = fid
     369                owners = user_fedids
     370                owners.append(fid)
     371            else:
     372                if len(fedids) > 1:
     373                    raise service_error(service_error.req,
     374                            "User asserting different fedid")
     375                else:
     376                    # Which is a singleton
     377                    owners = user_fedids
     378        # Confirm authorization
     379
     380        for u in user:
     381            self.log.debug("[lookup_access] Checking access for %s" % \
     382                    ((tb, project, u),))
     383            if self.auth.check_attribute((tb, project, u), 'access'):
     384                self.log.debug("[lookup_access] Access granted")
     385                break
     386            else:
     387                self.log.debug("[lookup_access] Access Denied")
     388        else:
     389            raise service_error(service_error.access, "Access denied")
     390
     391        # This maps a valid user to the Emulab projects and users to use
     392        found, user_match = self.find_access((tb, project, user))
     393       
     394        if found == None:
     395            raise service_error(service_error.access,
     396                    "Access denied - cannot map access")
     397
     398        # resolve <dynamic> and <same> in found
     399        dyn_proj = False
     400        dyn_create_user = False
     401        dyn_service_user = False
     402
     403        if found[0].name == "<same>":
     404            if project != None:
     405                rp.name = project
     406            else :
     407                raise service_error(\
     408                        service_error.server_config,
     409                        "Project matched <same> when no project given")
     410        elif found[0].name == "<dynamic>":
     411            rp.name = None
     412            dyn_proj = True
     413        else:
     414            rp.name = found[0].name
     415        rp.node_types = found[0].node_types;
     416
     417        if found[1] == "<same>":
     418            if user_match == "<any>":
     419                if user != None: rcu = user[0]
     420                else: raise service_error(\
     421                        service_error.server_config,
     422                        "Matched <same> on anonymous request")
     423            else:
     424                rcu = user_match
     425        elif found[1] == "<dynamic>":
     426            rcu = None
     427            dyn_create_user = True
     428        else:
     429            rcu = found[1]
     430       
     431        if found[2] == "<same>":
     432            if user_match == "<any>":
     433                if user != None: rsu = user[0]
     434                else: raise service_error(\
     435                        service_error.server_config,
     436                        "Matched <same> on anonymous request")
     437            else:
     438                rsu = user_match
     439        elif found[2] == "<dynamic>":
     440            rsu = None
     441            dyn_service_user = True
     442        else:
     443            rsu = found[2]
     444
     445        return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\
     446                owners
    447447
    448448    def build_response(self, alloc_id, ap):
    449         """
    450         Create the SOAP response.
    451 
    452         Build the dictionary description of the response and use
    453         fedd_utils.pack_soap to create the soap message.  ap is the allocate
    454         project message returned from a remote project allocation (even if that
    455         allocation was done locally).
    456         """
    457         # Because alloc_id is already a fedd_services_types.IDType_Holder,
    458         # there's no need to repack it
    459         msg = {
    460                 'allocID': alloc_id,
    461                 'emulab': {
    462                     'domain': self.domain,
    463                     'boss': self.boss,
    464                     'ops': self.ops,
    465                     'fileServer': self.fileserver,
    466                     'eventServer': self.eventserver,
    467                     'project': ap['project']
    468                 },
    469             }
    470         if len(self.attrs) > 0:
    471             msg['emulab']['fedAttr'] = \
    472                 [ { 'attribute': x, 'value' : y } \
    473                         for x,y in self.attrs.iteritems()]
    474         return msg
     449        """
     450        Create the SOAP response.
     451
     452        Build the dictionary description of the response and use
     453        fedd_utils.pack_soap to create the soap message.  ap is the allocate
     454        project message returned from a remote project allocation (even if that
     455        allocation was done locally).
     456        """
     457        # Because alloc_id is already a fedd_services_types.IDType_Holder,
     458        # there's no need to repack it
     459        msg = {
     460                'allocID': alloc_id,
     461                'emulab': {
     462                    'domain': self.domain,
     463                    'boss': self.boss,
     464                    'ops': self.ops,
     465                    'fileServer': self.fileserver,
     466                    'eventServer': self.eventserver,
     467                    'project': ap['project']
     468                },
     469            }
     470        if len(self.attrs) > 0:
     471            msg['emulab']['fedAttr'] = \
     472                [ { 'attribute': x, 'value' : y } \
     473                        for x,y in self.attrs.iteritems()]
     474        return msg
    475475
    476476    def RequestAccess(self, req, fid):
    477         """
    478         Handle the access request.  Proxy if not for us.
    479 
    480         Parse out the fields and make the allocations or rejections if for us,
    481         otherwise, assuming we're willing to proxy, proxy the request out.
    482         """
    483 
    484         def gateway_hardware(h):
    485             if h == 'GWTYPE': return self.attrs.get('connectorType', 'GWTYPE')
    486             else: return h
    487 
    488         # The dance to get into the request body
    489         if req.has_key('RequestAccessRequestBody'):
    490             req = req['RequestAccessRequestBody']
    491         else:
    492             raise service_error(service_error.req, "No request!?")
    493 
    494         if req.has_key('destinationTestbed'):
    495             dt = unpack_id(req['destinationTestbed'])
    496 
    497         if dt == None or dt in self.testbed:
    498             # Request for this fedd
    499             found, dyn, owners = self.lookup_access(req, fid)
    500             restricted = None
    501             ap = None
    502 
    503             # If this is a request to export a project and the access project
    504             # is not the project to export, access denied.
    505             if req.has_key('exportProject'):
    506                 ep = unpack_id(req['exportProject'])
    507                 if ep != found[0].name:
    508                     raise service_error(service_error.access,
    509                             "Cannot export %s" % ep)
    510 
    511             # Check for access to restricted nodes
    512             if req.has_key('resources') and req['resources'].has_key('node'):
    513                 resources = req['resources']
    514                 restricted = [ gateway_hardware(t) for n in resources['node'] \
    515                                 if n.has_key('hardware') \
    516                                     for t in n['hardware'] \
    517                                         if gateway_hardware(t) \
    518                                             in self.restricted ]
    519                 inaccessible = [ t for t in restricted \
    520                                     if t not in found[0].node_types]
    521                 if len(inaccessible) > 0:
    522                     raise service_error(service_error.access,
    523                             "Access denied (nodetypes %s)" % \
    524                             str(', ').join(inaccessible))
    525             # These collect the keys for the two roles into single sets, one
    526             # for creation and one for service.  The sets are a simple way to
    527             # eliminate duplicates
    528             create_ssh = set([ x['sshPubkey'] \
    529                     for x in req['createAccess'] \
    530                         if x.has_key('sshPubkey')])
    531 
    532             service_ssh = set([ x['sshPubkey'] \
    533                     for x in req['serviceAccess'] \
    534                         if x.has_key('sshPubkey')])
    535 
    536             if len(create_ssh) > 0 and len(service_ssh) >0:
    537                 if dyn[1]:
    538                     # Compose the dynamic project request
    539                     # (only dynamic, dynamic currently allowed)
    540                     preq = { 'AllocateProjectRequestBody': \
    541                                 { 'project' : {\
    542                                     'user': [ \
    543                                     { \
    544                                         'access': [ { 'sshPubkey': s } \
    545                                             for s in service_ssh ],
    546                                         'role': "serviceAccess",\
    547                                     }, \
    548                                     { \
    549                                         'access': [ { 'sshPubkey': s } \
    550                                             for s in create_ssh ],
    551                                         'role': "experimentCreation",\
    552                                     }, \
    553                                     ], \
    554                                     }\
    555                                 }\
    556                             }
    557                     if restricted != None and len(restricted) > 0:
    558                         preq['AllocateProjectRequestBody']['resources'] = \
    559                              {'node': [ { 'hardware' :  [ h ] } \
    560                                     for h in restricted ] }
    561                     ap = self.allocate_project.dynamic_project(preq)
    562                 else:
    563                     preq = {'StaticProjectRequestBody' : \
    564                             { 'project': \
    565                                 { 'name' : { 'localname' : found[0].name },\
    566                                   'user' : [ \
    567                                     {\
    568                                         'userID': { 'localname' : found[1] }, \
    569                                         'access': [ { 'sshPubkey': s }
    570                                             for s in create_ssh ],
    571                                         'role': 'experimentCreation'\
    572                                     },\
    573                                     {\
    574                                         'userID': { 'localname' : found[2] }, \
    575                                         'access': [ { 'sshPubkey': s }
    576                                             for s in service_ssh ],
    577                                         'role': 'serviceAccess'\
    578                                     },\
    579                                 ]}\
    580                             }\
    581                     }
    582                     if restricted != None and len(restricted) > 0:
    583                         preq['StaticProjectRequestBody']['resources'] = \
    584                             {'node': [ { 'hardware' :  [ h ] } \
    585                                     for h in restricted ] }
    586                     ap = self.allocate_project.static_project(preq)
    587             else:
    588                 raise service_error(service_error.req,
    589                         "SSH access parameters required")
    590             # keep track of what's been added
    591             allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
    592             aid = unicode(allocID)
    593 
    594             self.state_lock.acquire()
    595             self.allocation[aid] = { }
    596             try:
    597                 pname = ap['project']['name']['localname']
    598             except KeyError:
    599                 pname = None
    600 
    601             if dyn[1]:
    602                 if not pname:
    603                     self.state_lock.release()
    604                     raise service_error(service_error.internal,
    605                             "Misformed allocation response?")
    606                 if self.projects.has_key(pname): self.projects[pname] += 1
    607                 else: self.projects[pname] = 1
    608                 self.allocation[aid]['project'] = pname
    609 
    610             if ap.has_key('resources'):
    611                 if not pname:
    612                     self.state_lock.release()
    613                     raise service_error(service_error.internal,
    614                             "Misformed allocation response?")
    615                 self.allocation[aid]['types'] = set()
    616                 nodes = ap['resources'].get('node', [])
    617                 for n in nodes:
    618                     for h in n.get('hardware', []):
    619                         if self.types.has_key((pname, h)):
    620                             self.types[(pname, h)] += 1
    621                         else:
    622                             self.types[(pname, h)] = 1
    623                         self.allocation[aid]['types'].add((pname,h))
    624 
    625 
    626             self.allocation[aid]['keys'] = [ ]
    627 
    628             try:
    629                 for u in ap['project']['user']:
    630                     uname = u['userID']['localname']
    631                     for k in [ k['sshPubkey'] for k in u['access'] \
    632                             if k.has_key('sshPubkey') ]:
    633                         kv = "%s:%s" % (uname, k)
    634                         if self.keys.has_key(kv): self.keys[kv] += 1
    635                         else: self.keys[kv] = 1
    636                         self.allocation[aid]['keys'].append((uname, k))
    637             except KeyError:
    638                 self.state_lock.release()
    639                 raise service_error(service_error.internal,
    640                         "Misformed allocation response?")
    641 
    642 
    643             self.allocation[aid]['owners'] = owners
    644             self.write_state()
    645             self.state_lock.release()
    646             for o in owners:
    647                 self.auth.set_attribute(o, allocID)
    648             resp = self.build_response({ 'fedid': allocID } , ap)
    649             return resp
    650         else:
    651             if self.allow_proxy:
    652                 resp = self.proxy_RequestAccess.call_service(dt, req,
    653                             self.cert_file, self.cert_pwd,
    654                             self.trusted_certs)
    655                 if resp.has_key('RequestAccessResponseBody'):
    656                     return resp['RequestAccessResponseBody']
    657                 else:
    658                     return None
    659             else:
    660                 raise service_error(service_error.access,
    661                         "Access proxying denied")
     477        """
     478        Handle the access request.  Proxy if not for us.
     479
     480        Parse out the fields and make the allocations or rejections if for us,
     481        otherwise, assuming we're willing to proxy, proxy the request out.
     482        """
     483
     484        def gateway_hardware(h):
     485            if h == 'GWTYPE': return self.attrs.get('connectorType', 'GWTYPE')
     486            else: return h
     487
     488        # The dance to get into the request body
     489        if req.has_key('RequestAccessRequestBody'):
     490            req = req['RequestAccessRequestBody']
     491        else:
     492            raise service_error(service_error.req, "No request!?")
     493
     494        if req.has_key('destinationTestbed'):
     495            dt = unpack_id(req['destinationTestbed'])
     496
     497        if dt == None or dt in self.testbed:
     498            # Request for this fedd
     499            found, dyn, owners = self.lookup_access(req, fid)
     500            restricted = None
     501            ap = None
     502
     503            # If this is a request to export a project and the access project
     504            # is not the project to export, access denied.
     505            if req.has_key('exportProject'):
     506                ep = unpack_id(req['exportProject'])
     507                if ep != found[0].name:
     508                    raise service_error(service_error.access,
     509                            "Cannot export %s" % ep)
     510
     511            # Check for access to restricted nodes
     512            if req.has_key('resources') and req['resources'].has_key('node'):
     513                resources = req['resources']
     514                restricted = [ gateway_hardware(t) for n in resources['node'] \
     515                                if n.has_key('hardware') \
     516                                    for t in n['hardware'] \
     517                                        if gateway_hardware(t) \
     518                                            in self.restricted ]
     519                inaccessible = [ t for t in restricted \
     520                                    if t not in found[0].node_types]
     521                if len(inaccessible) > 0:
     522                    raise service_error(service_error.access,
     523                            "Access denied (nodetypes %s)" % \
     524                            str(', ').join(inaccessible))
     525            # These collect the keys for the two roles into single sets, one
     526            # for creation and one for service.  The sets are a simple way to
     527            # eliminate duplicates
     528            create_ssh = set([ x['sshPubkey'] \
     529                    for x in req['createAccess'] \
     530                        if x.has_key('sshPubkey')])
     531
     532            service_ssh = set([ x['sshPubkey'] \
     533                    for x in req['serviceAccess'] \
     534                        if x.has_key('sshPubkey')])
     535
     536            if len(create_ssh) > 0 and len(service_ssh) >0:
     537                if dyn[1]:
     538                    # Compose the dynamic project request
     539                    # (only dynamic, dynamic currently allowed)
     540                    preq = { 'AllocateProjectRequestBody': \
     541                                { 'project' : {\
     542                                    'user': [ \
     543                                    { \
     544                                        'access': [ { 'sshPubkey': s } \
     545                                            for s in service_ssh ],
     546                                        'role': "serviceAccess",\
     547                                    }, \
     548                                    { \
     549                                        'access': [ { 'sshPubkey': s } \
     550                                            for s in create_ssh ],
     551                                        'role': "experimentCreation",\
     552                                    }, \
     553                                    ], \
     554                                    }\
     555                                }\
     556                            }
     557                    if restricted != None and len(restricted) > 0:
     558                        preq['AllocateProjectRequestBody']['resources'] = \
     559                             {'node': [ { 'hardware' :  [ h ] } \
     560                                    for h in restricted ] }
     561                    ap = self.allocate_project.dynamic_project(preq)
     562                else:
     563                    preq = {'StaticProjectRequestBody' : \
     564                            { 'project': \
     565                                { 'name' : { 'localname' : found[0].name },\
     566                                  'user' : [ \
     567                                    {\
     568                                        'userID': { 'localname' : found[1] }, \
     569                                        'access': [ { 'sshPubkey': s }
     570                                            for s in create_ssh ],
     571                                        'role': 'experimentCreation'\
     572                                    },\
     573                                    {\
     574                                        'userID': { 'localname' : found[2] }, \
     575                                        'access': [ { 'sshPubkey': s }
     576                                            for s in service_ssh ],
     577                                        'role': 'serviceAccess'\
     578                                    },\
     579                                ]}\
     580                            }\
     581                    }
     582                    if restricted != None and len(restricted) > 0:
     583                        preq['StaticProjectRequestBody']['resources'] = \
     584                            {'node': [ { 'hardware' :  [ h ] } \
     585                                    for h in restricted ] }
     586                    ap = self.allocate_project.static_project(preq)
     587            else:
     588                raise service_error(service_error.req,
     589                        "SSH access parameters required")
     590            # keep track of what's been added
     591            allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
     592            aid = unicode(allocID)
     593
     594            self.state_lock.acquire()
     595            self.allocation[aid] = { }
     596            try:
     597                pname = ap['project']['name']['localname']
     598            except KeyError:
     599                pname = None
     600
     601            if dyn[1]:
     602                if not pname:
     603                    self.state_lock.release()
     604                    raise service_error(service_error.internal,
     605                            "Misformed allocation response?")
     606                if self.projects.has_key(pname): self.projects[pname] += 1
     607                else: self.projects[pname] = 1
     608                self.allocation[aid]['project'] = pname
     609
     610            if ap.has_key('resources'):
     611                if not pname:
     612                    self.state_lock.release()
     613                    raise service_error(service_error.internal,
     614                            "Misformed allocation response?")
     615                self.allocation[aid]['types'] = set()
     616                nodes = ap['resources'].get('node', [])
     617                for n in nodes:
     618                    for h in n.get('hardware', []):
     619                        if self.types.has_key((pname, h)):
     620                            self.types[(pname, h)] += 1
     621                        else:
     622                            self.types[(pname, h)] = 1
     623                        self.allocation[aid]['types'].add((pname,h))
     624
     625
     626            self.allocation[aid]['keys'] = [ ]
     627
     628            try:
     629                for u in ap['project']['user']:
     630                    uname = u['userID']['localname']
     631                    for k in [ k['sshPubkey'] for k in u['access'] \
     632                            if k.has_key('sshPubkey') ]:
     633                        kv = "%s:%s" % (uname, k)
     634                        if self.keys.has_key(kv): self.keys[kv] += 1
     635                        else: self.keys[kv] = 1
     636                        self.allocation[aid]['keys'].append((uname, k))
     637            except KeyError:
     638                self.state_lock.release()
     639                raise service_error(service_error.internal,
     640                        "Misformed allocation response?")
     641
     642
     643            self.allocation[aid]['owners'] = owners
     644            self.write_state()
     645            self.state_lock.release()
     646            for o in owners:
     647                self.auth.set_attribute(o, allocID)
     648            resp = self.build_response({ 'fedid': allocID } , ap)
     649            return resp
     650        else:
     651            if self.allow_proxy:
     652                resp = self.proxy_RequestAccess.call_service(dt, req,
     653                            self.cert_file, self.cert_pwd,
     654                            self.trusted_certs)
     655                if resp.has_key('RequestAccessResponseBody'):
     656                    return resp['RequestAccessResponseBody']
     657                else:
     658                    return None
     659            else:
     660                raise service_error(service_error.access,
     661                        "Access proxying denied")
    662662
    663663    def ReleaseAccess(self, req, fid):
    664         # The dance to get into the request body
    665         if req.has_key('ReleaseAccessRequestBody'):
    666             req = req['ReleaseAccessRequestBody']
    667         else:
    668             raise service_error(service_error.req, "No request!?")
    669 
    670         if req.has_key('destinationTestbed'):
    671             dt = unpack_id(req['destinationTestbed'])
    672         else:
    673             dt = None
    674 
    675         if dt == None or dt in self.testbed:
    676             # Local request
    677             try:
    678                 if req['allocID'].has_key('localname'):
    679                     auth_attr = aid = req['allocID']['localname']
    680                 elif req['allocID'].has_key('fedid'):
    681                     aid = unicode(req['allocID']['fedid'])
    682                     auth_attr = req['allocID']['fedid']
    683                 else:
    684                     raise service_error(service_error.req,
    685                             "Only localnames and fedids are understood")
    686             except KeyError:
    687                 raise service_error(service_error.req, "Badly formed request")
    688 
    689             self.log.debug("[access] deallocation requested for %s", aid)
    690             if not self.auth.check_attribute(fid, auth_attr):
    691                 self.log.debug("[access] deallocation denied for %s", aid)
    692                 raise service_error(service_error.access, "Access Denied")
    693 
    694             # If we know this allocation, reduce the reference counts and
    695             # remove the local allocations.  Otherwise report an error.  If
    696             # there is an allocation to delete, del_users will be a dictonary
    697             # of sets where the key is the user that owns the keys in the set.
    698             # We use a set to avoid duplicates.  del_project is just the name
    699             # of any dynamic project to delete.  We're somewhat lazy about
    700             # deleting authorization attributes.  Having access to something
    701             # that doesn't exist isn't harmful.
    702             del_users = { }
    703             del_project = None
    704             del_types = set()
    705 
    706             if self.allocation.has_key(aid):
    707                 self.log.debug("Found allocation for %s" %aid)
    708                 self.state_lock.acquire()
    709                 for k in self.allocation[aid]['keys']:
    710                     kk = "%s:%s" % k
    711                     self.keys[kk] -= 1
    712                     if self.keys[kk] == 0:
    713                         if not del_users.has_key(k[0]):
    714                             del_users[k[0]] = set()
    715                         del_users[k[0]].add(k[1])
    716                         del self.keys[kk]
    717 
    718                 if self.allocation[aid].has_key('project'):
    719                     pname = self.allocation[aid]['project']
    720                     self.projects[pname] -= 1
    721                     if self.projects[pname] == 0:
    722                         del_project = pname
    723                         del self.projects[pname]
    724 
    725                 if self.allocation[aid].has_key('types'):
    726                     for t in self.allocation[aid]['types']:
    727                         self.types[t] -= 1
    728                         if self.types[t] == 0:
    729                             if not del_project: del_project = t[0]
    730                             del_types.add(t[1])
    731                             del self.types[t]
    732 
    733                 del self.allocation[aid]
    734                 self.write_state()
    735                 self.state_lock.release()
    736                 # If we actually have resources to deallocate, prepare the call.
    737                 if del_project or del_users:
    738                     msg = { 'project': { }}
    739                     if del_project:
    740                         msg['project']['name']= {'localname': del_project}
    741                     users = [ ]
    742                     for u in del_users.keys():
    743                         users.append({ 'userID': { 'localname': u },\
    744                             'access' :  \
    745                                     [ {'sshPubkey' : s } for s in del_users[u]]\
    746                         })
    747                     if users:
    748                         msg['project']['user'] = users
    749                     if len(del_types) > 0:
    750                         msg['resources'] = { 'node': \
    751                                 [ {'hardware': [ h ] } for h in del_types ]\
    752                             }
    753                     if self.allocate_project.release_project:
    754                         msg = { 'ReleaseProjectRequestBody' : msg}
    755                         self.allocate_project.release_project(msg)
    756                 return { 'allocID': req['allocID'] }
    757             else:
    758                 raise service_error(service_error.req, "No such allocation")
    759 
    760         else:
    761             if self.allow_proxy:
    762                 resp = self.proxy_ReleaseAccess.call_service(dt, req,
    763                             self.cert_file, self.cert_pwd,
    764                             self.trusted_certs)
    765                 if resp.has_key('ReleaseAccessResponseBody'):
    766                     return resp['ReleaseAccessResponseBody']
    767                 else:
    768                     return None
    769             else:
    770                 raise service_error(service_error.access,
    771                         "Access proxying denied")
    772 
    773 
     664        # The dance to get into the request body
     665        if req.has_key('ReleaseAccessRequestBody'):
     666            req = req['ReleaseAccessRequestBody']
     667        else:
     668            raise service_error(service_error.req, "No request!?")
     669
     670        if req.has_key('destinationTestbed'):
     671            dt = unpack_id(req['destinationTestbed'])
     672        else:
     673            dt = None
     674
     675        if dt == None or dt in self.testbed:
     676            # Local request
     677            try:
     678                if req['allocID'].has_key('localname'):
     679                    auth_attr = aid = req['allocID']['localname']
     680                elif req['allocID'].has_key('fedid'):
     681                    aid = unicode(req['allocID']['fedid'])
     682                    auth_attr = req['allocID']['fedid']
     683                else:
     684                    raise service_error(service_error.req,
     685                            "Only localnames and fedids are understood")
     686            except KeyError:
     687                raise service_error(service_error.req, "Badly formed request")
     688
     689            self.log.debug("[access] deallocation requested for %s", aid)
     690            if not self.auth.check_attribute(fid, auth_attr):
     691                self.log.debug("[access] deallocation denied for %s", aid)
     692                raise service_error(service_error.access, "Access Denied")
     693
     694            # If we know this allocation, reduce the reference counts and
     695            # remove the local allocations.  Otherwise report an error.  If
     696            # there is an allocation to delete, del_users will be a dictonary
     697            # of sets where the key is the user that owns the keys in the set.
     698            # We use a set to avoid duplicates.  del_project is just the name
     699            # of any dynamic project to delete.  We're somewhat lazy about
     700            # deleting authorization attributes.  Having access to something
     701            # that doesn't exist isn't harmful.
     702            del_users = { }
     703            del_project = None
     704            del_types = set()
     705
     706            if self.allocation.has_key(aid):
     707                self.log.debug("Found allocation for %s" %aid)
     708                self.state_lock.acquire()
     709                for k in self.allocation[aid]['keys']:
     710                    kk = "%s:%s" % k
     711                    self.keys[kk] -= 1
     712                    if self.keys[kk] == 0:
     713                        if not del_users.has_key(k[0]):
     714                            del_users[k[0]] = set()
     715                        del_users[k[0]].add(k[1])
     716                        del self.keys[kk]
     717
     718                if self.allocation[aid].has_key('project'):
     719                    pname = self.allocation[aid]['project']
     720                    self.projects[pname] -= 1
     721                    if self.projects[pname] == 0:
     722                        del_project = pname
     723                        del self.projects[pname]
     724
     725                if self.allocation[aid].has_key('types'):
     726                    for t in self.allocation[aid]['types']:
     727                        self.types[t] -= 1
     728                        if self.types[t] == 0:
     729                            if not del_project: del_project = t[0]
     730                            del_types.add(t[1])
     731                            del self.types[t]
     732
     733                del self.allocation[aid]
     734                self.write_state()
     735                self.state_lock.release()
     736                # If we actually have resources to deallocate, prepare the call.
     737                if del_project or del_users:
     738                    msg = { 'project': { }}
     739                    if del_project:
     740                        msg['project']['name']= {'localname': del_project}
     741                    users = [ ]
     742                    for u in del_users.keys():
     743                        users.append({ 'userID': { 'localname': u },\
     744                            'access' :  \
     745                                    [ {'sshPubkey' : s } for s in del_users[u]]\
     746                        })
     747                    if users:
     748                        msg['project']['user'] = users
     749                    if len(del_types) > 0:
     750                        msg['resources'] = { 'node': \
     751                                [ {'hardware': [ h ] } for h in del_types ]\
     752                            }
     753                    if self.allocate_project.release_project:
     754                        msg = { 'ReleaseProjectRequestBody' : msg}
     755                        self.allocate_project.release_project(msg)
     756                return { 'allocID': req['allocID'] }
     757            else:
     758                raise service_error(service_error.req, "No such allocation")
     759
     760        else:
     761            if self.allow_proxy:
     762                resp = self.proxy_ReleaseAccess.call_service(dt, req,
     763                            self.cert_file, self.cert_pwd,
     764                            self.trusted_certs)
     765                if resp.has_key('ReleaseAccessResponseBody'):
     766                    return resp['ReleaseAccessResponseBody']
     767                else:
     768                    return None
     769            else:
     770                raise service_error(service_error.access,
     771                        "Access proxying denied")
     772
     773
Note: See TracChangeset for help on using the changeset viewer.