##// END OF EJS Templates
Added HTTP_X_FORWARDED_FOR as another method of extracting IP for pull/push logs....
marcink -
r2184:79e4d6b9 beta
parent child Browse files
Show More
@@ -1,202 +1,213 b''
1 """The base Controller API
1 """The base Controller API
2
2
3 Provides the BaseController class for subclassing.
3 Provides the BaseController class for subclassing.
4 """
4 """
5 import logging
5 import logging
6 import time
6 import time
7 import traceback
7 import traceback
8
8
9 from paste.auth.basic import AuthBasicAuthenticator
9 from paste.auth.basic import AuthBasicAuthenticator
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 from paste.httpheaders import WWW_AUTHENTICATE
11 from paste.httpheaders import WWW_AUTHENTICATE
12
12
13 from pylons import config, tmpl_context as c, request, session, url
13 from pylons import config, tmpl_context as c, request, session, url
14 from pylons.controllers import WSGIController
14 from pylons.controllers import WSGIController
15 from pylons.controllers.util import redirect
15 from pylons.controllers.util import redirect
16 from pylons.templating import render_mako as render
16 from pylons.templating import render_mako as render
17
17
18 from rhodecode import __version__, BACKENDS
18 from rhodecode import __version__, BACKENDS
19
19
20 from rhodecode.lib.utils2 import str2bool, safe_unicode
20 from rhodecode.lib.utils2 import str2bool, safe_unicode
21 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
21 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
22 HasPermissionAnyMiddleware, CookieStoreWrapper
22 HasPermissionAnyMiddleware, CookieStoreWrapper
23 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
23 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
24 from rhodecode.model import meta
24 from rhodecode.model import meta
25
25
26 from rhodecode.model.db import Repository
26 from rhodecode.model.db import Repository
27 from rhodecode.model.notification import NotificationModel
27 from rhodecode.model.notification import NotificationModel
28 from rhodecode.model.scm import ScmModel
28 from rhodecode.model.scm import ScmModel
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32
33 class BasicAuth(AuthBasicAuthenticator):
33 class BasicAuth(AuthBasicAuthenticator):
34
34
35 def __init__(self, realm, authfunc, auth_http_code=None):
35 def __init__(self, realm, authfunc, auth_http_code=None):
36 self.realm = realm
36 self.realm = realm
37 self.authfunc = authfunc
37 self.authfunc = authfunc
38 self._rc_auth_http_code = auth_http_code
38 self._rc_auth_http_code = auth_http_code
39
39
40 def build_authentication(self):
40 def build_authentication(self):
41 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
41 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
42 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
42 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
43 # return 403 if alternative http return code is specified in
43 # return 403 if alternative http return code is specified in
44 # RhodeCode config
44 # RhodeCode config
45 return HTTPForbidden(headers=head)
45 return HTTPForbidden(headers=head)
46 return HTTPUnauthorized(headers=head)
46 return HTTPUnauthorized(headers=head)
47
47
48
48
49 class BaseVCSController(object):
49 class BaseVCSController(object):
50
50
51 def __init__(self, application, config):
51 def __init__(self, application, config):
52 self.application = application
52 self.application = application
53 self.config = config
53 self.config = config
54 # base path of repo locations
54 # base path of repo locations
55 self.basepath = self.config['base_path']
55 self.basepath = self.config['base_path']
56 #authenticate this mercurial request using authfunc
56 #authenticate this mercurial request using authfunc
57 self.authenticate = BasicAuth('', authfunc,
57 self.authenticate = BasicAuth('', authfunc,
58 config.get('auth_ret_code'))
58 config.get('auth_ret_code'))
59 self.ipaddr = '0.0.0.0'
59 self.ipaddr = '0.0.0.0'
60
60
61 def _handle_request(self, environ, start_response):
61 def _handle_request(self, environ, start_response):
62 raise NotImplementedError()
62 raise NotImplementedError()
63
63
64 def _get_by_id(self, repo_name):
64 def _get_by_id(self, repo_name):
65 """
65 """
66 Get's a special pattern _<ID> from clone url and tries to replace it
66 Get's a special pattern _<ID> from clone url and tries to replace it
67 with a repository_name for support of _<ID> non changable urls
67 with a repository_name for support of _<ID> non changable urls
68
68
69 :param repo_name:
69 :param repo_name:
70 """
70 """
71 try:
71 try:
72 data = repo_name.split('/')
72 data = repo_name.split('/')
73 if len(data) >= 2:
73 if len(data) >= 2:
74 by_id = data[1].split('_')
74 by_id = data[1].split('_')
75 if len(by_id) == 2 and by_id[1].isdigit():
75 if len(by_id) == 2 and by_id[1].isdigit():
76 _repo_name = Repository.get(by_id[1]).repo_name
76 _repo_name = Repository.get(by_id[1]).repo_name
77 data[1] = _repo_name
77 data[1] = _repo_name
78 except:
78 except:
79 log.debug('Failed to extract repo_name from id %s' % (
79 log.debug('Failed to extract repo_name from id %s' % (
80 traceback.format_exc()
80 traceback.format_exc()
81 )
81 )
82 )
82 )
83
83
84 return '/'.join(data)
84 return '/'.join(data)
85
85
86 def _invalidate_cache(self, repo_name):
86 def _invalidate_cache(self, repo_name):
87 """
87 """
88 Set's cache for this repository for invalidation on next access
88 Set's cache for this repository for invalidation on next access
89
89
90 :param repo_name: full repo name, also a cache key
90 :param repo_name: full repo name, also a cache key
91 """
91 """
92 invalidate_cache('get_repo_cached_%s' % repo_name)
92 invalidate_cache('get_repo_cached_%s' % repo_name)
93
93
94 def _check_permission(self, action, user, repo_name):
94 def _check_permission(self, action, user, repo_name):
95 """
95 """
96 Checks permissions using action (push/pull) user and repository
96 Checks permissions using action (push/pull) user and repository
97 name
97 name
98
98
99 :param action: push or pull action
99 :param action: push or pull action
100 :param user: user instance
100 :param user: user instance
101 :param repo_name: repository name
101 :param repo_name: repository name
102 """
102 """
103 if action == 'push':
103 if action == 'push':
104 if not HasPermissionAnyMiddleware('repository.write',
104 if not HasPermissionAnyMiddleware('repository.write',
105 'repository.admin')(user,
105 'repository.admin')(user,
106 repo_name):
106 repo_name):
107 return False
107 return False
108
108
109 else:
109 else:
110 #any other action need at least read permission
110 #any other action need at least read permission
111 if not HasPermissionAnyMiddleware('repository.read',
111 if not HasPermissionAnyMiddleware('repository.read',
112 'repository.write',
112 'repository.write',
113 'repository.admin')(user,
113 'repository.admin')(user,
114 repo_name):
114 repo_name):
115 return False
115 return False
116
116
117 return True
117 return True
118
118
119 def _get_ip_addr(self, environ):
120 proxy_key = 'HTTP_X_REAL_IP'
121 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
122 def_key = 'REMOTE_ADDR'
123
124 return environ.get(proxy_key2,
125 environ.get(proxy_key,
126 environ.get(def_key, '0.0.0.0')
127 )
128 )
129
119 def __call__(self, environ, start_response):
130 def __call__(self, environ, start_response):
120 start = time.time()
131 start = time.time()
121 try:
132 try:
122 return self._handle_request(environ, start_response)
133 return self._handle_request(environ, start_response)
123 finally:
134 finally:
124 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
135 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
125 log.debug('Request time: %.3fs' % (time.time() - start))
136 log.debug('Request time: %.3fs' % (time.time() - start))
126 meta.Session.remove()
137 meta.Session.remove()
127
138
128
139
129 class BaseController(WSGIController):
140 class BaseController(WSGIController):
130
141
131 def __before__(self):
142 def __before__(self):
132 c.rhodecode_version = __version__
143 c.rhodecode_version = __version__
133 c.rhodecode_instanceid = config.get('instance_id')
144 c.rhodecode_instanceid = config.get('instance_id')
134 c.rhodecode_name = config.get('rhodecode_title')
145 c.rhodecode_name = config.get('rhodecode_title')
135 c.use_gravatar = str2bool(config.get('use_gravatar'))
146 c.use_gravatar = str2bool(config.get('use_gravatar'))
136 c.ga_code = config.get('rhodecode_ga_code')
147 c.ga_code = config.get('rhodecode_ga_code')
137 c.repo_name = get_repo_slug(request)
148 c.repo_name = get_repo_slug(request)
138 c.backends = BACKENDS.keys()
149 c.backends = BACKENDS.keys()
139 c.unread_notifications = NotificationModel()\
150 c.unread_notifications = NotificationModel()\
140 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
151 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
141 self.cut_off_limit = int(config.get('cut_off_limit'))
152 self.cut_off_limit = int(config.get('cut_off_limit'))
142
153
143 self.sa = meta.Session
154 self.sa = meta.Session
144 self.scm_model = ScmModel(self.sa)
155 self.scm_model = ScmModel(self.sa)
145
156
146 def __call__(self, environ, start_response):
157 def __call__(self, environ, start_response):
147 """Invoke the Controller"""
158 """Invoke the Controller"""
148 # WSGIController.__call__ dispatches to the Controller method
159 # WSGIController.__call__ dispatches to the Controller method
149 # the request is routed to. This routing information is
160 # the request is routed to. This routing information is
150 # available in environ['pylons.routes_dict']
161 # available in environ['pylons.routes_dict']
151 start = time.time()
162 start = time.time()
152 try:
163 try:
153 # make sure that we update permissions each time we call controller
164 # make sure that we update permissions each time we call controller
154 api_key = request.GET.get('api_key')
165 api_key = request.GET.get('api_key')
155 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
166 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
156 user_id = cookie_store.get('user_id', None)
167 user_id = cookie_store.get('user_id', None)
157 username = get_container_username(environ, config)
168 username = get_container_username(environ, config)
158 auth_user = AuthUser(user_id, api_key, username)
169 auth_user = AuthUser(user_id, api_key, username)
159 request.user = auth_user
170 request.user = auth_user
160 self.rhodecode_user = c.rhodecode_user = auth_user
171 self.rhodecode_user = c.rhodecode_user = auth_user
161 if not self.rhodecode_user.is_authenticated and \
172 if not self.rhodecode_user.is_authenticated and \
162 self.rhodecode_user.user_id is not None:
173 self.rhodecode_user.user_id is not None:
163 self.rhodecode_user.set_authenticated(
174 self.rhodecode_user.set_authenticated(
164 cookie_store.get('is_authenticated')
175 cookie_store.get('is_authenticated')
165 )
176 )
166 log.info('User: %s accessed %s' % (
177 log.info('User: %s accessed %s' % (
167 auth_user, safe_unicode(environ.get('PATH_INFO')))
178 auth_user, safe_unicode(environ.get('PATH_INFO')))
168 )
179 )
169 return WSGIController.__call__(self, environ, start_response)
180 return WSGIController.__call__(self, environ, start_response)
170 finally:
181 finally:
171 log.info('Request to %s time: %.3fs' % (
182 log.info('Request to %s time: %.3fs' % (
172 safe_unicode(environ.get('PATH_INFO')), time.time() - start)
183 safe_unicode(environ.get('PATH_INFO')), time.time() - start)
173 )
184 )
174 meta.Session.remove()
185 meta.Session.remove()
175
186
176
187
177 class BaseRepoController(BaseController):
188 class BaseRepoController(BaseController):
178 """
189 """
179 Base class for controllers responsible for loading all needed data for
190 Base class for controllers responsible for loading all needed data for
180 repository loaded items are
191 repository loaded items are
181
192
182 c.rhodecode_repo: instance of scm repository
193 c.rhodecode_repo: instance of scm repository
183 c.rhodecode_db_repo: instance of db
194 c.rhodecode_db_repo: instance of db
184 c.repository_followers: number of followers
195 c.repository_followers: number of followers
185 c.repository_forks: number of forks
196 c.repository_forks: number of forks
186 """
197 """
187
198
188 def __before__(self):
199 def __before__(self):
189 super(BaseRepoController, self).__before__()
200 super(BaseRepoController, self).__before__()
190 if c.repo_name:
201 if c.repo_name:
191
202
192 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
203 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
193 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
204 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
194
205
195 if c.rhodecode_repo is None:
206 if c.rhodecode_repo is None:
196 log.error('%s this repository is present in database but it '
207 log.error('%s this repository is present in database but it '
197 'cannot be created as an scm instance', c.repo_name)
208 'cannot be created as an scm instance', c.repo_name)
198
209
199 redirect(url('home'))
210 redirect(url('home'))
200
211
201 c.repository_followers = self.scm_model.get_followers(c.repo_name)
212 c.repository_followers = self.scm_model.get_followers(c.repo_name)
202 c.repository_forks = self.scm_model.get_forks(c.repo_name)
213 c.repository_forks = self.scm_model.get_forks(c.repo_name)
@@ -1,251 +1,249 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplegit
3 rhodecode.lib.middleware.simplegit
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 It's implemented with basic auth function
7 It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import re
28 import re
29 import logging
29 import logging
30 import traceback
30 import traceback
31
31
32 from dulwich import server as dulserver
32 from dulwich import server as dulserver
33
33
34
34
35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
36
36
37 def handle(self):
37 def handle(self):
38 write = lambda x: self.proto.write_sideband(1, x)
38 write = lambda x: self.proto.write_sideband(1, x)
39
39
40 graph_walker = dulserver.ProtocolGraphWalker(self,
40 graph_walker = dulserver.ProtocolGraphWalker(self,
41 self.repo.object_store,
41 self.repo.object_store,
42 self.repo.get_peeled)
42 self.repo.get_peeled)
43 objects_iter = self.repo.fetch_objects(
43 objects_iter = self.repo.fetch_objects(
44 graph_walker.determine_wants, graph_walker, self.progress,
44 graph_walker.determine_wants, graph_walker, self.progress,
45 get_tagged=self.get_tagged)
45 get_tagged=self.get_tagged)
46
46
47 # Do they want any objects?
47 # Do they want any objects?
48 if objects_iter is None or len(objects_iter) == 0:
48 if objects_iter is None or len(objects_iter) == 0:
49 return
49 return
50
50
51 self.progress("counting objects: %d, done.\n" % len(objects_iter))
51 self.progress("counting objects: %d, done.\n" % len(objects_iter))
52 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
52 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
53 objects_iter, len(objects_iter))
53 objects_iter, len(objects_iter))
54 messages = []
54 messages = []
55 messages.append('thank you for using rhodecode')
55 messages.append('thank you for using rhodecode')
56
56
57 for msg in messages:
57 for msg in messages:
58 self.progress(msg + "\n")
58 self.progress(msg + "\n")
59 # we are done
59 # we are done
60 self.proto.write("0000")
60 self.proto.write("0000")
61
61
62 dulserver.DEFAULT_HANDLERS = {
62 dulserver.DEFAULT_HANDLERS = {
63 'git-upload-pack': SimpleGitUploadPackHandler,
63 'git-upload-pack': SimpleGitUploadPackHandler,
64 'git-receive-pack': dulserver.ReceivePackHandler,
64 'git-receive-pack': dulserver.ReceivePackHandler,
65 }
65 }
66
66
67 from dulwich.repo import Repo
67 from dulwich.repo import Repo
68 from dulwich.web import make_wsgi_chain
68 from dulwich.web import make_wsgi_chain
69
69
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71
71
72 from rhodecode.lib.utils2 import safe_str
72 from rhodecode.lib.utils2 import safe_str
73 from rhodecode.lib.base import BaseVCSController
73 from rhodecode.lib.base import BaseVCSController
74 from rhodecode.lib.auth import get_container_username
74 from rhodecode.lib.auth import get_container_username
75 from rhodecode.lib.utils import is_valid_repo
75 from rhodecode.lib.utils import is_valid_repo
76 from rhodecode.model.db import User
76 from rhodecode.model.db import User
77
77
78 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
78 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
79
79
80 log = logging.getLogger(__name__)
80 log = logging.getLogger(__name__)
81
81
82
82
83 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
83 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
84
84
85
85
86 def is_git(environ):
86 def is_git(environ):
87 path_info = environ['PATH_INFO']
87 path_info = environ['PATH_INFO']
88 isgit_path = GIT_PROTO_PAT.match(path_info)
88 isgit_path = GIT_PROTO_PAT.match(path_info)
89 log.debug('pathinfo: %s detected as GIT %s' % (
89 log.debug('pathinfo: %s detected as GIT %s' % (
90 path_info, isgit_path != None)
90 path_info, isgit_path != None)
91 )
91 )
92 return isgit_path
92 return isgit_path
93
93
94
94
95 class SimpleGit(BaseVCSController):
95 class SimpleGit(BaseVCSController):
96
96
97 def _handle_request(self, environ, start_response):
97 def _handle_request(self, environ, start_response):
98
98
99 if not is_git(environ):
99 if not is_git(environ):
100 return self.application(environ, start_response)
100 return self.application(environ, start_response)
101
101
102 proxy_key = 'HTTP_X_REAL_IP'
102 ipaddr = self._get_ip_addr(environ)
103 def_key = 'REMOTE_ADDR'
104 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
105 username = None
103 username = None
106 # skip passing error to error controller
104 # skip passing error to error controller
107 environ['pylons.status_code_redirect'] = True
105 environ['pylons.status_code_redirect'] = True
108
106
109 #======================================================================
107 #======================================================================
110 # EXTRACT REPOSITORY NAME FROM ENV
108 # EXTRACT REPOSITORY NAME FROM ENV
111 #======================================================================
109 #======================================================================
112 try:
110 try:
113 repo_name = self.__get_repository(environ)
111 repo_name = self.__get_repository(environ)
114 log.debug('Extracted repo name is %s' % repo_name)
112 log.debug('Extracted repo name is %s' % repo_name)
115 except:
113 except:
116 return HTTPInternalServerError()(environ, start_response)
114 return HTTPInternalServerError()(environ, start_response)
117
115
118 # quick check if that dir exists...
116 # quick check if that dir exists...
119 if is_valid_repo(repo_name, self.basepath) is False:
117 if is_valid_repo(repo_name, self.basepath) is False:
120 return HTTPNotFound()(environ, start_response)
118 return HTTPNotFound()(environ, start_response)
121
119
122 #======================================================================
120 #======================================================================
123 # GET ACTION PULL or PUSH
121 # GET ACTION PULL or PUSH
124 #======================================================================
122 #======================================================================
125 action = self.__get_action(environ)
123 action = self.__get_action(environ)
126
124
127 #======================================================================
125 #======================================================================
128 # CHECK ANONYMOUS PERMISSION
126 # CHECK ANONYMOUS PERMISSION
129 #======================================================================
127 #======================================================================
130 if action in ['pull', 'push']:
128 if action in ['pull', 'push']:
131 anonymous_user = self.__get_user('default')
129 anonymous_user = self.__get_user('default')
132 username = anonymous_user.username
130 username = anonymous_user.username
133 anonymous_perm = self._check_permission(action, anonymous_user,
131 anonymous_perm = self._check_permission(action, anonymous_user,
134 repo_name)
132 repo_name)
135
133
136 if anonymous_perm is not True or anonymous_user.active is False:
134 if anonymous_perm is not True or anonymous_user.active is False:
137 if anonymous_perm is not True:
135 if anonymous_perm is not True:
138 log.debug('Not enough credentials to access this '
136 log.debug('Not enough credentials to access this '
139 'repository as anonymous user')
137 'repository as anonymous user')
140 if anonymous_user.active is False:
138 if anonymous_user.active is False:
141 log.debug('Anonymous access is disabled, running '
139 log.debug('Anonymous access is disabled, running '
142 'authentication')
140 'authentication')
143 #==============================================================
141 #==============================================================
144 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
142 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
145 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
143 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
146 #==============================================================
144 #==============================================================
147
145
148 # Attempting to retrieve username from the container
146 # Attempting to retrieve username from the container
149 username = get_container_username(environ, self.config)
147 username = get_container_username(environ, self.config)
150
148
151 # If not authenticated by the container, running basic auth
149 # If not authenticated by the container, running basic auth
152 if not username:
150 if not username:
153 self.authenticate.realm = \
151 self.authenticate.realm = \
154 safe_str(self.config['rhodecode_realm'])
152 safe_str(self.config['rhodecode_realm'])
155 result = self.authenticate(environ)
153 result = self.authenticate(environ)
156 if isinstance(result, str):
154 if isinstance(result, str):
157 AUTH_TYPE.update(environ, 'basic')
155 AUTH_TYPE.update(environ, 'basic')
158 REMOTE_USER.update(environ, result)
156 REMOTE_USER.update(environ, result)
159 username = result
157 username = result
160 else:
158 else:
161 return result.wsgi_application(environ, start_response)
159 return result.wsgi_application(environ, start_response)
162
160
163 #==============================================================
161 #==============================================================
164 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
162 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
165 #==============================================================
163 #==============================================================
166 if action in ['pull', 'push']:
164 if action in ['pull', 'push']:
167 try:
165 try:
168 user = self.__get_user(username)
166 user = self.__get_user(username)
169 if user is None or not user.active:
167 if user is None or not user.active:
170 return HTTPForbidden()(environ, start_response)
168 return HTTPForbidden()(environ, start_response)
171 username = user.username
169 username = user.username
172 except:
170 except:
173 log.error(traceback.format_exc())
171 log.error(traceback.format_exc())
174 return HTTPInternalServerError()(environ,
172 return HTTPInternalServerError()(environ,
175 start_response)
173 start_response)
176
174
177 #check permissions for this repository
175 #check permissions for this repository
178 perm = self._check_permission(action, user, repo_name)
176 perm = self._check_permission(action, user, repo_name)
179 if perm is not True:
177 if perm is not True:
180 return HTTPForbidden()(environ, start_response)
178 return HTTPForbidden()(environ, start_response)
181
179
182 #===================================================================
180 #===================================================================
183 # GIT REQUEST HANDLING
181 # GIT REQUEST HANDLING
184 #===================================================================
182 #===================================================================
185 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
183 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
186 log.debug('Repository path is %s' % repo_path)
184 log.debug('Repository path is %s' % repo_path)
187
185
188 try:
186 try:
189 #invalidate cache on push
187 #invalidate cache on push
190 if action == 'push':
188 if action == 'push':
191 self._invalidate_cache(repo_name)
189 self._invalidate_cache(repo_name)
192 log.info('%s action on GIT repo "%s"' % (action, repo_name))
190 log.info('%s action on GIT repo "%s"' % (action, repo_name))
193 app = self.__make_app(repo_name, repo_path)
191 app = self.__make_app(repo_name, repo_path)
194 return app(environ, start_response)
192 return app(environ, start_response)
195 except Exception:
193 except Exception:
196 log.error(traceback.format_exc())
194 log.error(traceback.format_exc())
197 return HTTPInternalServerError()(environ, start_response)
195 return HTTPInternalServerError()(environ, start_response)
198
196
199 def __make_app(self, repo_name, repo_path):
197 def __make_app(self, repo_name, repo_path):
200 """
198 """
201 Make an wsgi application using dulserver
199 Make an wsgi application using dulserver
202
200
203 :param repo_name: name of the repository
201 :param repo_name: name of the repository
204 :param repo_path: full path to the repository
202 :param repo_path: full path to the repository
205 """
203 """
206 _d = {'/' + repo_name: Repo(repo_path)}
204 _d = {'/' + repo_name: Repo(repo_path)}
207 backend = dulserver.DictBackend(_d)
205 backend = dulserver.DictBackend(_d)
208 gitserve = make_wsgi_chain(backend)
206 gitserve = make_wsgi_chain(backend)
209
207
210 return gitserve
208 return gitserve
211
209
212 def __get_repository(self, environ):
210 def __get_repository(self, environ):
213 """
211 """
214 Get's repository name out of PATH_INFO header
212 Get's repository name out of PATH_INFO header
215
213
216 :param environ: environ where PATH_INFO is stored
214 :param environ: environ where PATH_INFO is stored
217 """
215 """
218 try:
216 try:
219 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
217 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
220 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
218 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
221 except:
219 except:
222 log.error(traceback.format_exc())
220 log.error(traceback.format_exc())
223 raise
221 raise
224
222
225 return repo_name
223 return repo_name
226
224
227 def __get_user(self, username):
225 def __get_user(self, username):
228 return User.get_by_username(username)
226 return User.get_by_username(username)
229
227
230 def __get_action(self, environ):
228 def __get_action(self, environ):
231 """
229 """
232 Maps git request commands into a pull or push command.
230 Maps git request commands into a pull or push command.
233
231
234 :param environ:
232 :param environ:
235 """
233 """
236 service = environ['QUERY_STRING'].split('=')
234 service = environ['QUERY_STRING'].split('=')
237
235
238 if len(service) > 1:
236 if len(service) > 1:
239 service_cmd = service[1]
237 service_cmd = service[1]
240 mapping = {
238 mapping = {
241 'git-receive-pack': 'push',
239 'git-receive-pack': 'push',
242 'git-upload-pack': 'pull',
240 'git-upload-pack': 'pull',
243 }
241 }
244 op = mapping[service_cmd]
242 op = mapping[service_cmd]
245 self._git_stored_op = op
243 self._git_stored_op = op
246 return op
244 return op
247 else:
245 else:
248 # try to fallback to stored variable as we don't know if the last
246 # try to fallback to stored variable as we don't know if the last
249 # operation is pull/push
247 # operation is pull/push
250 op = getattr(self, '_git_stored_op', 'pull')
248 op = getattr(self, '_git_stored_op', 'pull')
251 return op
249 return op
@@ -1,258 +1,256 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplehg
3 rhodecode.lib.middleware.simplehg
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleHG middleware for handling mercurial protocol request
6 SimpleHG middleware for handling mercurial protocol request
7 (push/clone etc.). It's implemented with basic auth function
7 (push/clone etc.). It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import urllib
30 import urllib
31
31
32 from mercurial.error import RepoError
32 from mercurial.error import RepoError
33 from mercurial.hgweb import hgweb_mod
33 from mercurial.hgweb import hgweb_mod
34
34
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36
36
37 from rhodecode.lib.utils2 import safe_str
37 from rhodecode.lib.utils2 import safe_str
38 from rhodecode.lib.base import BaseVCSController
38 from rhodecode.lib.base import BaseVCSController
39 from rhodecode.lib.auth import get_container_username
39 from rhodecode.lib.auth import get_container_username
40 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
40 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
41 from rhodecode.model.db import User
41 from rhodecode.model.db import User
42
42
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 def is_mercurial(environ):
48 def is_mercurial(environ):
49 """
49 """
50 Returns True if request's target is mercurial server - header
50 Returns True if request's target is mercurial server - header
51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
52 """
52 """
53 http_accept = environ.get('HTTP_ACCEPT')
53 http_accept = environ.get('HTTP_ACCEPT')
54 path_info = environ['PATH_INFO']
54 path_info = environ['PATH_INFO']
55 if http_accept and http_accept.startswith('application/mercurial'):
55 if http_accept and http_accept.startswith('application/mercurial'):
56 ishg_path = True
56 ishg_path = True
57 else:
57 else:
58 ishg_path = False
58 ishg_path = False
59
59
60 log.debug('pathinfo: %s detected as HG %s' % (
60 log.debug('pathinfo: %s detected as HG %s' % (
61 path_info, ishg_path)
61 path_info, ishg_path)
62 )
62 )
63 return ishg_path
63 return ishg_path
64
64
65
65
66 class SimpleHg(BaseVCSController):
66 class SimpleHg(BaseVCSController):
67
67
68 def _handle_request(self, environ, start_response):
68 def _handle_request(self, environ, start_response):
69 if not is_mercurial(environ):
69 if not is_mercurial(environ):
70 return self.application(environ, start_response)
70 return self.application(environ, start_response)
71
71
72 proxy_key = 'HTTP_X_REAL_IP'
72 ipaddr = self._get_ip_addr(environ)
73 def_key = 'REMOTE_ADDR'
74 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
75
73
76 # skip passing error to error controller
74 # skip passing error to error controller
77 environ['pylons.status_code_redirect'] = True
75 environ['pylons.status_code_redirect'] = True
78
76
79 #======================================================================
77 #======================================================================
80 # EXTRACT REPOSITORY NAME FROM ENV
78 # EXTRACT REPOSITORY NAME FROM ENV
81 #======================================================================
79 #======================================================================
82 try:
80 try:
83 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
81 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
84 log.debug('Extracted repo name is %s' % repo_name)
82 log.debug('Extracted repo name is %s' % repo_name)
85 except:
83 except:
86 return HTTPInternalServerError()(environ, start_response)
84 return HTTPInternalServerError()(environ, start_response)
87
85
88 # quick check if that dir exists...
86 # quick check if that dir exists...
89 if is_valid_repo(repo_name, self.basepath) is False:
87 if is_valid_repo(repo_name, self.basepath) is False:
90 return HTTPNotFound()(environ, start_response)
88 return HTTPNotFound()(environ, start_response)
91
89
92 #======================================================================
90 #======================================================================
93 # GET ACTION PULL or PUSH
91 # GET ACTION PULL or PUSH
94 #======================================================================
92 #======================================================================
95 action = self.__get_action(environ)
93 action = self.__get_action(environ)
96
94
97 #======================================================================
95 #======================================================================
98 # CHECK ANONYMOUS PERMISSION
96 # CHECK ANONYMOUS PERMISSION
99 #======================================================================
97 #======================================================================
100 if action in ['pull', 'push']:
98 if action in ['pull', 'push']:
101 anonymous_user = self.__get_user('default')
99 anonymous_user = self.__get_user('default')
102 username = anonymous_user.username
100 username = anonymous_user.username
103 anonymous_perm = self._check_permission(action, anonymous_user,
101 anonymous_perm = self._check_permission(action, anonymous_user,
104 repo_name)
102 repo_name)
105
103
106 if anonymous_perm is not True or anonymous_user.active is False:
104 if anonymous_perm is not True or anonymous_user.active is False:
107 if anonymous_perm is not True:
105 if anonymous_perm is not True:
108 log.debug('Not enough credentials to access this '
106 log.debug('Not enough credentials to access this '
109 'repository as anonymous user')
107 'repository as anonymous user')
110 if anonymous_user.active is False:
108 if anonymous_user.active is False:
111 log.debug('Anonymous access is disabled, running '
109 log.debug('Anonymous access is disabled, running '
112 'authentication')
110 'authentication')
113 #==============================================================
111 #==============================================================
114 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
112 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
115 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
113 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
116 #==============================================================
114 #==============================================================
117
115
118 # Attempting to retrieve username from the container
116 # Attempting to retrieve username from the container
119 username = get_container_username(environ, self.config)
117 username = get_container_username(environ, self.config)
120
118
121 # If not authenticated by the container, running basic auth
119 # If not authenticated by the container, running basic auth
122 if not username:
120 if not username:
123 self.authenticate.realm = \
121 self.authenticate.realm = \
124 safe_str(self.config['rhodecode_realm'])
122 safe_str(self.config['rhodecode_realm'])
125 result = self.authenticate(environ)
123 result = self.authenticate(environ)
126 if isinstance(result, str):
124 if isinstance(result, str):
127 AUTH_TYPE.update(environ, 'basic')
125 AUTH_TYPE.update(environ, 'basic')
128 REMOTE_USER.update(environ, result)
126 REMOTE_USER.update(environ, result)
129 username = result
127 username = result
130 else:
128 else:
131 return result.wsgi_application(environ, start_response)
129 return result.wsgi_application(environ, start_response)
132
130
133 #==============================================================
131 #==============================================================
134 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
132 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
135 #==============================================================
133 #==============================================================
136 if action in ['pull', 'push']:
134 if action in ['pull', 'push']:
137 try:
135 try:
138 user = self.__get_user(username)
136 user = self.__get_user(username)
139 if user is None or not user.active:
137 if user is None or not user.active:
140 return HTTPForbidden()(environ, start_response)
138 return HTTPForbidden()(environ, start_response)
141 username = user.username
139 username = user.username
142 except:
140 except:
143 log.error(traceback.format_exc())
141 log.error(traceback.format_exc())
144 return HTTPInternalServerError()(environ,
142 return HTTPInternalServerError()(environ,
145 start_response)
143 start_response)
146
144
147 #check permissions for this repository
145 #check permissions for this repository
148 perm = self._check_permission(action, user, repo_name)
146 perm = self._check_permission(action, user, repo_name)
149 if perm is not True:
147 if perm is not True:
150 return HTTPForbidden()(environ, start_response)
148 return HTTPForbidden()(environ, start_response)
151
149
152 # extras are injected into mercurial UI object and later available
150 # extras are injected into mercurial UI object and later available
153 # in hg hooks executed by rhodecode
151 # in hg hooks executed by rhodecode
154 extras = {
152 extras = {
155 'ip': ipaddr,
153 'ip': ipaddr,
156 'username': username,
154 'username': username,
157 'action': action,
155 'action': action,
158 'repository': repo_name
156 'repository': repo_name
159 }
157 }
160
158
161 #======================================================================
159 #======================================================================
162 # MERCURIAL REQUEST HANDLING
160 # MERCURIAL REQUEST HANDLING
163 #======================================================================
161 #======================================================================
164 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
162 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
165 log.debug('Repository path is %s' % repo_path)
163 log.debug('Repository path is %s' % repo_path)
166
164
167 baseui = make_ui('db')
165 baseui = make_ui('db')
168 self.__inject_extras(repo_path, baseui, extras)
166 self.__inject_extras(repo_path, baseui, extras)
169
167
170 try:
168 try:
171 # invalidate cache on push
169 # invalidate cache on push
172 if action == 'push':
170 if action == 'push':
173 self._invalidate_cache(repo_name)
171 self._invalidate_cache(repo_name)
174 log.info('%s action on HG repo "%s"' % (action, repo_name))
172 log.info('%s action on HG repo "%s"' % (action, repo_name))
175 app = self.__make_app(repo_path, baseui, extras)
173 app = self.__make_app(repo_path, baseui, extras)
176 return app(environ, start_response)
174 return app(environ, start_response)
177 except RepoError, e:
175 except RepoError, e:
178 if str(e).find('not found') != -1:
176 if str(e).find('not found') != -1:
179 return HTTPNotFound()(environ, start_response)
177 return HTTPNotFound()(environ, start_response)
180 except Exception:
178 except Exception:
181 log.error(traceback.format_exc())
179 log.error(traceback.format_exc())
182 return HTTPInternalServerError()(environ, start_response)
180 return HTTPInternalServerError()(environ, start_response)
183
181
184 def __make_app(self, repo_name, baseui, extras):
182 def __make_app(self, repo_name, baseui, extras):
185 """
183 """
186 Make an wsgi application using hgweb, and inject generated baseui
184 Make an wsgi application using hgweb, and inject generated baseui
187 instance, additionally inject some extras into ui object
185 instance, additionally inject some extras into ui object
188 """
186 """
189 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
187 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
190
188
191 def __get_repository(self, environ):
189 def __get_repository(self, environ):
192 """
190 """
193 Get's repository name out of PATH_INFO header
191 Get's repository name out of PATH_INFO header
194
192
195 :param environ: environ where PATH_INFO is stored
193 :param environ: environ where PATH_INFO is stored
196 """
194 """
197 try:
195 try:
198 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
196 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
199 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
197 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
200 if repo_name.endswith('/'):
198 if repo_name.endswith('/'):
201 repo_name = repo_name.rstrip('/')
199 repo_name = repo_name.rstrip('/')
202 except:
200 except:
203 log.error(traceback.format_exc())
201 log.error(traceback.format_exc())
204 raise
202 raise
205
203
206 return repo_name
204 return repo_name
207
205
208 def __get_user(self, username):
206 def __get_user(self, username):
209 return User.get_by_username(username)
207 return User.get_by_username(username)
210
208
211 def __get_action(self, environ):
209 def __get_action(self, environ):
212 """
210 """
213 Maps mercurial request commands into a clone,pull or push command.
211 Maps mercurial request commands into a clone,pull or push command.
214 This should always return a valid command string
212 This should always return a valid command string
215
213
216 :param environ:
214 :param environ:
217 """
215 """
218 mapping = {'changegroup': 'pull',
216 mapping = {'changegroup': 'pull',
219 'changegroupsubset': 'pull',
217 'changegroupsubset': 'pull',
220 'stream_out': 'pull',
218 'stream_out': 'pull',
221 'listkeys': 'pull',
219 'listkeys': 'pull',
222 'unbundle': 'push',
220 'unbundle': 'push',
223 'pushkey': 'push', }
221 'pushkey': 'push', }
224 for qry in environ['QUERY_STRING'].split('&'):
222 for qry in environ['QUERY_STRING'].split('&'):
225 if qry.startswith('cmd'):
223 if qry.startswith('cmd'):
226 cmd = qry.split('=')[-1]
224 cmd = qry.split('=')[-1]
227 if cmd in mapping:
225 if cmd in mapping:
228 return mapping[cmd]
226 return mapping[cmd]
229 else:
227 else:
230 return 'pull'
228 return 'pull'
231
229
232 def __inject_extras(self, repo_path, baseui, extras={}):
230 def __inject_extras(self, repo_path, baseui, extras={}):
233 """
231 """
234 Injects some extra params into baseui instance
232 Injects some extra params into baseui instance
235
233
236 also overwrites global settings with those takes from local hgrc file
234 also overwrites global settings with those takes from local hgrc file
237
235
238 :param baseui: baseui instance
236 :param baseui: baseui instance
239 :param extras: dict with extra params to put into baseui
237 :param extras: dict with extra params to put into baseui
240 """
238 """
241
239
242 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
240 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
243
241
244 # make our hgweb quiet so it doesn't print output
242 # make our hgweb quiet so it doesn't print output
245 baseui.setconfig('ui', 'quiet', 'true')
243 baseui.setconfig('ui', 'quiet', 'true')
246
244
247 #inject some additional parameters that will be available in ui
245 #inject some additional parameters that will be available in ui
248 #for hooks
246 #for hooks
249 for k, v in extras.items():
247 for k, v in extras.items():
250 baseui.setconfig('rhodecode_extras', k, v)
248 baseui.setconfig('rhodecode_extras', k, v)
251
249
252 repoui = make_ui('file', hgrc, False)
250 repoui = make_ui('file', hgrc, False)
253
251
254 if repoui:
252 if repoui:
255 #overwrite our ui instance with the section from hgrc file
253 #overwrite our ui instance with the section from hgrc file
256 for section in ui_sections:
254 for section in ui_sections:
257 for k, v in repoui.configitems(section):
255 for k, v in repoui.configitems(section):
258 baseui.setconfig(section, k, v)
256 baseui.setconfig(section, k, v)
General Comments 0
You need to be logged in to leave comments. Login now