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)