##// END OF EJS Templates
improved logging in git/hg middlewares
marcink -
r2026:c6e288dc beta
parent child Browse files
Show More
@@ -1,249 +1,248 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplegit
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 7 It's implemented with basic auth function
8 8
9 9 :created_on: Apr 28, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import logging
29 29 import traceback
30 30
31 31 from dulwich import server as dulserver
32 32
33 33
34 34 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
35 35
36 36 def handle(self):
37 37 write = lambda x: self.proto.write_sideband(1, x)
38 38
39 39 graph_walker = dulserver.ProtocolGraphWalker(self,
40 40 self.repo.object_store,
41 41 self.repo.get_peeled)
42 42 objects_iter = self.repo.fetch_objects(
43 43 graph_walker.determine_wants, graph_walker, self.progress,
44 44 get_tagged=self.get_tagged)
45 45
46 46 # Do they want any objects?
47 47 if objects_iter is None or len(objects_iter) == 0:
48 48 return
49 49
50 50 self.progress("counting objects: %d, done.\n" % len(objects_iter))
51 51 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
52 52 objects_iter, len(objects_iter))
53 53 messages = []
54 54 messages.append('thank you for using rhodecode')
55 55
56 56 for msg in messages:
57 57 self.progress(msg + "\n")
58 58 # we are done
59 59 self.proto.write("0000")
60 60
61 61 dulserver.DEFAULT_HANDLERS = {
62 62 'git-upload-pack': SimpleGitUploadPackHandler,
63 63 'git-receive-pack': dulserver.ReceivePackHandler,
64 64 }
65 65
66 66 from dulwich.repo import Repo
67 67 from dulwich.web import HTTPGitApplication
68 68
69 69 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
70 70
71 71 from rhodecode.lib import safe_str
72 72 from rhodecode.lib.base import BaseVCSController
73 73 from rhodecode.lib.auth import get_container_username
74 74 from rhodecode.lib.utils import is_valid_repo
75 75 from rhodecode.model.db import User
76 76
77 77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
78 78
79 79 log = logging.getLogger(__name__)
80 80
81 81
82 82 def is_git(environ):
83 83 """Returns True if request's target is git server.
84 84 ``HTTP_USER_AGENT`` would then have git client version given.
85 85
86 86 :param environ:
87 87 """
88 88 http_user_agent = environ.get('HTTP_USER_AGENT')
89 89 if http_user_agent and http_user_agent.startswith('git'):
90 90 return True
91 91 return False
92 92
93 93
94 94 class SimpleGit(BaseVCSController):
95 95
96 96 def _handle_request(self, environ, start_response):
97 97 if not is_git(environ):
98 98 return self.application(environ, start_response)
99 99
100 100 proxy_key = 'HTTP_X_REAL_IP'
101 101 def_key = 'REMOTE_ADDR'
102 102 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
103 103 username = None
104 104 # skip passing error to error controller
105 105 environ['pylons.status_code_redirect'] = True
106 106
107 107 #======================================================================
108 108 # EXTRACT REPOSITORY NAME FROM ENV
109 109 #======================================================================
110 110 try:
111 111 repo_name = self.__get_repository(environ)
112 112 log.debug('Extracted repo name is %s' % repo_name)
113 113 except:
114 114 return HTTPInternalServerError()(environ, start_response)
115 115
116 116 #======================================================================
117 117 # GET ACTION PULL or PUSH
118 118 #======================================================================
119 119 action = self.__get_action(environ)
120 120
121 121 #======================================================================
122 122 # CHECK ANONYMOUS PERMISSION
123 123 #======================================================================
124 124 if action in ['pull', 'push']:
125 125 anonymous_user = self.__get_user('default')
126 126 username = anonymous_user.username
127 anonymous_perm = self._check_permission(action,anonymous_user,
127 anonymous_perm = self._check_permission(action, anonymous_user,
128 128 repo_name)
129 129
130 130 if anonymous_perm is not True or anonymous_user.active is False:
131 131 if anonymous_perm is not True:
132 132 log.debug('Not enough credentials to access this '
133 133 'repository as anonymous user')
134 134 if anonymous_user.active is False:
135 135 log.debug('Anonymous access is disabled, running '
136 136 'authentication')
137 137 #==============================================================
138 138 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
139 139 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
140 140 #==============================================================
141 141
142 142 # Attempting to retrieve username from the container
143 143 username = get_container_username(environ, self.config)
144 144
145 145 # If not authenticated by the container, running basic auth
146 146 if not username:
147 147 self.authenticate.realm = \
148 148 safe_str(self.config['rhodecode_realm'])
149 149 result = self.authenticate(environ)
150 150 if isinstance(result, str):
151 151 AUTH_TYPE.update(environ, 'basic')
152 152 REMOTE_USER.update(environ, result)
153 153 username = result
154 154 else:
155 155 return result.wsgi_application(environ, start_response)
156 156
157 157 #==============================================================
158 158 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
159 159 #==============================================================
160 log.info('%s action on GIT repo "%s"' % (action, repo_name))
161 160 if action in ['pull', 'push']:
162 161 try:
163 162 user = self.__get_user(username)
164 163 if user is None or not user.active:
165 164 return HTTPForbidden()(environ, start_response)
166 165 username = user.username
167 166 except:
168 167 log.error(traceback.format_exc())
169 168 return HTTPInternalServerError()(environ,
170 169 start_response)
171 170
172 171 #check permissions for this repository
173 172 perm = self._check_permission(action, user,
174 173 repo_name)
175 174 if perm is not True:
176 175 return HTTPForbidden()(environ, start_response)
177 176
178 177 #===================================================================
179 178 # GIT REQUEST HANDLING
180 179 #===================================================================
181 180
182 181 repo_path = safe_str(os.path.join(self.basepath, repo_name))
183 182 log.debug('Repository path is %s' % repo_path)
184 183
185 184 # quick check if that dir exists...
186 185 if is_valid_repo(repo_name, self.basepath) is False:
187 186 return HTTPNotFound()(environ, start_response)
188 187
189 188 try:
190 189 #invalidate cache on push
191 190 if action == 'push':
192 191 self._invalidate_cache(repo_name)
193
192 log.info('%s action on GIT repo "%s"' % (action, repo_name))
194 193 app = self.__make_app(repo_name, repo_path)
195 194 return app(environ, start_response)
196 195 except Exception:
197 196 log.error(traceback.format_exc())
198 197 return HTTPInternalServerError()(environ, start_response)
199 198
200 199 def __make_app(self, repo_name, repo_path):
201 200 """
202 201 Make an wsgi application using dulserver
203 202
204 203 :param repo_name: name of the repository
205 204 :param repo_path: full path to the repository
206 205 """
207 206
208 207 _d = {'/' + repo_name: Repo(repo_path)}
209 208 backend = dulserver.DictBackend(_d)
210 209 gitserve = HTTPGitApplication(backend)
211 210
212 211 return gitserve
213 212
214 213 def __get_repository(self, environ):
215 214 """
216 215 Get's repository name out of PATH_INFO header
217 216
218 217 :param environ: environ where PATH_INFO is stored
219 218 """
220 219 try:
221 220 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
222 221 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
223 222 if repo_name.endswith('/'):
224 223 repo_name = repo_name.rstrip('/')
225 224 except:
226 225 log.error(traceback.format_exc())
227 226 raise
228 227 repo_name = repo_name.split('/')[0]
229 228 return repo_name
230 229
231 230 def __get_user(self, username):
232 231 return User.get_by_username(username)
233 232
234 233 def __get_action(self, environ):
235 234 """Maps git request commands into a pull or push command.
236 235
237 236 :param environ:
238 237 """
239 238 service = environ['QUERY_STRING'].split('=')
240 239 if len(service) > 1:
241 240 service_cmd = service[1]
242 241 mapping = {'git-receive-pack': 'push',
243 242 'git-upload-pack': 'pull',
244 243 }
245 244
246 245 return mapping.get(service_cmd,
247 246 service_cmd if service_cmd else 'other')
248 247 else:
249 248 return 'other'
@@ -1,248 +1,247 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplehg
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleHG middleware for handling mercurial protocol request
7 7 (push/clone etc.). It's implemented with basic auth function
8 8
9 9 :created_on: Apr 28, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import logging
29 29 import traceback
30 30
31 31 from mercurial.error import RepoError
32 32 from mercurial.hgweb import hgweb_mod
33 33
34 34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 35
36 36 from rhodecode.lib import safe_str
37 37 from rhodecode.lib.base import BaseVCSController
38 38 from rhodecode.lib.auth import get_container_username
39 39 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
40 40 from rhodecode.model.db import User
41 41
42 42 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 def is_mercurial(environ):
48 48 """Returns True if request's target is mercurial server - header
49 49 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
50 50 """
51 51 http_accept = environ.get('HTTP_ACCEPT')
52 52 if http_accept and http_accept.startswith('application/mercurial'):
53 53 return True
54 54 return False
55 55
56 56
57 57 class SimpleHg(BaseVCSController):
58 58
59 59 def _handle_request(self, environ, start_response):
60 60 if not is_mercurial(environ):
61 61 return self.application(environ, start_response)
62 62
63 63 proxy_key = 'HTTP_X_REAL_IP'
64 64 def_key = 'REMOTE_ADDR'
65 65 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
66 66
67 67 # skip passing error to error controller
68 68 environ['pylons.status_code_redirect'] = True
69 69
70 70 #======================================================================
71 71 # EXTRACT REPOSITORY NAME FROM ENV
72 72 #======================================================================
73 73 try:
74 74 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
75 75 log.debug('Extracted repo name is %s' % repo_name)
76 76 except:
77 77 return HTTPInternalServerError()(environ, start_response)
78 78
79 79 #======================================================================
80 80 # GET ACTION PULL or PUSH
81 81 #======================================================================
82 82 action = self.__get_action(environ)
83 83 #======================================================================
84 84 # CHECK ANONYMOUS PERMISSION
85 85 #======================================================================
86 86 if action in ['pull', 'push']:
87 87 anonymous_user = self.__get_user('default')
88 88
89 89 username = anonymous_user.username
90 90 anonymous_perm = self._check_permission(action, anonymous_user,
91 91 repo_name)
92 92
93 93 if anonymous_perm is not True or anonymous_user.active is False:
94 94 if anonymous_perm is not True:
95 95 log.debug('Not enough credentials to access this '
96 96 'repository as anonymous user')
97 97 if anonymous_user.active is False:
98 98 log.debug('Anonymous access is disabled, running '
99 99 'authentication')
100 100 #==============================================================
101 101 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
102 102 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
103 103 #==============================================================
104 104
105 105 # Attempting to retrieve username from the container
106 106 username = get_container_username(environ, self.config)
107 107
108 108 # If not authenticated by the container, running basic auth
109 109 if not username:
110 110 self.authenticate.realm = \
111 111 safe_str(self.config['rhodecode_realm'])
112 112 result = self.authenticate(environ)
113 113 if isinstance(result, str):
114 114 AUTH_TYPE.update(environ, 'basic')
115 115 REMOTE_USER.update(environ, result)
116 116 username = result
117 117 else:
118 118 return result.wsgi_application(environ, start_response)
119 119
120 120 #==============================================================
121 121 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
122 122 #==============================================================
123 log.info('%s action on HG repo "%s"' % (action, repo_name))
124 123 if action in ['pull', 'push']:
125 124 try:
126 125 user = self.__get_user(username)
127 126 if user is None or not user.active:
128 127 return HTTPForbidden()(environ, start_response)
129 128 username = user.username
130 129 except:
131 130 log.error(traceback.format_exc())
132 131 return HTTPInternalServerError()(environ,
133 132 start_response)
134 133
135 134 #check permissions for this repository
136 135 perm = self._check_permission(action, user,
137 136 repo_name)
138 137 if perm is not True:
139 138 return HTTPForbidden()(environ, start_response)
140 139
141 140 extras = {'ip': ipaddr,
142 141 'username': username,
143 142 'action': action,
144 143 'repository': repo_name}
145 144
146 145 #======================================================================
147 146 # MERCURIAL REQUEST HANDLING
148 147 #======================================================================
149 148
150 149 repo_path = safe_str(os.path.join(self.basepath, repo_name))
151 150 log.debug('Repository path is %s' % repo_path)
152 151
153 152 baseui = make_ui('db')
154 153 self.__inject_extras(repo_path, baseui, extras)
155 154
156 155 # quick check if that dir exists...
157 156 if is_valid_repo(repo_name, self.basepath) is False:
158 157 return HTTPNotFound()(environ, start_response)
159 158
160 159 try:
161 160 # invalidate cache on push
162 161 if action == 'push':
163 162 self._invalidate_cache(repo_name)
164
163 log.info('%s action on HG repo "%s"' % (action, repo_name))
165 164 app = self.__make_app(repo_path, baseui, extras)
166 165 return app(environ, start_response)
167 166 except RepoError, e:
168 167 if str(e).find('not found') != -1:
169 168 return HTTPNotFound()(environ, start_response)
170 169 except Exception:
171 170 log.error(traceback.format_exc())
172 171 return HTTPInternalServerError()(environ, start_response)
173 172
174 173 def __make_app(self, repo_name, baseui, extras):
175 174 """
176 175 Make an wsgi application using hgweb, and inject generated baseui
177 176 instance, additionally inject some extras into ui object
178 177 """
179 178 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
180 179
181 180 def __get_repository(self, environ):
182 181 """
183 182 Get's repository name out of PATH_INFO header
184 183
185 184 :param environ: environ where PATH_INFO is stored
186 185 """
187 186 try:
188 187 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
189 188 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
190 189 if repo_name.endswith('/'):
191 190 repo_name = repo_name.rstrip('/')
192 191 except:
193 192 log.error(traceback.format_exc())
194 193 raise
195 194
196 195 return repo_name
197 196
198 197 def __get_user(self, username):
199 198 return User.get_by_username(username)
200 199
201 200 def __get_action(self, environ):
202 201 """
203 202 Maps mercurial request commands into a clone,pull or push command.
204 203 This should always return a valid command string
205 204
206 205 :param environ:
207 206 """
208 207 mapping = {'changegroup': 'pull',
209 208 'changegroupsubset': 'pull',
210 209 'stream_out': 'pull',
211 210 'listkeys': 'pull',
212 211 'unbundle': 'push',
213 212 'pushkey': 'push', }
214 213 for qry in environ['QUERY_STRING'].split('&'):
215 214 if qry.startswith('cmd'):
216 215 cmd = qry.split('=')[-1]
217 216 if cmd in mapping:
218 217 return mapping[cmd]
219 218 else:
220 219 return 'pull'
221 220
222 221 def __inject_extras(self, repo_path, baseui, extras={}):
223 222 """
224 223 Injects some extra params into baseui instance
225 224
226 225 also overwrites global settings with those takes from local hgrc file
227 226
228 227 :param baseui: baseui instance
229 228 :param extras: dict with extra params to put into baseui
230 229 """
231 230
232 231 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
233 232
234 233 # make our hgweb quiet so it doesn't print output
235 234 baseui.setconfig('ui', 'quiet', 'true')
236 235
237 236 #inject some additional parameters that will be available in ui
238 237 #for hooks
239 238 for k, v in extras.items():
240 239 baseui.setconfig('rhodecode_extras', k, v)
241 240
242 241 repoui = make_ui('file', hgrc, False)
243 242
244 243 if repoui:
245 244 #overwrite our ui instance with the section from hgrc file
246 245 for section in ui_sections:
247 246 for k, v in repoui.configitems(section):
248 247 baseui.setconfig(section, k, v)
General Comments 0
You need to be logged in to leave comments. Login now