comparison pylons_app/lib/middleware/simplehg.py @ 343:6484963056cd

implemented cache for repeated queries in simplehg mercurial requests
author Marcin Kuzminski <marcin@python-works.com>
date Wed, 14 Jul 2010 12:31:11 +0200
parents 1ef52a70f3b7
children 664a5b8c551a
comparison
equal deleted inserted replaced
342:c71dc6ef36e6 343:6484963056cd
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # encoding: utf-8 2 # encoding: utf-8
3 # middleware to handle mercurial api calls 3 # middleware to handle mercurial api calls
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5
6 # This program is free software; you can redistribute it and/or 6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License 7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2 8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license. 9 # of the License or (at your opinion) any later version of the license.
10 # 10 #
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 itertools import chain
30 from mercurial.error import RepoError
30 from mercurial.hgweb import hgweb 31 from mercurial.hgweb import hgweb
31 from mercurial.hgweb.request import wsgiapplication 32 from mercurial.hgweb.request import wsgiapplication
32 from mercurial.error import RepoError
33 from paste.auth.basic import AuthBasicAuthenticator 33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE 34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from pylons_app.lib.auth import authfunc, HasPermissionAnyMiddleware 35 from pylons_app.lib.auth import authfunc, HasPermissionAnyMiddleware, \
36 get_user_cached
36 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache, \ 37 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache, \
37 check_repo_fast 38 check_repo_fast
38 from pylons_app.model import meta 39 from pylons_app.model import meta
39 from pylons_app.model.db import UserLog, User 40 from pylons_app.model.db import UserLog, User
40 import pylons_app.lib.helpers as h
41 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError 41 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
42 import logging 42 import logging
43 import os 43 import os
44 import pylons_app.lib.helpers as h
44 import traceback 45 import traceback
46
45 log = logging.getLogger(__name__) 47 log = logging.getLogger(__name__)
46 48
47 class SimpleHg(object): 49 class SimpleHg(object):
48 50
49 def __init__(self, application, config): 51 def __init__(self, application, config):
54 self.authenticate = AuthBasicAuthenticator(realm, authfunc) 56 self.authenticate = AuthBasicAuthenticator(realm, authfunc)
55 57
56 def __call__(self, environ, start_response): 58 def __call__(self, environ, start_response):
57 if not is_mercurial(environ): 59 if not is_mercurial(environ):
58 return self.application(environ, start_response) 60 return self.application(environ, start_response)
59 else: 61
60 #=================================================================== 62 #===================================================================
61 # AUTHENTICATE THIS MERCURIAL REQUEST 63 # AUTHENTICATE THIS MERCURIAL REQUEST
62 #=================================================================== 64 #===================================================================
63 username = REMOTE_USER(environ) 65 username = REMOTE_USER(environ)
64 if not username: 66 if not username:
65 result = self.authenticate(environ) 67 result = self.authenticate(environ)
66 if isinstance(result, str): 68 if isinstance(result, str):
67 AUTH_TYPE.update(environ, 'basic') 69 AUTH_TYPE.update(environ, 'basic')
68 REMOTE_USER.update(environ, result) 70 REMOTE_USER.update(environ, result)
69 else: 71 else:
70 return result.wsgi_application(environ, start_response) 72 return result.wsgi_application(environ, start_response)
71 73
74 try:
75 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
76 except:
77 log.error(traceback.format_exc())
78 return HTTPInternalServerError()(environ, start_response)
79
80 #===================================================================
81 # CHECK PERMISSIONS FOR THIS REQUEST
82 #===================================================================
83 action = self.__get_action(environ)
84 if action:
85 username = self.__get_environ_user(environ)
72 try: 86 try:
73 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:]) 87 user = self.__get_user(username)
74 except: 88 except:
75 log.error(traceback.format_exc()) 89 log.error(traceback.format_exc())
76 return HTTPInternalServerError()(environ, start_response) 90 return HTTPInternalServerError()(environ, start_response)
91 #check permissions for this repository
92 if action == 'pull':
93 if not HasPermissionAnyMiddleware('repository.read',
94 'repository.write',
95 'repository.admin')\
96 (user, repo_name):
97 return HTTPForbidden()(environ, start_response)
98 if action == 'push':
99 if not HasPermissionAnyMiddleware('repository.write',
100 'repository.admin')\
101 (user, repo_name):
102 return HTTPForbidden()(environ, start_response)
77 103
78 #=================================================================== 104 #log action
79 # CHECK PERMISSIONS FOR THIS REQUEST 105 proxy_key = 'HTTP_X_REAL_IP'
80 #=================================================================== 106 def_key = 'REMOTE_ADDR'
81 action = self.__get_action(environ) 107 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
82 if action: 108 self.__log_user_action(user, action, repo_name, ipaddr)
83 username = self.__get_environ_user(environ) 109
84 try: 110 #===================================================================
85 sa = meta.Session 111 # MERCURIAL REQUEST HANDLING
86 user = sa.query(User)\ 112 #===================================================================
87 .filter(User.username == username).one() 113 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
88 except: 114 self.baseui = make_ui('db')
89 log.error(traceback.format_exc()) 115 self.basepath = self.config['base_path']
90 return HTTPInternalServerError()(environ, start_response) 116 self.repo_path = os.path.join(self.basepath, repo_name)
91 #check permissions for this repository 117
92 if action == 'pull': 118 #quick check if that dir exists...
93 if not HasPermissionAnyMiddleware('repository.read', 119 if check_repo_fast(repo_name, self.basepath):
94 'repository.write', 120 return HTTPNotFound()(environ, start_response)
95 'repository.admin')\ 121 try:
96 (user, repo_name): 122 app = wsgiapplication(self.__make_app)
97 return HTTPForbidden()(environ, start_response) 123 except RepoError as e:
98 if action == 'push': 124 if str(e).find('not found') != -1:
99 if not HasPermissionAnyMiddleware('repository.write',
100 'repository.admin')\
101 (user, repo_name):
102 return HTTPForbidden()(environ, start_response)
103
104 #log action
105 proxy_key = 'HTTP_X_REAL_IP'
106 def_key = 'REMOTE_ADDR'
107 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
108 self.__log_user_action(user, action, repo_name, ipaddr)
109
110 #===================================================================
111 # MERCURIAL REQUEST HANDLING
112 #===================================================================
113 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
114 self.baseui = make_ui('db')
115 self.basepath = self.config['base_path']
116 self.repo_path = os.path.join(self.basepath, repo_name)
117
118 #quick check if that dir exists...
119 if check_repo_fast(repo_name, self.basepath):
120 return HTTPNotFound()(environ, start_response) 125 return HTTPNotFound()(environ, start_response)
121 try: 126 except Exception:
122 app = wsgiapplication(self.__make_app) 127 log.error(traceback.format_exc())
123 except RepoError as e: 128 return HTTPInternalServerError()(environ, start_response)
124 if str(e).find('not found') != -1: 129
125 return HTTPNotFound()(environ, start_response) 130 #invalidate cache on push
126 except Exception: 131 if action == 'push':
127 log.error(traceback.format_exc()) 132 self.__invalidate_cache(repo_name)
128 return HTTPInternalServerError()(environ, start_response) 133 messages = []
129 134 messages.append('thank you for using hg-app')
130 #invalidate cache on push 135
131 if action == 'push': 136 return self.msg_wrapper(app, environ, start_response, messages)
132 self.__invalidate_cache(repo_name) 137 else:
133 messages = [] 138 return app(environ, start_response)
134 messages.append('thank you for using hg-app')
135
136 return self.msg_wrapper(app, environ, start_response, messages)
137 else:
138 return app(environ, start_response)
139 139
140 140
141 def msg_wrapper(self, app, environ, start_response, messages=[]): 141 def msg_wrapper(self, app, environ, start_response, messages=[]):
142 """ 142 """
143 Wrapper for custom messages that come out of mercurial respond messages 143 Wrapper for custom messages that come out of mercurial respond messages
158 return self.__load_web_settings(hgserve) 158 return self.__load_web_settings(hgserve)
159 159
160 def __get_environ_user(self, environ): 160 def __get_environ_user(self, environ):
161 return environ.get('REMOTE_USER') 161 return environ.get('REMOTE_USER')
162 162
163 def __get_user(self, username):
164 return get_user_cached(username)
165
166
167
163 def __get_size(self, repo_path, content_size): 168 def __get_size(self, repo_path, content_size):
164 size = int(content_size) 169 size = int(content_size)
165 for path, dirs, files in os.walk(repo_path): 170 for path, dirs, files in os.walk(repo_path):
166 if path.find('.hg') == -1: 171 if path.find('.hg') == -1:
167 for f in files: 172 for f in files: