Mercurial > kallithea
comparison rhodecode/lib/middleware/simplegit.py @ 620:19a62a5490fe
added base simple git middleware, for future usage
author | Marcin Kuzminski <marcin@python-works.com> |
---|---|
date | Tue, 19 Oct 2010 00:55:05 +0200 |
parents | |
children | d5372213db98 |
comparison
equal
deleted
inserted
replaced
619:a1ec653f5f95 | 620:19a62a5490fe |
---|---|
1 #!/usr/bin/env python | |
2 # encoding: utf-8 | |
3 # middleware to handle mercurial api calls | |
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> | |
5 # | |
6 # This program is free software; you can redistribute it and/or | |
7 # modify it under the terms of the GNU General Public License | |
8 # as published by the Free Software Foundation; version 2 | |
9 # of the License or (at your opinion) any later version of the license. | |
10 # | |
11 # This program is distributed in the hope that it will be useful, | |
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 # GNU General Public License for more details. | |
15 # | |
16 # You should have received a copy of the GNU General Public License | |
17 # along with this program; if not, write to the Free Software | |
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |
19 # MA 02110-1301, USA. | |
20 from dulwich.repo import Repo | |
21 from dulwich.server import DictBackend | |
22 from dulwich.web import HTTPGitApplication | |
23 from itertools import chain | |
24 from paste.auth.basic import AuthBasicAuthenticator | |
25 from paste.httpheaders import REMOTE_USER, AUTH_TYPE | |
26 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware, \ | |
27 get_user_cached | |
28 from rhodecode.lib.utils import action_logger, is_git, invalidate_cache, \ | |
29 check_repo_fast | |
30 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError | |
31 import logging | |
32 import os | |
33 import traceback | |
34 """ | |
35 Created on 2010-04-28 | |
36 | |
37 @author: marcink | |
38 SimpleHG middleware for handling mercurial protocol request (push/clone etc.) | |
39 It's implemented with basic auth function | |
40 """ | |
41 | |
42 | |
43 | |
44 | |
45 log = logging.getLogger(__name__) | |
46 | |
47 class SimpleGit(object): | |
48 | |
49 def __init__(self, application, config): | |
50 self.application = application | |
51 self.config = config | |
52 #authenticate this mercurial request using | |
53 self.authenticate = AuthBasicAuthenticator('', authfunc) | |
54 | |
55 def __call__(self, environ, start_response): | |
56 if not is_git(environ): | |
57 return self.application(environ, start_response) | |
58 | |
59 #=================================================================== | |
60 # AUTHENTICATE THIS MERCURIAL REQUEST | |
61 #=================================================================== | |
62 username = REMOTE_USER(environ) | |
63 if not username: | |
64 self.authenticate.realm = self.config['rhodecode_realm'] | |
65 result = self.authenticate(environ) | |
66 if isinstance(result, str): | |
67 AUTH_TYPE.update(environ, 'basic') | |
68 REMOTE_USER.update(environ, result) | |
69 else: | |
70 return result.wsgi_application(environ, start_response) | |
71 | |
72 try: | |
73 self.repo_name = environ['PATH_INFO'].split('/')[1] | |
74 if self.repo_name.endswith('/'): | |
75 self.repo_name = self.repo_name.rstrip('/') | |
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) | |
86 try: | |
87 user = self.__get_user(username) | |
88 except: | |
89 log.error(traceback.format_exc()) | |
90 return HTTPInternalServerError()(environ, start_response) | |
91 | |
92 #check permissions for this repository | |
93 if action == 'push': | |
94 if not HasPermissionAnyMiddleware('repository.write', | |
95 'repository.admin')\ | |
96 (user, self.repo_name): | |
97 return HTTPForbidden()(environ, start_response) | |
98 | |
99 else: | |
100 #any other action need at least read permission | |
101 if not HasPermissionAnyMiddleware('repository.read', | |
102 'repository.write', | |
103 'repository.admin')\ | |
104 (user, self.repo_name): | |
105 return HTTPForbidden()(environ, start_response) | |
106 | |
107 #log action | |
108 if action in ('push', 'pull', 'clone'): | |
109 proxy_key = 'HTTP_X_REAL_IP' | |
110 def_key = 'REMOTE_ADDR' | |
111 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0')) | |
112 self.__log_user_action(user, action, self.repo_name, ipaddr) | |
113 | |
114 #=================================================================== | |
115 # GIT REQUEST HANDLING | |
116 #=================================================================== | |
117 self.basepath = self.config['base_path'] | |
118 self.repo_path = os.path.join(self.basepath, self.repo_name) | |
119 #quick check if that dir exists... | |
120 if check_repo_fast(self.repo_name, self.basepath): | |
121 return HTTPNotFound()(environ, start_response) | |
122 try: | |
123 app = self.__make_app() | |
124 except Exception: | |
125 log.error(traceback.format_exc()) | |
126 return HTTPInternalServerError()(environ, start_response) | |
127 | |
128 #invalidate cache on push | |
129 if action == 'push': | |
130 self.__invalidate_cache(self.repo_name) | |
131 messages = [] | |
132 messages.append('thank you for using rhodecode') | |
133 return app(environ, start_response) | |
134 #TODO: check other alternatives for msg wrapping | |
135 #return self.msg_wrapper(app, environ, start_response, messages) | |
136 else: | |
137 return app(environ, start_response) | |
138 | |
139 | |
140 def msg_wrapper(self, app, environ, start_response, messages=[]): | |
141 """ | |
142 Wrapper for custom messages that come out of mercurial respond messages | |
143 is a list of messages that the user will see at the end of response | |
144 from merurial protocol actions that involves remote answers | |
145 :param app: | |
146 :param environ: | |
147 :param start_response: | |
148 """ | |
149 def custom_messages(msg_list): | |
150 for msg in msg_list: | |
151 yield msg + '\n' | |
152 org_response = app(environ, start_response) | |
153 return chain(org_response, custom_messages(messages)) | |
154 | |
155 | |
156 def __make_app(self): | |
157 backend = DictBackend({'/' + self.repo_name: Repo(self.repo_path)}) | |
158 gitserve = HTTPGitApplication(backend) | |
159 | |
160 return gitserve | |
161 | |
162 def __get_environ_user(self, environ): | |
163 return environ.get('REMOTE_USER') | |
164 | |
165 def __get_user(self, username): | |
166 return get_user_cached(username) | |
167 | |
168 def __get_action(self, environ): | |
169 """ | |
170 Maps git request commands into a pull or push command. | |
171 :param environ: | |
172 """ | |
173 service = environ['QUERY_STRING'].split('=') | |
174 if len(service) > 1: | |
175 service_cmd = service[1] | |
176 mapping = {'git-receive-pack': 'pull', | |
177 'git-upload-pack': 'push', | |
178 } | |
179 | |
180 return mapping.get(service_cmd, service_cmd) | |
181 | |
182 def __log_user_action(self, user, action, repo, ipaddr): | |
183 action_logger(user, action, repo, ipaddr) | |
184 | |
185 def __invalidate_cache(self, repo_name): | |
186 """we know that some change was made to repositories and we should | |
187 invalidate the cache to see the changes right away but only for | |
188 push requests""" | |
189 invalidate_cache('cached_repo_list') | |
190 invalidate_cache('full_changelog', repo_name) |