#!/usr/bin/env python # # Copyright 2005 Branko Cibej # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. '''mod_python authorization handler for mod_authz_svn groups. This handler reads group definitions from a mod_authz_svn access configuration file and does an authz check against that information. Supported Require directives: - Require valid-user Checks if the authenticated user is mentioned in any of the groups. Note that this is authorization, not authentication; so, a user may have been authenticated correctly, yet still fail this test if she is not mentioned in the authz config file. - Require group name... Check if the authenticated user is a member of any of the named groups. - Require user name... Ignored. The authentication handlers are supposed to check this. Configuration: PythonAuthzHandler authz_svn_group PythonOption AuthzSVNGroupFile /path/to/file PythonOption AuthzSVNGroupAuthoritative Yes/On/1|No/Off/0 ... AuthzSVNGroupFile: Path to the mod_authz_svn configuration file. AuthzSVNGroupAuthoritative: If turned off, authz_svn_group.py will return DECLINED rather than HTTP_FORBIDDEN if a Require directive is not satisfied. ''' import os, sys import ConfigParser from mod_python import apache class __authz_info: '''Encapsulation of group info from the mod_authz_svn access file.''' def __init__(self, authz_file): '''Parse the SVN access file.''' self.__groups = {} self.__users = {} cfg = ConfigParser.ConfigParser() cfg.read(authz_file) if cfg.has_section('groups'): self.__init_groups(cfg) def __init_groups(self, cfg): '''Compute user and group membership.''' group_list = cfg.options('groups') group_map = {} for group in group_list: names = map(lambda x: x.strip(), cfg.get('groups', group).split(',')) group_map[group] = names for name in names: if not name.startswith('@'): self.__users[name] = None for group in group_list: self.__groups[group] = self.__expand_group_users(group, group_map) def __expand_group_users(self, group, group_map): '''Return the complete (recursive) list of users that belong to a particular group, as a map.''' users = {} for name in group_map[group]: if not name.startswith('@'): users[name] = None else: users.update(self.__expand_group_users(name[1:], group_map)) return users def is_valid_user(self, user): '''Return True if the user is valid.''' return self.__users.has_key(user) def is_user_in_group(self, user, group): '''Return True if the user is in a particular group.''' return (self.__groups.has_key(group) and self.__groups[group].has_key(user)) class __config: '''Handler configuration''' AUTHZ_FILE = 'AuthzSVNGroupFile' AUTHORITATIVE = 'AuthzSVNGroupAuthoritative' def __init__(self, req): self.__authz_file = None self.__authoritative = True cfg = req.get_options() if cfg.has_key(self.AUTHZ_FILE): self.__authz_file = cfg[self.AUTHZ_FILE] if not os.path.exists(self.__authz_file): req.log_error(('%s: "%s" not found' % (self.AUTHZ_FILE, self.__authz_file)), apache.APLOG_ERR) raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR if cfg.has_key(self.AUTHORITATIVE): authcfg = cfg[self.AUTHORITATIVE].lower() if authcfg in ['yes', 'on', '1']: self.__authoritative = True elif authcfg in ['no', 'off', '0']: self.__authoritative = False else: req.log_error(('%s: invalid value "%s"' % (self.AUTHORITATIVE, cfg[self.AUTHORITATIVE])), apache.APLOG_ERR) raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR pass def authz_file(self): return self.__authz_file def authoritative(self): return self.__authoritative def __init_authz_info(req, cfg): '''Initialize the global authz info if it is not available yet. Return False if this module is disabled.''' if not globals().has_key('__authz_svn_group_info'): if cfg.authz_file() is None: return False global __authz_svn_group_info __authz_svn_group_info = __authz_info(cfg.authz_file()) return True def authzhandler(req): '''The authorization handler.''' cfg = __config(req) if not __init_authz_info(req, cfg): return apache.DECLINED if cfg.authoritative(): forbidden = apache.HTTP_FORBIDDEN else: forbidden = apache.DECLINED req.get_basic_auth_pw() for requires in req.requires(): if requires == 'valid-user': if not __authz_svn_group_info.is_valid_user(req.user): return forbidden elif requires.startswith('group '): for group in requires.split()[1:]: if __authz_svn_group_info.is_user_in_group(req.user, group): break else: return forbidden elif requires.startswith('user '): pass # Handled by the authen handler else: req.log_error('Unknown directive "Require %s"' % requires, apache.APLOG_ERR) return apache.HTTP_INTERNAL_SERVER_ERROR return apache.OK