##// END OF EJS Templates
better logging
marcink -
r2021:a04844d9 beta
parent child Browse files
Show More
@@ -1,249 +1,249 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 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
160 log.info('%s action on GIT repo "%s"' % (action, repo_name))
161 161 if action in ['pull', 'push']:
162 162 try:
163 163 user = self.__get_user(username)
164 164 if user is None or not user.active:
165 165 return HTTPForbidden()(environ, start_response)
166 166 username = user.username
167 167 except:
168 168 log.error(traceback.format_exc())
169 169 return HTTPInternalServerError()(environ,
170 170 start_response)
171 171
172 172 #check permissions for this repository
173 173 perm = self._check_permission(action, user,
174 174 repo_name)
175 175 if perm is not True:
176 176 return HTTPForbidden()(environ, start_response)
177 177
178 178 #===================================================================
179 179 # GIT REQUEST HANDLING
180 180 #===================================================================
181 181
182 182 repo_path = safe_str(os.path.join(self.basepath, repo_name))
183 183 log.debug('Repository path is %s' % repo_path)
184 184
185 185 # quick check if that dir exists...
186 186 if is_valid_repo(repo_name, self.basepath) is False:
187 187 return HTTPNotFound()(environ, start_response)
188 188
189 189 try:
190 190 #invalidate cache on push
191 191 if action == 'push':
192 192 self._invalidate_cache(repo_name)
193 193
194 194 app = self.__make_app(repo_name, repo_path)
195 195 return app(environ, start_response)
196 196 except Exception:
197 197 log.error(traceback.format_exc())
198 198 return HTTPInternalServerError()(environ, start_response)
199 199
200 200 def __make_app(self, repo_name, repo_path):
201 201 """
202 202 Make an wsgi application using dulserver
203 203
204 204 :param repo_name: name of the repository
205 205 :param repo_path: full path to the repository
206 206 """
207 207
208 208 _d = {'/' + repo_name: Repo(repo_path)}
209 209 backend = dulserver.DictBackend(_d)
210 210 gitserve = HTTPGitApplication(backend)
211 211
212 212 return gitserve
213 213
214 214 def __get_repository(self, environ):
215 215 """
216 216 Get's repository name out of PATH_INFO header
217 217
218 218 :param environ: environ where PATH_INFO is stored
219 219 """
220 220 try:
221 221 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
222 222 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
223 223 if repo_name.endswith('/'):
224 224 repo_name = repo_name.rstrip('/')
225 225 except:
226 226 log.error(traceback.format_exc())
227 227 raise
228 228 repo_name = repo_name.split('/')[0]
229 229 return repo_name
230 230
231 231 def __get_user(self, username):
232 232 return User.get_by_username(username)
233 233
234 234 def __get_action(self, environ):
235 235 """Maps git request commands into a pull or push command.
236 236
237 237 :param environ:
238 238 """
239 239 service = environ['QUERY_STRING'].split('=')
240 240 if len(service) > 1:
241 241 service_cmd = service[1]
242 242 mapping = {'git-receive-pack': 'push',
243 243 'git-upload-pack': 'pull',
244 244 }
245 245
246 246 return mapping.get(service_cmd,
247 247 service_cmd if service_cmd else 'other')
248 248 else:
249 249 return 'other'
@@ -1,249 +1,248 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
84 83 #======================================================================
85 84 # CHECK ANONYMOUS PERMISSION
86 85 #======================================================================
87 86 if action in ['pull', 'push']:
88 87 anonymous_user = self.__get_user('default')
89 88
90 89 username = anonymous_user.username
91 anonymous_perm = self._check_permission(action,anonymous_user,
90 anonymous_perm = self._check_permission(action, anonymous_user,
92 91 repo_name)
93 92
94 93 if anonymous_perm is not True or anonymous_user.active is False:
95 94 if anonymous_perm is not True:
96 95 log.debug('Not enough credentials to access this '
97 96 'repository as anonymous user')
98 97 if anonymous_user.active is False:
99 98 log.debug('Anonymous access is disabled, running '
100 99 'authentication')
101 100 #==============================================================
102 101 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
103 102 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
104 103 #==============================================================
105 104
106 105 # Attempting to retrieve username from the container
107 106 username = get_container_username(environ, self.config)
108 107
109 108 # If not authenticated by the container, running basic auth
110 109 if not username:
111 110 self.authenticate.realm = \
112 111 safe_str(self.config['rhodecode_realm'])
113 112 result = self.authenticate(environ)
114 113 if isinstance(result, str):
115 114 AUTH_TYPE.update(environ, 'basic')
116 115 REMOTE_USER.update(environ, result)
117 116 username = result
118 117 else:
119 118 return result.wsgi_application(environ, start_response)
120 119
121 120 #==============================================================
122 121 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
123 122 #==============================================================
124
123 log.info('%s action on HG repo "%s"' % (action, repo_name))
125 124 if action in ['pull', 'push']:
126 125 try:
127 126 user = self.__get_user(username)
128 127 if user is None or not user.active:
129 128 return HTTPForbidden()(environ, start_response)
130 129 username = user.username
131 130 except:
132 131 log.error(traceback.format_exc())
133 132 return HTTPInternalServerError()(environ,
134 133 start_response)
135 134
136 135 #check permissions for this repository
137 136 perm = self._check_permission(action, user,
138 137 repo_name)
139 138 if perm is not True:
140 139 return HTTPForbidden()(environ, start_response)
141 140
142 141 extras = {'ip': ipaddr,
143 142 'username': username,
144 143 'action': action,
145 144 'repository': repo_name}
146 145
147 146 #======================================================================
148 147 # MERCURIAL REQUEST HANDLING
149 148 #======================================================================
150 149
151 150 repo_path = safe_str(os.path.join(self.basepath, repo_name))
152 151 log.debug('Repository path is %s' % repo_path)
153 152
154 153 baseui = make_ui('db')
155 154 self.__inject_extras(repo_path, baseui, extras)
156 155
157 156 # quick check if that dir exists...
158 157 if is_valid_repo(repo_name, self.basepath) is False:
159 158 return HTTPNotFound()(environ, start_response)
160 159
161 160 try:
162 161 # invalidate cache on push
163 162 if action == 'push':
164 163 self._invalidate_cache(repo_name)
165 164
166 165 app = self.__make_app(repo_path, baseui, extras)
167 166 return app(environ, start_response)
168 167 except RepoError, e:
169 168 if str(e).find('not found') != -1:
170 169 return HTTPNotFound()(environ, start_response)
171 170 except Exception:
172 171 log.error(traceback.format_exc())
173 172 return HTTPInternalServerError()(environ, start_response)
174 173
175 174 def __make_app(self, repo_name, baseui, extras):
176 175 """
177 176 Make an wsgi application using hgweb, and inject generated baseui
178 177 instance, additionally inject some extras into ui object
179 178 """
180 179 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
181 180
182 181 def __get_repository(self, environ):
183 182 """
184 183 Get's repository name out of PATH_INFO header
185 184
186 185 :param environ: environ where PATH_INFO is stored
187 186 """
188 187 try:
189 188 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
190 189 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
191 190 if repo_name.endswith('/'):
192 191 repo_name = repo_name.rstrip('/')
193 192 except:
194 193 log.error(traceback.format_exc())
195 194 raise
196 195
197 196 return repo_name
198 197
199 198 def __get_user(self, username):
200 199 return User.get_by_username(username)
201 200
202 201 def __get_action(self, environ):
203 202 """
204 203 Maps mercurial request commands into a clone,pull or push command.
205 204 This should always return a valid command string
206 205
207 206 :param environ:
208 207 """
209 208 mapping = {'changegroup': 'pull',
210 209 'changegroupsubset': 'pull',
211 210 'stream_out': 'pull',
212 211 'listkeys': 'pull',
213 212 'unbundle': 'push',
214 213 'pushkey': 'push', }
215 214 for qry in environ['QUERY_STRING'].split('&'):
216 215 if qry.startswith('cmd'):
217 216 cmd = qry.split('=')[-1]
218 217 if cmd in mapping:
219 218 return mapping[cmd]
220 219 else:
221 220 return 'pull'
222 221
223 222 def __inject_extras(self, repo_path, baseui, extras={}):
224 223 """
225 224 Injects some extra params into baseui instance
226 225
227 226 also overwrites global settings with those takes from local hgrc file
228 227
229 228 :param baseui: baseui instance
230 229 :param extras: dict with extra params to put into baseui
231 230 """
232 231
233 232 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
234 233
235 234 # make our hgweb quiet so it doesn't print output
236 235 baseui.setconfig('ui', 'quiet', 'true')
237 236
238 237 #inject some additional parameters that will be available in ui
239 238 #for hooks
240 239 for k, v in extras.items():
241 240 baseui.setconfig('rhodecode_extras', k, v)
242 241
243 242 repoui = make_ui('file', hgrc, False)
244 243
245 244 if repoui:
246 245 #overwrite our ui instance with the section from hgrc file
247 246 for section in ui_sections:
248 247 for k, v in repoui.configitems(section):
249 248 baseui.setconfig(section, k, v)
General Comments 0
You need to be logged in to leave comments. Login now