Follow-up to r29036: Now that the "mergeinfo" transaction file is no
[svn.git] / contrib / server-side / authz_svn_group.py
blobefe720d73a547fd28e2bb9563aa9eede0c3d3132
1 #!/usr/bin/env python
3 # Copyright 2005 Branko Cibej <brane@xbc.nu>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 '''mod_python authorization handler for mod_authz_svn groups.
19 This handler reads group definitions from a mod_authz_svn access
20 configuration file and does an authz check against that information.
22 Supported Require directives:
24 - Require valid-user
25 Checks if the authenticated user is mentioned in any of the groups.
26 Note that this is authorization, not authentication; so, a user may
27 have been authenticated correctly, yet still fail this test if
28 she is not mentioned in the authz config file.
30 - Require group name...
31 Check if the authenticated user is a member of any of the named
32 groups.
34 - Require user name...
35 Ignored. The authentication handlers are supposed to check this.
37 Configuration:
39 <Location ...>
40 PythonAuthzHandler authz_svn_group
41 PythonOption AuthzSVNGroupFile /path/to/file
42 PythonOption AuthzSVNGroupAuthoritative Yes/On/1|No/Off/0
43 ...
44 </Location>
46 AuthzSVNGroupFile: Path to the mod_authz_svn configuration file.
47 AuthzSVNGroupAuthoritative: If turned off, authz_svn_group.py will
48 return DECLINED rather than HTTP_FORBIDDEN if a Require
49 directive is not satisfied.
50 '''
52 import os, sys
53 import ConfigParser
54 from mod_python import apache
56 class __authz_info:
57 '''Encapsulation of group info from the mod_authz_svn access file.'''
59 def __init__(self, authz_file):
60 '''Parse the SVN access file.'''
61 self.__groups = {}
62 self.__users = {}
63 cfg = ConfigParser.ConfigParser()
64 cfg.read(authz_file)
65 if cfg.has_section('groups'):
66 self.__init_groups(cfg)
68 def __init_groups(self, cfg):
69 '''Compute user and group membership.'''
70 group_list = cfg.options('groups')
71 group_map = {}
72 for group in group_list:
73 names = map(lambda x: x.strip(),
74 cfg.get('groups', group).split(','))
75 group_map[group] = names
76 for name in names:
77 if not name.startswith('@'):
78 self.__users[name] = None
79 for group in group_list:
80 self.__groups[group] = self.__expand_group_users(group, group_map)
82 def __expand_group_users(self, group, group_map):
83 '''Return the complete (recursive) list of users that belong to
84 a particular group, as a map.'''
85 users = {}
86 for name in group_map[group]:
87 if not name.startswith('@'):
88 users[name] = None
89 else:
90 users.update(self.__expand_group_users(name[1:], group_map))
91 return users
93 def is_valid_user(self, user):
94 '''Return True if the user is valid.'''
95 return self.__users.has_key(user)
97 def is_user_in_group(self, user, group):
98 '''Return True if the user is in a particular group.'''
99 return (self.__groups.has_key(group)
100 and self.__groups[group].has_key(user))
103 class __config:
104 '''Handler configuration'''
106 AUTHZ_FILE = 'AuthzSVNGroupFile'
107 AUTHORITATIVE = 'AuthzSVNGroupAuthoritative'
109 def __init__(self, req):
110 self.__authz_file = None
111 self.__authoritative = True
112 cfg = req.get_options()
114 if cfg.has_key(self.AUTHZ_FILE):
115 self.__authz_file = cfg[self.AUTHZ_FILE]
116 if not os.path.exists(self.__authz_file):
117 req.log_error(('%s: "%s" not found'
118 % (self.AUTHZ_FILE, self.__authz_file)),
119 apache.APLOG_ERR)
120 raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR
122 if cfg.has_key(self.AUTHORITATIVE):
123 authcfg = cfg[self.AUTHORITATIVE].lower()
124 if authcfg in ['yes', 'on', '1']:
125 self.__authoritative = True
126 elif authcfg in ['no', 'off', '0']:
127 self.__authoritative = False
128 else:
129 req.log_error(('%s: invalid value "%s"'
130 % (self.AUTHORITATIVE, cfg[self.AUTHORITATIVE])),
131 apache.APLOG_ERR)
132 raise apache.SERVER_RETURN, apache.HTTP_INTERNAL_SERVER_ERROR
133 pass
135 def authz_file(self):
136 return self.__authz_file
138 def authoritative(self):
139 return self.__authoritative
142 def __init_authz_info(req, cfg):
143 '''Initialize the global authz info if it is not available yet.
144 Return False if this module is disabled.'''
145 if not globals().has_key('__authz_svn_group_info'):
146 if cfg.authz_file() is None:
147 return False
148 global __authz_svn_group_info
149 __authz_svn_group_info = __authz_info(cfg.authz_file())
150 return True
153 def authzhandler(req):
154 '''The authorization handler.'''
155 cfg = __config(req)
156 if not __init_authz_info(req, cfg):
157 return apache.DECLINED
159 if cfg.authoritative():
160 forbidden = apache.HTTP_FORBIDDEN
161 else:
162 forbidden = apache.DECLINED
164 req.get_basic_auth_pw()
165 for requires in req.requires():
166 if requires == 'valid-user':
167 if not __authz_svn_group_info.is_valid_user(req.user):
168 return forbidden
169 elif requires.startswith('group '):
170 for group in requires.split()[1:]:
171 if __authz_svn_group_info.is_user_in_group(req.user, group):
172 break
173 else:
174 return forbidden
175 elif requires.startswith('user '):
176 pass # Handled by the authen handler
177 else:
178 req.log_error('Unknown directive "Require %s"' % requires,
179 apache.APLOG_ERR)
180 return apache.HTTP_INTERNAL_SERVER_ERROR
182 return apache.OK