Mercurial > kallithea
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: |