comparison pylons_app/lib/middleware/simplehg.py @ 330:c961b78ff0a0

rewritten simplehg middleware. Now permissions are checked for each repository/request/user
author Marcin Kuzminski <marcin@python-works.com>
date Tue, 29 Jun 2010 20:43:01 +0200
parents 66f617f162f8
children 4c9a295d80a4
comparison
equal deleted inserted replaced
329:d6e2817734d2 330:c961b78ff0a0
24 @author: marcink 24 @author: marcink
25 SimpleHG middleware for handling mercurial protocol request (push/clone etc.) 25 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
26 It's implemented with basic auth function 26 It's implemented with basic auth function
27 """ 27 """
28 from datetime import datetime 28 from datetime import datetime
29 from itertools import chain
29 from mercurial.hgweb import hgweb 30 from mercurial.hgweb import hgweb
30 from mercurial.hgweb.request import wsgiapplication 31 from mercurial.hgweb.request import wsgiapplication
31 from paste.auth.basic import AuthBasicAuthenticator 32 from paste.auth.basic import AuthBasicAuthenticator
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE 33 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
33 from pylons_app.lib.auth import authfunc 34 from pylons_app.lib.auth import authfunc, HasPermissionAnyMiddleware
34 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache 35 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache
35 from pylons_app.model import meta 36 from pylons_app.model import meta
36 from pylons_app.model.db import UserLog, User 37 from pylons_app.model.db import UserLog, User
37 from webob.exc import HTTPNotFound 38 from webob.exc import HTTPNotFound, HTTPForbidden
38 import logging 39 import logging
39 import os 40 import os
40 from itertools import chain 41 import traceback
41 log = logging.getLogger(__name__) 42 log = logging.getLogger(__name__)
42 43
43 class SimpleHg(object): 44 class SimpleHg(object):
44 45
45 def __init__(self, application, config): 46 def __init__(self, application, config):
46 self.application = application 47 self.application = application
47 self.config = config 48 self.config = config
48 #authenticate this mercurial request using 49 #authenticate this mercurial request using
49 realm = '%s %s' % (config['hg_app_name'], 'mercurial repository') 50 realm = '%s %s' % (self.config['hg_app_name'], 'mercurial repository')
50 self.authenticate = AuthBasicAuthenticator(realm, authfunc) 51 self.authenticate = AuthBasicAuthenticator(realm, authfunc)
51 52
52 def __call__(self, environ, start_response): 53 def __call__(self, environ, start_response):
53 if not is_mercurial(environ): 54 if not is_mercurial(environ):
54 return self.application(environ, start_response) 55 return self.application(environ, start_response)
66 return result.wsgi_application(environ, start_response) 67 return result.wsgi_application(environ, start_response)
67 68
68 try: 69 try:
69 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:]) 70 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
70 except Exception as e: 71 except Exception as e:
71 log.error(e) 72 log.error(traceback.format_exc())
72 return HTTPNotFound()(environ, start_response) 73 return HTTPNotFound()(environ, start_response)
73 74
74 #since we wrap into hgweb, just reset the path 75 #===================================================================
75 environ['PATH_INFO'] = '/' 76 # CHECK PERMISSIONS FOR THIS REQUEST
77 #===================================================================
78 action = self.__get_action(environ)
79 if action:
80 username = self.__get_environ_user(environ)
81 try:
82 sa = meta.Session
83 user = sa.query(User)\
84 .filter(User.username == username).one()
85 except:
86 return HTTPNotFound()(environ, start_response)
87 #check permissions for this repository
88 if action == 'pull':
89 if not HasPermissionAnyMiddleware('repository.read',
90 'repository.write',
91 'repository.admin')\
92 (user, repo_name):
93 return HTTPForbidden()(environ, start_response)
94 if action == 'push':
95 if not HasPermissionAnyMiddleware('repository.write',
96 'repository.admin')\
97 (user, repo_name):
98 return HTTPForbidden()(environ, start_response)
99
100 #log action
101 self.__log_user_action(user, action, repo_name)
102
103 #===================================================================
104 # MERCURIAL REQUEST HANDLING
105 #===================================================================
106 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
76 self.baseui = make_ui(self.config['hg_app_repo_conf']) 107 self.baseui = make_ui(self.config['hg_app_repo_conf'])
77 self.basepath = self.baseui.configitems('paths')[0][1]\ 108 self.basepath = self.config['base_path']
78 .replace('*', '')
79 self.repo_path = os.path.join(self.basepath, repo_name) 109 self.repo_path = os.path.join(self.basepath, repo_name)
80 try: 110 try:
81 app = wsgiapplication(self.__make_app) 111 app = wsgiapplication(self.__make_app)
82 except Exception as e: 112 except Exception:
83 log.error(e) 113 log.error(traceback.format_exc())
84 return HTTPNotFound()(environ, start_response) 114 return HTTPNotFound()(environ, start_response)
85 action = self.__get_action(environ) 115
116
86 #invalidate cache on push 117 #invalidate cache on push
87 if action == 'push': 118 if action == 'push':
88 self.__invalidate_cache(repo_name) 119 self.__invalidate_cache(repo_name)
89 120
90 if action:
91 username = self.__get_environ_user(environ)
92 self.__log_user_action(username, action, repo_name)
93 messages = ['thanks for using hg app !'] 121 messages = ['thanks for using hg app !']
94 return self.msg_wrapper(app, environ, start_response, messages) 122 return self.msg_wrapper(app, environ, start_response, messages)
95 123
96 124
97 def msg_wrapper(self, app, environ, start_response, messages): 125 def msg_wrapper(self, app, environ, start_response, messages):
98 """ 126 """
99 Wrapper for custom messages that come out of mercurial respond messages 127 Wrapper for custom messages that come out of mercurial respond messages
100 is a list of messages that the user will see at the end of response from 128 is a list of messages that the user will see at the end of response
101 merurial protocol actions that involves remote answers 129 from merurial protocol actions that involves remote answers
102 @param app: 130 @param app:
103 @param environ: 131 @param environ:
104 @param start_response: 132 @param start_response:
105 """ 133 """
106 def custom_messages(msg_list): 134 def custom_messages(msg_list):
131 if qry.startswith('cmd'): 159 if qry.startswith('cmd'):
132 cmd = qry.split('=')[-1] 160 cmd = qry.split('=')[-1]
133 if mapping.has_key(cmd): 161 if mapping.has_key(cmd):
134 return mapping[cmd] 162 return mapping[cmd]
135 163
136 def __log_user_action(self, username, action, repo): 164 def __log_user_action(self, user, action, repo):
137 sa = meta.Session 165 sa = meta.Session
138 try: 166 try:
139 user = sa.query(User).filter(User.username == username).one()
140 user_log = UserLog() 167 user_log = UserLog()
141 user_log.user_id = user.user_id 168 user_log.user_id = user.user_id
142 user_log.action = action 169 user_log.action = action
143 user_log.repository = repo.replace('/', '') 170 user_log.repository = repo.replace('/', '')
144 user_log.action_date = datetime.now() 171 user_log.action_date = datetime.now()
145 sa.add(user_log) 172 sa.add(user_log)
146 sa.commit() 173 sa.commit()
147 log.info('Adding user %s, action %s on %s', 174 log.info('Adding user %s, action %s on %s',
148 username, action, repo) 175 user.username, action, repo)
149 except Exception as e: 176 except Exception as e:
150 sa.rollback() 177 sa.rollback()
151 log.error('could not log user action:%s', str(e)) 178 log.error('could not log user action:%s', str(e))
152 179
153 def __invalidate_cache(self, repo_name): 180 def __invalidate_cache(self, repo_name):