##// END OF EJS Templates
vcs: handle excessive slashes in from of the repo name path, fixes #5522
marcink -
r3328:44e97172 default
parent child Browse files
Show More
@@ -1,157 +1,160 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 SimpleHG middleware for handling mercurial protocol request
22 SimpleHG middleware for handling mercurial protocol request
23 (push/clone etc.). It's implemented with basic auth function
23 (push/clone etc.). It's implemented with basic auth function
24 """
24 """
25
25
26 import logging
26 import logging
27 import urlparse
27 import urlparse
28 import urllib
28 import urllib
29
29
30 from rhodecode.lib import utils
30 from rhodecode.lib import utils
31 from rhodecode.lib.ext_json import json
31 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.middleware import simplevcs
32 from rhodecode.lib.middleware import simplevcs
33
33
34 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
35
35
36
36
37 class SimpleHg(simplevcs.SimpleVCS):
37 class SimpleHg(simplevcs.SimpleVCS):
38
38
39 SCM = 'hg'
39 SCM = 'hg'
40
40
41 def _get_repository_name(self, environ):
41 def _get_repository_name(self, environ):
42 """
42 """
43 Gets repository name out of PATH_INFO header
43 Gets repository name out of PATH_INFO header
44
44
45 :param environ: environ where PATH_INFO is stored
45 :param environ: environ where PATH_INFO is stored
46 """
46 """
47 return environ['PATH_INFO'].strip('/')
47 repo_name = environ['PATH_INFO']
48 if repo_name and repo_name.startswith('/'):
49 # remove only the first leading /
50 repo_name = repo_name[1:]
51 return repo_name.rstrip('/')
48
52
49 _ACTION_MAPPING = {
53 _ACTION_MAPPING = {
50 'changegroup': 'pull',
54 'changegroup': 'pull',
51 'changegroupsubset': 'pull',
55 'changegroupsubset': 'pull',
52 'getbundle': 'pull',
56 'getbundle': 'pull',
53 'stream_out': 'pull',
57 'stream_out': 'pull',
54 'listkeys': 'pull',
58 'listkeys': 'pull',
55 'between': 'pull',
59 'between': 'pull',
56 'branchmap': 'pull',
60 'branchmap': 'pull',
57 'branches': 'pull',
61 'branches': 'pull',
58 'clonebundles': 'pull',
62 'clonebundles': 'pull',
59 'capabilities': 'pull',
63 'capabilities': 'pull',
60 'debugwireargs': 'pull',
64 'debugwireargs': 'pull',
61 'heads': 'pull',
65 'heads': 'pull',
62 'lookup': 'pull',
66 'lookup': 'pull',
63 'hello': 'pull',
67 'hello': 'pull',
64 'known': 'pull',
68 'known': 'pull',
65
69
66 # largefiles
70 # largefiles
67 'putlfile': 'push',
71 'putlfile': 'push',
68 'getlfile': 'pull',
72 'getlfile': 'pull',
69 'statlfile': 'pull',
73 'statlfile': 'pull',
70 'lheads': 'pull',
74 'lheads': 'pull',
71
75
72 # evolve
76 # evolve
73 'evoext_obshashrange_v1': 'pull',
77 'evoext_obshashrange_v1': 'pull',
74 'evoext_obshash': 'pull',
78 'evoext_obshash': 'pull',
75 'evoext_obshash1': 'pull',
79 'evoext_obshash1': 'pull',
76
80
77 'unbundle': 'push',
81 'unbundle': 'push',
78 'pushkey': 'push',
82 'pushkey': 'push',
79 }
83 }
80
84
81 @classmethod
85 @classmethod
82 def _get_xarg_headers(cls, environ):
86 def _get_xarg_headers(cls, environ):
83 i = 1
87 i = 1
84 chunks = [] # gather chunks stored in multiple 'hgarg_N'
88 chunks = [] # gather chunks stored in multiple 'hgarg_N'
85 while True:
89 while True:
86 head = environ.get('HTTP_X_HGARG_{}'.format(i))
90 head = environ.get('HTTP_X_HGARG_{}'.format(i))
87 if not head:
91 if not head:
88 break
92 break
89 i += 1
93 i += 1
90 chunks.append(urllib.unquote_plus(head))
94 chunks.append(urllib.unquote_plus(head))
91 full_arg = ''.join(chunks)
95 full_arg = ''.join(chunks)
92 pref = 'cmds='
96 pref = 'cmds='
93 if full_arg.startswith(pref):
97 if full_arg.startswith(pref):
94 # strip the cmds= header defining our batch commands
98 # strip the cmds= header defining our batch commands
95 full_arg = full_arg[len(pref):]
99 full_arg = full_arg[len(pref):]
96 cmds = full_arg.split(';')
100 cmds = full_arg.split(';')
97 return cmds
101 return cmds
98
102
99 @classmethod
103 @classmethod
100 def _get_batch_cmd(cls, environ):
104 def _get_batch_cmd(cls, environ):
101 """
105 """
102 Handle batch command send commands. Those are ';' separated commands
106 Handle batch command send commands. Those are ';' separated commands
103 sent by batch command that server needs to execute. We need to extract
107 sent by batch command that server needs to execute. We need to extract
104 those, and map them to our ACTION_MAPPING to get all push/pull commands
108 those, and map them to our ACTION_MAPPING to get all push/pull commands
105 specified in the batch
109 specified in the batch
106 """
110 """
107 default = 'push'
111 default = 'push'
108 batch_cmds = []
112 batch_cmds = []
109 try:
113 try:
110 cmds = cls._get_xarg_headers(environ)
114 cmds = cls._get_xarg_headers(environ)
111 for pair in cmds:
115 for pair in cmds:
112 parts = pair.split(' ', 1)
116 parts = pair.split(' ', 1)
113 if len(parts) != 2:
117 if len(parts) != 2:
114 continue
118 continue
115 # entry should be in a format `key ARGS`
119 # entry should be in a format `key ARGS`
116 cmd, args = parts
120 cmd, args = parts
117 action = cls._ACTION_MAPPING.get(cmd, default)
121 action = cls._ACTION_MAPPING.get(cmd, default)
118 batch_cmds.append(action)
122 batch_cmds.append(action)
119 except Exception:
123 except Exception:
120 log.exception('Failed to extract batch commands operations')
124 log.exception('Failed to extract batch commands operations')
121
125
122 # in case we failed, (e.g malformed data) assume it's PUSH sub-command
126 # in case we failed, (e.g malformed data) assume it's PUSH sub-command
123 # for safety
127 # for safety
124 return batch_cmds or [default]
128 return batch_cmds or [default]
125
129
126 def _get_action(self, environ):
130 def _get_action(self, environ):
127 """
131 """
128 Maps mercurial request commands into a pull or push command.
132 Maps mercurial request commands into a pull or push command.
129 In case of unknown/unexpected data, it returns 'push' to be safe.
133 In case of unknown/unexpected data, it returns 'push' to be safe.
130
134
131 :param environ:
135 :param environ:
132 """
136 """
133 default = 'push'
137 default = 'push'
134 query = urlparse.parse_qs(environ['QUERY_STRING'],
138 query = urlparse.parse_qs(environ['QUERY_STRING'],
135 keep_blank_values=True)
139 keep_blank_values=True)
136
140
137 if 'cmd' in query:
141 if 'cmd' in query:
138 cmd = query['cmd'][0]
142 cmd = query['cmd'][0]
139 if cmd == 'batch':
143 if cmd == 'batch':
140 cmds = self._get_batch_cmd(environ)
144 cmds = self._get_batch_cmd(environ)
141 if 'push' in cmds:
145 if 'push' in cmds:
142 return 'push'
146 return 'push'
143 else:
147 else:
144 return 'pull'
148 return 'pull'
145 return self._ACTION_MAPPING.get(cmd, default)
149 return self._ACTION_MAPPING.get(cmd, default)
146
150
147 return default
151 return default
148
152
149 def _create_wsgi_app(self, repo_path, repo_name, config):
153 def _create_wsgi_app(self, repo_path, repo_name, config):
150 return self.scm_app.create_hg_wsgi_app(
154 return self.scm_app.create_hg_wsgi_app(repo_path, repo_name, config)
151 repo_path, repo_name, config)
152
155
153 def _create_config(self, extras, repo_name):
156 def _create_config(self, extras, repo_name):
154 config = utils.make_db_config(repo=repo_name)
157 config = utils.make_db_config(repo=repo_name)
155 config.set('rhodecode', 'RC_SCM_DATA', json.dumps(extras))
158 config.set('rhodecode', 'RC_SCM_DATA', json.dumps(extras))
156
159
157 return config.serialize()
160 return config.serialize()
@@ -1,673 +1,669 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2018 RhodeCode GmbH
3 # Copyright (C) 2014-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 It's implemented with basic auth function
23 It's implemented with basic auth function
24 """
24 """
25
25
26 import os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import importlib
29 import importlib
30 from functools import wraps
30 from functools import wraps
31 from StringIO import StringIO
31 from StringIO import StringIO
32 from lxml import etree
32 from lxml import etree
33
33
34 import time
34 import time
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36
36
37 from pyramid.httpexceptions import (
37 from pyramid.httpexceptions import (
38 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
38 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
39 from zope.cachedescriptors.property import Lazy as LazyProperty
39 from zope.cachedescriptors.property import Lazy as LazyProperty
40
40
41 import rhodecode
41 import rhodecode
42 from rhodecode.authentication.base import authenticate, VCS_TYPE, loadplugin
42 from rhodecode.authentication.base import authenticate, VCS_TYPE, loadplugin
43 from rhodecode.lib import rc_cache
43 from rhodecode.lib import rc_cache
44 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
44 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
45 from rhodecode.lib.base import (
45 from rhodecode.lib.base import (
46 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
46 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
47 from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
47 from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
48 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
48 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
49 from rhodecode.lib.middleware import appenlight
49 from rhodecode.lib.middleware import appenlight
50 from rhodecode.lib.middleware.utils import scm_app_http
50 from rhodecode.lib.middleware.utils import scm_app_http
51 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
51 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
52 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool, safe_unicode
52 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool, safe_unicode
53 from rhodecode.lib.vcs.conf import settings as vcs_settings
53 from rhodecode.lib.vcs.conf import settings as vcs_settings
54 from rhodecode.lib.vcs.backends import base
54 from rhodecode.lib.vcs.backends import base
55
55
56 from rhodecode.model import meta
56 from rhodecode.model import meta
57 from rhodecode.model.db import User, Repository, PullRequest
57 from rhodecode.model.db import User, Repository, PullRequest
58 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.scm import ScmModel
59 from rhodecode.model.pull_request import PullRequestModel
59 from rhodecode.model.pull_request import PullRequestModel
60 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
60 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 def extract_svn_txn_id(acl_repo_name, data):
65 def extract_svn_txn_id(acl_repo_name, data):
66 """
66 """
67 Helper method for extraction of svn txn_id from submitted XML data during
67 Helper method for extraction of svn txn_id from submitted XML data during
68 POST operations
68 POST operations
69 """
69 """
70 try:
70 try:
71 root = etree.fromstring(data)
71 root = etree.fromstring(data)
72 pat = re.compile(r'/txn/(?P<txn_id>.*)')
72 pat = re.compile(r'/txn/(?P<txn_id>.*)')
73 for el in root:
73 for el in root:
74 if el.tag == '{DAV:}source':
74 if el.tag == '{DAV:}source':
75 for sub_el in el:
75 for sub_el in el:
76 if sub_el.tag == '{DAV:}href':
76 if sub_el.tag == '{DAV:}href':
77 match = pat.search(sub_el.text)
77 match = pat.search(sub_el.text)
78 if match:
78 if match:
79 svn_tx_id = match.groupdict()['txn_id']
79 svn_tx_id = match.groupdict()['txn_id']
80 txn_id = rc_cache.utils.compute_key_from_params(
80 txn_id = rc_cache.utils.compute_key_from_params(
81 acl_repo_name, svn_tx_id)
81 acl_repo_name, svn_tx_id)
82 return txn_id
82 return txn_id
83 except Exception:
83 except Exception:
84 log.exception('Failed to extract txn_id')
84 log.exception('Failed to extract txn_id')
85
85
86
86
87 def initialize_generator(factory):
87 def initialize_generator(factory):
88 """
88 """
89 Initializes the returned generator by draining its first element.
89 Initializes the returned generator by draining its first element.
90
90
91 This can be used to give a generator an initializer, which is the code
91 This can be used to give a generator an initializer, which is the code
92 up to the first yield statement. This decorator enforces that the first
92 up to the first yield statement. This decorator enforces that the first
93 produced element has the value ``"__init__"`` to make its special
93 produced element has the value ``"__init__"`` to make its special
94 purpose very explicit in the using code.
94 purpose very explicit in the using code.
95 """
95 """
96
96
97 @wraps(factory)
97 @wraps(factory)
98 def wrapper(*args, **kwargs):
98 def wrapper(*args, **kwargs):
99 gen = factory(*args, **kwargs)
99 gen = factory(*args, **kwargs)
100 try:
100 try:
101 init = gen.next()
101 init = gen.next()
102 except StopIteration:
102 except StopIteration:
103 raise ValueError('Generator must yield at least one element.')
103 raise ValueError('Generator must yield at least one element.')
104 if init != "__init__":
104 if init != "__init__":
105 raise ValueError('First yielded element must be "__init__".')
105 raise ValueError('First yielded element must be "__init__".')
106 return gen
106 return gen
107 return wrapper
107 return wrapper
108
108
109
109
110 class SimpleVCS(object):
110 class SimpleVCS(object):
111 """Common functionality for SCM HTTP handlers."""
111 """Common functionality for SCM HTTP handlers."""
112
112
113 SCM = 'unknown'
113 SCM = 'unknown'
114
114
115 acl_repo_name = None
115 acl_repo_name = None
116 url_repo_name = None
116 url_repo_name = None
117 vcs_repo_name = None
117 vcs_repo_name = None
118 rc_extras = {}
118 rc_extras = {}
119
119
120 # We have to handle requests to shadow repositories different than requests
120 # We have to handle requests to shadow repositories different than requests
121 # to normal repositories. Therefore we have to distinguish them. To do this
121 # to normal repositories. Therefore we have to distinguish them. To do this
122 # we use this regex which will match only on URLs pointing to shadow
122 # we use this regex which will match only on URLs pointing to shadow
123 # repositories.
123 # repositories.
124 shadow_repo_re = re.compile(
124 shadow_repo_re = re.compile(
125 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
125 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
126 '(?P<target>{slug_pat})/' # target repo
126 '(?P<target>{slug_pat})/' # target repo
127 'pull-request/(?P<pr_id>\d+)/' # pull request
127 'pull-request/(?P<pr_id>\d+)/' # pull request
128 'repository$' # shadow repo
128 'repository$' # shadow repo
129 .format(slug_pat=SLUG_RE.pattern))
129 .format(slug_pat=SLUG_RE.pattern))
130
130
131 def __init__(self, config, registry):
131 def __init__(self, config, registry):
132 self.registry = registry
132 self.registry = registry
133 self.config = config
133 self.config = config
134 # re-populated by specialized middleware
134 # re-populated by specialized middleware
135 self.repo_vcs_config = base.Config()
135 self.repo_vcs_config = base.Config()
136 self.rhodecode_settings = SettingsModel().get_all_settings(cache=True)
136 self.rhodecode_settings = SettingsModel().get_all_settings(cache=True)
137
137
138 registry.rhodecode_settings = self.rhodecode_settings
138 registry.rhodecode_settings = self.rhodecode_settings
139 # authenticate this VCS request using authfunc
139 # authenticate this VCS request using authfunc
140 auth_ret_code_detection = \
140 auth_ret_code_detection = \
141 str2bool(self.config.get('auth_ret_code_detection', False))
141 str2bool(self.config.get('auth_ret_code_detection', False))
142 self.authenticate = BasicAuth(
142 self.authenticate = BasicAuth(
143 '', authenticate, registry, config.get('auth_ret_code'),
143 '', authenticate, registry, config.get('auth_ret_code'),
144 auth_ret_code_detection)
144 auth_ret_code_detection)
145 self.ip_addr = '0.0.0.0'
145 self.ip_addr = '0.0.0.0'
146
146
147 @LazyProperty
147 @LazyProperty
148 def global_vcs_config(self):
148 def global_vcs_config(self):
149 try:
149 try:
150 return VcsSettingsModel().get_ui_settings_as_config_obj()
150 return VcsSettingsModel().get_ui_settings_as_config_obj()
151 except Exception:
151 except Exception:
152 return base.Config()
152 return base.Config()
153
153
154 @property
154 @property
155 def base_path(self):
155 def base_path(self):
156 settings_path = self.repo_vcs_config.get(
156 settings_path = self.repo_vcs_config.get(*VcsSettingsModel.PATH_SETTING)
157 *VcsSettingsModel.PATH_SETTING)
158
157
159 if not settings_path:
158 if not settings_path:
160 settings_path = self.global_vcs_config.get(
159 settings_path = self.global_vcs_config.get(*VcsSettingsModel.PATH_SETTING)
161 *VcsSettingsModel.PATH_SETTING)
162
160
163 if not settings_path:
161 if not settings_path:
164 # try, maybe we passed in explicitly as config option
162 # try, maybe we passed in explicitly as config option
165 settings_path = self.config.get('base_path')
163 settings_path = self.config.get('base_path')
166
164
167 if not settings_path:
165 if not settings_path:
168 raise ValueError('FATAL: base_path is empty')
166 raise ValueError('FATAL: base_path is empty')
169 return settings_path
167 return settings_path
170
168
171 def set_repo_names(self, environ):
169 def set_repo_names(self, environ):
172 """
170 """
173 This will populate the attributes acl_repo_name, url_repo_name,
171 This will populate the attributes acl_repo_name, url_repo_name,
174 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
172 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
175 shadow) repositories all names are equal. In case of requests to a
173 shadow) repositories all names are equal. In case of requests to a
176 shadow repository the acl-name points to the target repo of the pull
174 shadow repository the acl-name points to the target repo of the pull
177 request and the vcs-name points to the shadow repo file system path.
175 request and the vcs-name points to the shadow repo file system path.
178 The url-name is always the URL used by the vcs client program.
176 The url-name is always the URL used by the vcs client program.
179
177
180 Example in case of a shadow repo:
178 Example in case of a shadow repo:
181 acl_repo_name = RepoGroup/MyRepo
179 acl_repo_name = RepoGroup/MyRepo
182 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
180 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
183 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
181 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
184 """
182 """
185 # First we set the repo name from URL for all attributes. This is the
183 # First we set the repo name from URL for all attributes. This is the
186 # default if handling normal (non shadow) repo requests.
184 # default if handling normal (non shadow) repo requests.
187 self.url_repo_name = self._get_repository_name(environ)
185 self.url_repo_name = self._get_repository_name(environ)
188 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
186 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
189 self.is_shadow_repo = False
187 self.is_shadow_repo = False
190
188
191 # Check if this is a request to a shadow repository.
189 # Check if this is a request to a shadow repository.
192 match = self.shadow_repo_re.match(self.url_repo_name)
190 match = self.shadow_repo_re.match(self.url_repo_name)
193 if match:
191 if match:
194 match_dict = match.groupdict()
192 match_dict = match.groupdict()
195
193
196 # Build acl repo name from regex match.
194 # Build acl repo name from regex match.
197 acl_repo_name = safe_unicode('{groups}{target}'.format(
195 acl_repo_name = safe_unicode('{groups}{target}'.format(
198 groups=match_dict['groups'] or '',
196 groups=match_dict['groups'] or '',
199 target=match_dict['target']))
197 target=match_dict['target']))
200
198
201 # Retrieve pull request instance by ID from regex match.
199 # Retrieve pull request instance by ID from regex match.
202 pull_request = PullRequest.get(match_dict['pr_id'])
200 pull_request = PullRequest.get(match_dict['pr_id'])
203
201
204 # Only proceed if we got a pull request and if acl repo name from
202 # Only proceed if we got a pull request and if acl repo name from
205 # URL equals the target repo name of the pull request.
203 # URL equals the target repo name of the pull request.
206 if pull_request and \
204 if pull_request and \
207 (acl_repo_name == pull_request.target_repo.repo_name):
205 (acl_repo_name == pull_request.target_repo.repo_name):
208 repo_id = pull_request.target_repo.repo_id
206 repo_id = pull_request.target_repo.repo_id
209 # Get file system path to shadow repository.
207 # Get file system path to shadow repository.
210 workspace_id = PullRequestModel()._workspace_id(pull_request)
208 workspace_id = PullRequestModel()._workspace_id(pull_request)
211 target_vcs = pull_request.target_repo.scm_instance()
209 target_vcs = pull_request.target_repo.scm_instance()
212 vcs_repo_name = target_vcs._get_shadow_repository_path(
210 vcs_repo_name = target_vcs._get_shadow_repository_path(
213 repo_id, workspace_id)
211 repo_id, workspace_id)
214
212
215 # Store names for later usage.
213 # Store names for later usage.
216 self.vcs_repo_name = vcs_repo_name
214 self.vcs_repo_name = vcs_repo_name
217 self.acl_repo_name = acl_repo_name
215 self.acl_repo_name = acl_repo_name
218 self.is_shadow_repo = True
216 self.is_shadow_repo = True
219
217
220 log.debug('Setting all VCS repository names: %s', {
218 log.debug('Setting all VCS repository names: %s', {
221 'acl_repo_name': self.acl_repo_name,
219 'acl_repo_name': self.acl_repo_name,
222 'url_repo_name': self.url_repo_name,
220 'url_repo_name': self.url_repo_name,
223 'vcs_repo_name': self.vcs_repo_name,
221 'vcs_repo_name': self.vcs_repo_name,
224 })
222 })
225
223
226 @property
224 @property
227 def scm_app(self):
225 def scm_app(self):
228 custom_implementation = self.config['vcs.scm_app_implementation']
226 custom_implementation = self.config['vcs.scm_app_implementation']
229 if custom_implementation == 'http':
227 if custom_implementation == 'http':
230 log.info('Using HTTP implementation of scm app.')
228 log.info('Using HTTP implementation of scm app.')
231 scm_app_impl = scm_app_http
229 scm_app_impl = scm_app_http
232 else:
230 else:
233 log.info('Using custom implementation of scm_app: "{}"'.format(
231 log.info('Using custom implementation of scm_app: "{}"'.format(
234 custom_implementation))
232 custom_implementation))
235 scm_app_impl = importlib.import_module(custom_implementation)
233 scm_app_impl = importlib.import_module(custom_implementation)
236 return scm_app_impl
234 return scm_app_impl
237
235
238 def _get_by_id(self, repo_name):
236 def _get_by_id(self, repo_name):
239 """
237 """
240 Gets a special pattern _<ID> from clone url and tries to replace it
238 Gets a special pattern _<ID> from clone url and tries to replace it
241 with a repository_name for support of _<ID> non changeable urls
239 with a repository_name for support of _<ID> non changeable urls
242 """
240 """
243
241
244 data = repo_name.split('/')
242 data = repo_name.split('/')
245 if len(data) >= 2:
243 if len(data) >= 2:
246 from rhodecode.model.repo import RepoModel
244 from rhodecode.model.repo import RepoModel
247 by_id_match = RepoModel().get_repo_by_id(repo_name)
245 by_id_match = RepoModel().get_repo_by_id(repo_name)
248 if by_id_match:
246 if by_id_match:
249 data[1] = by_id_match.repo_name
247 data[1] = by_id_match.repo_name
250
248
251 return safe_str('/'.join(data))
249 return safe_str('/'.join(data))
252
250
253 def _invalidate_cache(self, repo_name):
251 def _invalidate_cache(self, repo_name):
254 """
252 """
255 Set's cache for this repository for invalidation on next access
253 Set's cache for this repository for invalidation on next access
256
254
257 :param repo_name: full repo name, also a cache key
255 :param repo_name: full repo name, also a cache key
258 """
256 """
259 ScmModel().mark_for_invalidation(repo_name)
257 ScmModel().mark_for_invalidation(repo_name)
260
258
261 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
259 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
262 db_repo = Repository.get_by_repo_name(repo_name)
260 db_repo = Repository.get_by_repo_name(repo_name)
263 if not db_repo:
261 if not db_repo:
264 log.debug('Repository `%s` not found inside the database.',
262 log.debug('Repository `%s` not found inside the database.',
265 repo_name)
263 repo_name)
266 return False
264 return False
267
265
268 if db_repo.repo_type != scm_type:
266 if db_repo.repo_type != scm_type:
269 log.warning(
267 log.warning(
270 'Repository `%s` have incorrect scm_type, expected %s got %s',
268 'Repository `%s` have incorrect scm_type, expected %s got %s',
271 repo_name, db_repo.repo_type, scm_type)
269 repo_name, db_repo.repo_type, scm_type)
272 return False
270 return False
273
271
274 config = db_repo._config
272 config = db_repo._config
275 config.set('extensions', 'largefiles', '')
273 config.set('extensions', 'largefiles', '')
276 return is_valid_repo(
274 return is_valid_repo(
277 repo_name, base_path,
275 repo_name, base_path,
278 explicit_scm=scm_type, expect_scm=scm_type, config=config)
276 explicit_scm=scm_type, expect_scm=scm_type, config=config)
279
277
280 def valid_and_active_user(self, user):
278 def valid_and_active_user(self, user):
281 """
279 """
282 Checks if that user is not empty, and if it's actually object it checks
280 Checks if that user is not empty, and if it's actually object it checks
283 if he's active.
281 if he's active.
284
282
285 :param user: user object or None
283 :param user: user object or None
286 :return: boolean
284 :return: boolean
287 """
285 """
288 if user is None:
286 if user is None:
289 return False
287 return False
290
288
291 elif user.active:
289 elif user.active:
292 return True
290 return True
293
291
294 return False
292 return False
295
293
296 @property
294 @property
297 def is_shadow_repo_dir(self):
295 def is_shadow_repo_dir(self):
298 return os.path.isdir(self.vcs_repo_name)
296 return os.path.isdir(self.vcs_repo_name)
299
297
300 def _check_permission(self, action, user, auth_user, repo_name, ip_addr=None,
298 def _check_permission(self, action, user, auth_user, repo_name, ip_addr=None,
301 plugin_id='', plugin_cache_active=False, cache_ttl=0):
299 plugin_id='', plugin_cache_active=False, cache_ttl=0):
302 """
300 """
303 Checks permissions using action (push/pull) user and repository
301 Checks permissions using action (push/pull) user and repository
304 name. If plugin_cache and ttl is set it will use the plugin which
302 name. If plugin_cache and ttl is set it will use the plugin which
305 authenticated the user to store the cached permissions result for N
303 authenticated the user to store the cached permissions result for N
306 amount of seconds as in cache_ttl
304 amount of seconds as in cache_ttl
307
305
308 :param action: push or pull action
306 :param action: push or pull action
309 :param user: user instance
307 :param user: user instance
310 :param repo_name: repository name
308 :param repo_name: repository name
311 """
309 """
312
310
313 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
311 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
314 plugin_id, plugin_cache_active, cache_ttl)
312 plugin_id, plugin_cache_active, cache_ttl)
315
313
316 user_id = user.user_id
314 user_id = user.user_id
317 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
315 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
318 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
316 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
319
317
320 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
318 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
321 expiration_time=cache_ttl,
319 expiration_time=cache_ttl,
322 condition=plugin_cache_active)
320 condition=plugin_cache_active)
323 def compute_perm_vcs(
321 def compute_perm_vcs(
324 cache_name, plugin_id, action, user_id, repo_name, ip_addr):
322 cache_name, plugin_id, action, user_id, repo_name, ip_addr):
325
323
326 log.debug('auth: calculating permission access now...')
324 log.debug('auth: calculating permission access now...')
327 # check IP
325 # check IP
328 inherit = user.inherit_default_permissions
326 inherit = user.inherit_default_permissions
329 ip_allowed = AuthUser.check_ip_allowed(
327 ip_allowed = AuthUser.check_ip_allowed(
330 user_id, ip_addr, inherit_from_default=inherit)
328 user_id, ip_addr, inherit_from_default=inherit)
331 if ip_allowed:
329 if ip_allowed:
332 log.info('Access for IP:%s allowed', ip_addr)
330 log.info('Access for IP:%s allowed', ip_addr)
333 else:
331 else:
334 return False
332 return False
335
333
336 if action == 'push':
334 if action == 'push':
337 perms = ('repository.write', 'repository.admin')
335 perms = ('repository.write', 'repository.admin')
338 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
336 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
339 return False
337 return False
340
338
341 else:
339 else:
342 # any other action need at least read permission
340 # any other action need at least read permission
343 perms = (
341 perms = (
344 'repository.read', 'repository.write', 'repository.admin')
342 'repository.read', 'repository.write', 'repository.admin')
345 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
343 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
346 return False
344 return False
347
345
348 return True
346 return True
349
347
350 start = time.time()
348 start = time.time()
351 log.debug('Running plugin `%s` permissions check', plugin_id)
349 log.debug('Running plugin `%s` permissions check', plugin_id)
352
350
353 # for environ based auth, password can be empty, but then the validation is
351 # for environ based auth, password can be empty, but then the validation is
354 # on the server that fills in the env data needed for authentication
352 # on the server that fills in the env data needed for authentication
355 perm_result = compute_perm_vcs(
353 perm_result = compute_perm_vcs(
356 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr)
354 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr)
357
355
358 auth_time = time.time() - start
356 auth_time = time.time() - start
359 log.debug('Permissions for plugin `%s` completed in %.3fs, '
357 log.debug('Permissions for plugin `%s` completed in %.3fs, '
360 'expiration time of fetched cache %.1fs.',
358 'expiration time of fetched cache %.1fs.',
361 plugin_id, auth_time, cache_ttl)
359 plugin_id, auth_time, cache_ttl)
362
360
363 return perm_result
361 return perm_result
364
362
365 def _check_ssl(self, environ, start_response):
363 def _check_ssl(self, environ, start_response):
366 """
364 """
367 Checks the SSL check flag and returns False if SSL is not present
365 Checks the SSL check flag and returns False if SSL is not present
368 and required True otherwise
366 and required True otherwise
369 """
367 """
370 org_proto = environ['wsgi._org_proto']
368 org_proto = environ['wsgi._org_proto']
371 # check if we have SSL required ! if not it's a bad request !
369 # check if we have SSL required ! if not it's a bad request !
372 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
370 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
373 if require_ssl and org_proto == 'http':
371 if require_ssl and org_proto == 'http':
374 log.debug(
372 log.debug(
375 'Bad request: detected protocol is `%s` and '
373 'Bad request: detected protocol is `%s` and '
376 'SSL/HTTPS is required.', org_proto)
374 'SSL/HTTPS is required.', org_proto)
377 return False
375 return False
378 return True
376 return True
379
377
380 def _get_default_cache_ttl(self):
378 def _get_default_cache_ttl(self):
381 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
379 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
382 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
380 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
383 plugin_settings = plugin.get_settings()
381 plugin_settings = plugin.get_settings()
384 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
382 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
385 plugin_settings) or (False, 0)
383 plugin_settings) or (False, 0)
386 return plugin_cache_active, cache_ttl
384 return plugin_cache_active, cache_ttl
387
385
388 def __call__(self, environ, start_response):
386 def __call__(self, environ, start_response):
389 try:
387 try:
390 return self._handle_request(environ, start_response)
388 return self._handle_request(environ, start_response)
391 except Exception:
389 except Exception:
392 log.exception("Exception while handling request")
390 log.exception("Exception while handling request")
393 appenlight.track_exception(environ)
391 appenlight.track_exception(environ)
394 return HTTPInternalServerError()(environ, start_response)
392 return HTTPInternalServerError()(environ, start_response)
395 finally:
393 finally:
396 meta.Session.remove()
394 meta.Session.remove()
397
395
398 def _handle_request(self, environ, start_response):
396 def _handle_request(self, environ, start_response):
399
400 if not self._check_ssl(environ, start_response):
397 if not self._check_ssl(environ, start_response):
401 reason = ('SSL required, while RhodeCode was unable '
398 reason = ('SSL required, while RhodeCode was unable '
402 'to detect this as SSL request')
399 'to detect this as SSL request')
403 log.debug('User not allowed to proceed, %s', reason)
400 log.debug('User not allowed to proceed, %s', reason)
404 return HTTPNotAcceptable(reason)(environ, start_response)
401 return HTTPNotAcceptable(reason)(environ, start_response)
405
402
406 if not self.url_repo_name:
403 if not self.url_repo_name:
407 log.warning('Repository name is empty: %s', self.url_repo_name)
404 log.warning('Repository name is empty: %s', self.url_repo_name)
408 # failed to get repo name, we fail now
405 # failed to get repo name, we fail now
409 return HTTPNotFound()(environ, start_response)
406 return HTTPNotFound()(environ, start_response)
410 log.debug('Extracted repo name is %s', self.url_repo_name)
407 log.debug('Extracted repo name is %s', self.url_repo_name)
411
408
412 ip_addr = get_ip_addr(environ)
409 ip_addr = get_ip_addr(environ)
413 user_agent = get_user_agent(environ)
410 user_agent = get_user_agent(environ)
414 username = None
411 username = None
415
412
416 # skip passing error to error controller
413 # skip passing error to error controller
417 environ['pylons.status_code_redirect'] = True
414 environ['pylons.status_code_redirect'] = True
418
415
419 # ======================================================================
416 # ======================================================================
420 # GET ACTION PULL or PUSH
417 # GET ACTION PULL or PUSH
421 # ======================================================================
418 # ======================================================================
422 action = self._get_action(environ)
419 action = self._get_action(environ)
423
420
424 # ======================================================================
421 # ======================================================================
425 # Check if this is a request to a shadow repository of a pull request.
422 # Check if this is a request to a shadow repository of a pull request.
426 # In this case only pull action is allowed.
423 # In this case only pull action is allowed.
427 # ======================================================================
424 # ======================================================================
428 if self.is_shadow_repo and action != 'pull':
425 if self.is_shadow_repo and action != 'pull':
429 reason = 'Only pull action is allowed for shadow repositories.'
426 reason = 'Only pull action is allowed for shadow repositories.'
430 log.debug('User not allowed to proceed, %s', reason)
427 log.debug('User not allowed to proceed, %s', reason)
431 return HTTPNotAcceptable(reason)(environ, start_response)
428 return HTTPNotAcceptable(reason)(environ, start_response)
432
429
433 # Check if the shadow repo actually exists, in case someone refers
430 # Check if the shadow repo actually exists, in case someone refers
434 # to it, and it has been deleted because of successful merge.
431 # to it, and it has been deleted because of successful merge.
435 if self.is_shadow_repo and not self.is_shadow_repo_dir:
432 if self.is_shadow_repo and not self.is_shadow_repo_dir:
436 log.debug(
433 log.debug(
437 'Shadow repo detected, and shadow repo dir `%s` is missing',
434 'Shadow repo detected, and shadow repo dir `%s` is missing',
438 self.is_shadow_repo_dir)
435 self.is_shadow_repo_dir)
439 return HTTPNotFound()(environ, start_response)
436 return HTTPNotFound()(environ, start_response)
440
437
441 # ======================================================================
438 # ======================================================================
442 # CHECK ANONYMOUS PERMISSION
439 # CHECK ANONYMOUS PERMISSION
443 # ======================================================================
440 # ======================================================================
444 detect_force_push = False
441 detect_force_push = False
445 check_branch_perms = False
442 check_branch_perms = False
446 if action in ['pull', 'push']:
443 if action in ['pull', 'push']:
447 user_obj = anonymous_user = User.get_default_user()
444 user_obj = anonymous_user = User.get_default_user()
448 auth_user = user_obj.AuthUser()
445 auth_user = user_obj.AuthUser()
449 username = anonymous_user.username
446 username = anonymous_user.username
450 if anonymous_user.active:
447 if anonymous_user.active:
451 plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
448 plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
452 # ONLY check permissions if the user is activated
449 # ONLY check permissions if the user is activated
453 anonymous_perm = self._check_permission(
450 anonymous_perm = self._check_permission(
454 action, anonymous_user, auth_user, self.acl_repo_name, ip_addr,
451 action, anonymous_user, auth_user, self.acl_repo_name, ip_addr,
455 plugin_id='anonymous_access',
452 plugin_id='anonymous_access',
456 plugin_cache_active=plugin_cache_active,
453 plugin_cache_active=plugin_cache_active,
457 cache_ttl=cache_ttl,
454 cache_ttl=cache_ttl,
458 )
455 )
459 else:
456 else:
460 anonymous_perm = False
457 anonymous_perm = False
461
458
462 if not anonymous_user.active or not anonymous_perm:
459 if not anonymous_user.active or not anonymous_perm:
463 if not anonymous_user.active:
460 if not anonymous_user.active:
464 log.debug('Anonymous access is disabled, running '
461 log.debug('Anonymous access is disabled, running '
465 'authentication')
462 'authentication')
466
463
467 if not anonymous_perm:
464 if not anonymous_perm:
468 log.debug('Not enough credentials to access this '
465 log.debug('Not enough credentials to access this '
469 'repository as anonymous user')
466 'repository as anonymous user')
470
467
471 username = None
468 username = None
472 # ==============================================================
469 # ==============================================================
473 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
470 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
474 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
471 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
475 # ==============================================================
472 # ==============================================================
476
473
477 # try to auth based on environ, container auth methods
474 # try to auth based on environ, container auth methods
478 log.debug('Running PRE-AUTH for container based authentication')
475 log.debug('Running PRE-AUTH for container based authentication')
479 pre_auth = authenticate(
476 pre_auth = authenticate(
480 '', '', environ, VCS_TYPE, registry=self.registry,
477 '', '', environ, VCS_TYPE, registry=self.registry,
481 acl_repo_name=self.acl_repo_name)
478 acl_repo_name=self.acl_repo_name)
482 if pre_auth and pre_auth.get('username'):
479 if pre_auth and pre_auth.get('username'):
483 username = pre_auth['username']
480 username = pre_auth['username']
484 log.debug('PRE-AUTH got %s as username', username)
481 log.debug('PRE-AUTH got %s as username', username)
485 if pre_auth:
482 if pre_auth:
486 log.debug('PRE-AUTH successful from %s',
483 log.debug('PRE-AUTH successful from %s',
487 pre_auth.get('auth_data', {}).get('_plugin'))
484 pre_auth.get('auth_data', {}).get('_plugin'))
488
485
489 # If not authenticated by the container, running basic auth
486 # If not authenticated by the container, running basic auth
490 # before inject the calling repo_name for special scope checks
487 # before inject the calling repo_name for special scope checks
491 self.authenticate.acl_repo_name = self.acl_repo_name
488 self.authenticate.acl_repo_name = self.acl_repo_name
492
489
493 plugin_cache_active, cache_ttl = False, 0
490 plugin_cache_active, cache_ttl = False, 0
494 plugin = None
491 plugin = None
495 if not username:
492 if not username:
496 self.authenticate.realm = self.authenticate.get_rc_realm()
493 self.authenticate.realm = self.authenticate.get_rc_realm()
497
494
498 try:
495 try:
499 auth_result = self.authenticate(environ)
496 auth_result = self.authenticate(environ)
500 except (UserCreationError, NotAllowedToCreateUserError) as e:
497 except (UserCreationError, NotAllowedToCreateUserError) as e:
501 log.error(e)
498 log.error(e)
502 reason = safe_str(e)
499 reason = safe_str(e)
503 return HTTPNotAcceptable(reason)(environ, start_response)
500 return HTTPNotAcceptable(reason)(environ, start_response)
504
501
505 if isinstance(auth_result, dict):
502 if isinstance(auth_result, dict):
506 AUTH_TYPE.update(environ, 'basic')
503 AUTH_TYPE.update(environ, 'basic')
507 REMOTE_USER.update(environ, auth_result['username'])
504 REMOTE_USER.update(environ, auth_result['username'])
508 username = auth_result['username']
505 username = auth_result['username']
509 plugin = auth_result.get('auth_data', {}).get('_plugin')
506 plugin = auth_result.get('auth_data', {}).get('_plugin')
510 log.info(
507 log.info(
511 'MAIN-AUTH successful for user `%s` from %s plugin',
508 'MAIN-AUTH successful for user `%s` from %s plugin',
512 username, plugin)
509 username, plugin)
513
510
514 plugin_cache_active, cache_ttl = auth_result.get(
511 plugin_cache_active, cache_ttl = auth_result.get(
515 'auth_data', {}).get('_ttl_cache') or (False, 0)
512 'auth_data', {}).get('_ttl_cache') or (False, 0)
516 else:
513 else:
517 return auth_result.wsgi_application(
514 return auth_result.wsgi_application(environ, start_response)
518 environ, start_response)
519
515
520 # ==============================================================
516 # ==============================================================
521 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
517 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
522 # ==============================================================
518 # ==============================================================
523 user = User.get_by_username(username)
519 user = User.get_by_username(username)
524 if not self.valid_and_active_user(user):
520 if not self.valid_and_active_user(user):
525 return HTTPForbidden()(environ, start_response)
521 return HTTPForbidden()(environ, start_response)
526 username = user.username
522 username = user.username
527 user_id = user.user_id
523 user_id = user.user_id
528
524
529 # check user attributes for password change flag
525 # check user attributes for password change flag
530 user_obj = user
526 user_obj = user
531 auth_user = user_obj.AuthUser()
527 auth_user = user_obj.AuthUser()
532 if user_obj and user_obj.username != User.DEFAULT_USER and \
528 if user_obj and user_obj.username != User.DEFAULT_USER and \
533 user_obj.user_data.get('force_password_change'):
529 user_obj.user_data.get('force_password_change'):
534 reason = 'password change required'
530 reason = 'password change required'
535 log.debug('User not allowed to authenticate, %s', reason)
531 log.debug('User not allowed to authenticate, %s', reason)
536 return HTTPNotAcceptable(reason)(environ, start_response)
532 return HTTPNotAcceptable(reason)(environ, start_response)
537
533
538 # check permissions for this repository
534 # check permissions for this repository
539 perm = self._check_permission(
535 perm = self._check_permission(
540 action, user, auth_user, self.acl_repo_name, ip_addr,
536 action, user, auth_user, self.acl_repo_name, ip_addr,
541 plugin, plugin_cache_active, cache_ttl)
537 plugin, plugin_cache_active, cache_ttl)
542 if not perm:
538 if not perm:
543 return HTTPForbidden()(environ, start_response)
539 return HTTPForbidden()(environ, start_response)
544 environ['rc_auth_user_id'] = user_id
540 environ['rc_auth_user_id'] = user_id
545
541
546 if action == 'push':
542 if action == 'push':
547 perms = auth_user.get_branch_permissions(self.acl_repo_name)
543 perms = auth_user.get_branch_permissions(self.acl_repo_name)
548 if perms:
544 if perms:
549 check_branch_perms = True
545 check_branch_perms = True
550 detect_force_push = True
546 detect_force_push = True
551
547
552 # extras are injected into UI object and later available
548 # extras are injected into UI object and later available
553 # in hooks executed by RhodeCode
549 # in hooks executed by RhodeCode
554 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
550 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
555
551
556 extras = vcs_operation_context(
552 extras = vcs_operation_context(
557 environ, repo_name=self.acl_repo_name, username=username,
553 environ, repo_name=self.acl_repo_name, username=username,
558 action=action, scm=self.SCM, check_locking=check_locking,
554 action=action, scm=self.SCM, check_locking=check_locking,
559 is_shadow_repo=self.is_shadow_repo, check_branch_perms=check_branch_perms,
555 is_shadow_repo=self.is_shadow_repo, check_branch_perms=check_branch_perms,
560 detect_force_push=detect_force_push
556 detect_force_push=detect_force_push
561 )
557 )
562
558
563 # ======================================================================
559 # ======================================================================
564 # REQUEST HANDLING
560 # REQUEST HANDLING
565 # ======================================================================
561 # ======================================================================
566 repo_path = os.path.join(
562 repo_path = os.path.join(
567 safe_str(self.base_path), safe_str(self.vcs_repo_name))
563 safe_str(self.base_path), safe_str(self.vcs_repo_name))
568 log.debug('Repository path is %s', repo_path)
564 log.debug('Repository path is %s', repo_path)
569
565
570 fix_PATH()
566 fix_PATH()
571
567
572 log.info(
568 log.info(
573 '%s action on %s repo "%s" by "%s" from %s %s',
569 '%s action on %s repo "%s" by "%s" from %s %s',
574 action, self.SCM, safe_str(self.url_repo_name),
570 action, self.SCM, safe_str(self.url_repo_name),
575 safe_str(username), ip_addr, user_agent)
571 safe_str(username), ip_addr, user_agent)
576
572
577 return self._generate_vcs_response(
573 return self._generate_vcs_response(
578 environ, start_response, repo_path, extras, action)
574 environ, start_response, repo_path, extras, action)
579
575
580 @initialize_generator
576 @initialize_generator
581 def _generate_vcs_response(
577 def _generate_vcs_response(
582 self, environ, start_response, repo_path, extras, action):
578 self, environ, start_response, repo_path, extras, action):
583 """
579 """
584 Returns a generator for the response content.
580 Returns a generator for the response content.
585
581
586 This method is implemented as a generator, so that it can trigger
582 This method is implemented as a generator, so that it can trigger
587 the cache validation after all content sent back to the client. It
583 the cache validation after all content sent back to the client. It
588 also handles the locking exceptions which will be triggered when
584 also handles the locking exceptions which will be triggered when
589 the first chunk is produced by the underlying WSGI application.
585 the first chunk is produced by the underlying WSGI application.
590 """
586 """
591 txn_id = ''
587 txn_id = ''
592 if 'CONTENT_LENGTH' in environ and environ['REQUEST_METHOD'] == 'MERGE':
588 if 'CONTENT_LENGTH' in environ and environ['REQUEST_METHOD'] == 'MERGE':
593 # case for SVN, we want to re-use the callback daemon port
589 # case for SVN, we want to re-use the callback daemon port
594 # so we use the txn_id, for this we peek the body, and still save
590 # so we use the txn_id, for this we peek the body, and still save
595 # it as wsgi.input
591 # it as wsgi.input
596 data = environ['wsgi.input'].read()
592 data = environ['wsgi.input'].read()
597 environ['wsgi.input'] = StringIO(data)
593 environ['wsgi.input'] = StringIO(data)
598 txn_id = extract_svn_txn_id(self.acl_repo_name, data)
594 txn_id = extract_svn_txn_id(self.acl_repo_name, data)
599
595
600 callback_daemon, extras = self._prepare_callback_daemon(
596 callback_daemon, extras = self._prepare_callback_daemon(
601 extras, environ, action, txn_id=txn_id)
597 extras, environ, action, txn_id=txn_id)
602 log.debug('HOOKS extras is %s', extras)
598 log.debug('HOOKS extras is %s', extras)
603
599
604 config = self._create_config(extras, self.acl_repo_name)
600 config = self._create_config(extras, self.acl_repo_name)
605 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
601 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
606 with callback_daemon:
602 with callback_daemon:
607 app.rc_extras = extras
603 app.rc_extras = extras
608
604
609 try:
605 try:
610 response = app(environ, start_response)
606 response = app(environ, start_response)
611 finally:
607 finally:
612 # This statement works together with the decorator
608 # This statement works together with the decorator
613 # "initialize_generator" above. The decorator ensures that
609 # "initialize_generator" above. The decorator ensures that
614 # we hit the first yield statement before the generator is
610 # we hit the first yield statement before the generator is
615 # returned back to the WSGI server. This is needed to
611 # returned back to the WSGI server. This is needed to
616 # ensure that the call to "app" above triggers the
612 # ensure that the call to "app" above triggers the
617 # needed callback to "start_response" before the
613 # needed callback to "start_response" before the
618 # generator is actually used.
614 # generator is actually used.
619 yield "__init__"
615 yield "__init__"
620
616
621 # iter content
617 # iter content
622 for chunk in response:
618 for chunk in response:
623 yield chunk
619 yield chunk
624
620
625 try:
621 try:
626 # invalidate cache on push
622 # invalidate cache on push
627 if action == 'push':
623 if action == 'push':
628 self._invalidate_cache(self.url_repo_name)
624 self._invalidate_cache(self.url_repo_name)
629 finally:
625 finally:
630 meta.Session.remove()
626 meta.Session.remove()
631
627
632 def _get_repository_name(self, environ):
628 def _get_repository_name(self, environ):
633 """Get repository name out of the environmnent
629 """Get repository name out of the environmnent
634
630
635 :param environ: WSGI environment
631 :param environ: WSGI environment
636 """
632 """
637 raise NotImplementedError()
633 raise NotImplementedError()
638
634
639 def _get_action(self, environ):
635 def _get_action(self, environ):
640 """Map request commands into a pull or push command.
636 """Map request commands into a pull or push command.
641
637
642 :param environ: WSGI environment
638 :param environ: WSGI environment
643 """
639 """
644 raise NotImplementedError()
640 raise NotImplementedError()
645
641
646 def _create_wsgi_app(self, repo_path, repo_name, config):
642 def _create_wsgi_app(self, repo_path, repo_name, config):
647 """Return the WSGI app that will finally handle the request."""
643 """Return the WSGI app that will finally handle the request."""
648 raise NotImplementedError()
644 raise NotImplementedError()
649
645
650 def _create_config(self, extras, repo_name):
646 def _create_config(self, extras, repo_name):
651 """Create a safe config representation."""
647 """Create a safe config representation."""
652 raise NotImplementedError()
648 raise NotImplementedError()
653
649
654 def _should_use_callback_daemon(self, extras, environ, action):
650 def _should_use_callback_daemon(self, extras, environ, action):
655 return True
651 return True
656
652
657 def _prepare_callback_daemon(self, extras, environ, action, txn_id=None):
653 def _prepare_callback_daemon(self, extras, environ, action, txn_id=None):
658 direct_calls = vcs_settings.HOOKS_DIRECT_CALLS
654 direct_calls = vcs_settings.HOOKS_DIRECT_CALLS
659 if not self._should_use_callback_daemon(extras, environ, action):
655 if not self._should_use_callback_daemon(extras, environ, action):
660 # disable callback daemon for actions that don't require it
656 # disable callback daemon for actions that don't require it
661 direct_calls = True
657 direct_calls = True
662
658
663 return prepare_callback_daemon(
659 return prepare_callback_daemon(
664 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
660 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
665 host=vcs_settings.HOOKS_HOST, use_direct_calls=direct_calls, txn_id=txn_id)
661 host=vcs_settings.HOOKS_HOST, use_direct_calls=direct_calls, txn_id=txn_id)
666
662
667
663
668 def _should_check_locking(query_string):
664 def _should_check_locking(query_string):
669 # this is kind of hacky, but due to how mercurial handles client-server
665 # this is kind of hacky, but due to how mercurial handles client-server
670 # server see all operation on commit; bookmarks, phases and
666 # server see all operation on commit; bookmarks, phases and
671 # obsolescence marker in different transaction, we don't want to check
667 # obsolescence marker in different transaction, we don't want to check
672 # locking on those
668 # locking on those
673 return query_string not in ['cmd=listkeys']
669 return query_string not in ['cmd=listkeys']
@@ -1,459 +1,469 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Test suite for making push/pull operations, on specially modified INI files
22 Test suite for making push/pull operations, on specially modified INI files
23
23
24 .. important::
24 .. important::
25
25
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
27 to redirect things to stderr instead of stdout.
27 to redirect things to stderr instead of stdout.
28 """
28 """
29
29
30
30
31 import time
31 import time
32
32
33 import pytest
33 import pytest
34
34
35 from rhodecode.lib import rc_cache
35 from rhodecode.lib import rc_cache
36 from rhodecode.model.auth_token import AuthTokenModel
36 from rhodecode.model.auth_token import AuthTokenModel
37 from rhodecode.model.db import Repository, UserIpMap, CacheKey
37 from rhodecode.model.db import Repository, UserIpMap, CacheKey
38 from rhodecode.model.meta import Session
38 from rhodecode.model.meta import Session
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.user import UserModel
40 from rhodecode.model.user import UserModel
41 from rhodecode.tests import (GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN)
41 from rhodecode.tests import (GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN)
42
42
43 from rhodecode.tests.vcs_operations import (
43 from rhodecode.tests.vcs_operations import (
44 Command, _check_proper_clone, _check_proper_git_push,
44 Command, _check_proper_clone, _check_proper_git_push,
45 _add_files_and_push, HG_REPO_WITH_GROUP, GIT_REPO_WITH_GROUP)
45 _add_files_and_push, HG_REPO_WITH_GROUP, GIT_REPO_WITH_GROUP)
46
46
47
47
48 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
48 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
49 class TestVCSOperations(object):
49 class TestVCSOperations(object):
50
50
51 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
51 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
52 clone_url = rc_web_server.repo_clone_url(HG_REPO)
52 clone_url = rc_web_server.repo_clone_url(HG_REPO)
53 stdout, stderr = Command('/tmp').execute(
53 stdout, stderr = Command('/tmp').execute(
54 'hg clone', clone_url, tmpdir.strpath)
54 'hg clone', clone_url, tmpdir.strpath)
55 _check_proper_clone(stdout, stderr, 'hg')
55 _check_proper_clone(stdout, stderr, 'hg')
56
56
57 def test_clone_git_repo_by_admin(self, rc_web_server, tmpdir):
57 def test_clone_git_repo_by_admin(self, rc_web_server, tmpdir):
58 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
58 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
59 cmd = Command('/tmp')
59 cmd = Command('/tmp')
60 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
60 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
61 _check_proper_clone(stdout, stderr, 'git')
61 _check_proper_clone(stdout, stderr, 'git')
62 cmd.assert_returncode_success()
62 cmd.assert_returncode_success()
63
63
64 def test_clone_git_repo_by_admin_with_git_suffix(self, rc_web_server, tmpdir):
64 def test_clone_git_repo_by_admin_with_git_suffix(self, rc_web_server, tmpdir):
65 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
65 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
66 cmd = Command('/tmp')
66 cmd = Command('/tmp')
67 stdout, stderr = cmd.execute('git clone', clone_url+".git", tmpdir.strpath)
67 stdout, stderr = cmd.execute('git clone', clone_url+".git", tmpdir.strpath)
68 _check_proper_clone(stdout, stderr, 'git')
68 _check_proper_clone(stdout, stderr, 'git')
69 cmd.assert_returncode_success()
69 cmd.assert_returncode_success()
70
70
71 def test_clone_hg_repo_by_id_by_admin(self, rc_web_server, tmpdir):
71 def test_clone_hg_repo_by_id_by_admin(self, rc_web_server, tmpdir):
72 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
72 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
73 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
73 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
74 stdout, stderr = Command('/tmp').execute(
74 stdout, stderr = Command('/tmp').execute(
75 'hg clone', clone_url, tmpdir.strpath)
75 'hg clone', clone_url, tmpdir.strpath)
76 _check_proper_clone(stdout, stderr, 'hg')
76 _check_proper_clone(stdout, stderr, 'hg')
77
77
78 def test_clone_git_repo_by_id_by_admin(self, rc_web_server, tmpdir):
78 def test_clone_git_repo_by_id_by_admin(self, rc_web_server, tmpdir):
79 repo_id = Repository.get_by_repo_name(GIT_REPO).repo_id
79 repo_id = Repository.get_by_repo_name(GIT_REPO).repo_id
80 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
80 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
81 cmd = Command('/tmp')
81 cmd = Command('/tmp')
82 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
82 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
83 _check_proper_clone(stdout, stderr, 'git')
83 _check_proper_clone(stdout, stderr, 'git')
84 cmd.assert_returncode_success()
84 cmd.assert_returncode_success()
85
85
86 def test_clone_hg_repo_with_group_by_admin(self, rc_web_server, tmpdir):
86 def test_clone_hg_repo_with_group_by_admin(self, rc_web_server, tmpdir):
87 clone_url = rc_web_server.repo_clone_url(HG_REPO_WITH_GROUP)
87 clone_url = rc_web_server.repo_clone_url(HG_REPO_WITH_GROUP)
88 stdout, stderr = Command('/tmp').execute(
88 stdout, stderr = Command('/tmp').execute(
89 'hg clone', clone_url, tmpdir.strpath)
89 'hg clone', clone_url, tmpdir.strpath)
90 _check_proper_clone(stdout, stderr, 'hg')
90 _check_proper_clone(stdout, stderr, 'hg')
91
91
92 def test_clone_git_repo_with_group_by_admin(self, rc_web_server, tmpdir):
92 def test_clone_git_repo_with_group_by_admin(self, rc_web_server, tmpdir):
93 clone_url = rc_web_server.repo_clone_url(GIT_REPO_WITH_GROUP)
93 clone_url = rc_web_server.repo_clone_url(GIT_REPO_WITH_GROUP)
94 cmd = Command('/tmp')
94 cmd = Command('/tmp')
95 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
95 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
96 _check_proper_clone(stdout, stderr, 'git')
96 _check_proper_clone(stdout, stderr, 'git')
97 cmd.assert_returncode_success()
97 cmd.assert_returncode_success()
98
98
99 def test_clone_git_repo_shallow_by_admin(self, rc_web_server, tmpdir):
99 def test_clone_git_repo_shallow_by_admin(self, rc_web_server, tmpdir):
100 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
100 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
101 cmd = Command('/tmp')
101 cmd = Command('/tmp')
102 stdout, stderr = cmd.execute(
102 stdout, stderr = cmd.execute(
103 'git clone --depth=1', clone_url, tmpdir.strpath)
103 'git clone --depth=1', clone_url, tmpdir.strpath)
104
104
105 assert '' == stdout
105 assert '' == stdout
106 assert 'Cloning into' in stderr
106 assert 'Cloning into' in stderr
107 cmd.assert_returncode_success()
107 cmd.assert_returncode_success()
108
108
109 def test_clone_wrong_credentials_hg(self, rc_web_server, tmpdir):
109 def test_clone_wrong_credentials_hg(self, rc_web_server, tmpdir):
110 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
110 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
111 stdout, stderr = Command('/tmp').execute(
111 stdout, stderr = Command('/tmp').execute(
112 'hg clone', clone_url, tmpdir.strpath)
112 'hg clone', clone_url, tmpdir.strpath)
113 assert 'abort: authorization failed' in stderr
113 assert 'abort: authorization failed' in stderr
114
114
115 def test_clone_wrong_credentials_git(self, rc_web_server, tmpdir):
115 def test_clone_wrong_credentials_git(self, rc_web_server, tmpdir):
116 clone_url = rc_web_server.repo_clone_url(GIT_REPO, passwd='bad!')
116 clone_url = rc_web_server.repo_clone_url(GIT_REPO, passwd='bad!')
117 stdout, stderr = Command('/tmp').execute(
117 stdout, stderr = Command('/tmp').execute(
118 'git clone', clone_url, tmpdir.strpath)
118 'git clone', clone_url, tmpdir.strpath)
119 assert 'fatal: Authentication failed' in stderr
119 assert 'fatal: Authentication failed' in stderr
120
120
121 def test_clone_git_dir_as_hg(self, rc_web_server, tmpdir):
121 def test_clone_git_dir_as_hg(self, rc_web_server, tmpdir):
122 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
122 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
123 stdout, stderr = Command('/tmp').execute(
123 stdout, stderr = Command('/tmp').execute(
124 'hg clone', clone_url, tmpdir.strpath)
124 'hg clone', clone_url, tmpdir.strpath)
125 assert 'HTTP Error 404: Not Found' in stderr
125 assert 'HTTP Error 404: Not Found' in stderr
126
126
127 def test_clone_hg_repo_as_git(self, rc_web_server, tmpdir):
127 def test_clone_hg_repo_as_git(self, rc_web_server, tmpdir):
128 clone_url = rc_web_server.repo_clone_url(HG_REPO)
128 clone_url = rc_web_server.repo_clone_url(HG_REPO)
129 stdout, stderr = Command('/tmp').execute(
129 stdout, stderr = Command('/tmp').execute(
130 'git clone', clone_url, tmpdir.strpath)
130 'git clone', clone_url, tmpdir.strpath)
131 assert 'not found' in stderr
131 assert 'not found' in stderr
132
132
133 def test_clone_non_existing_path_hg(self, rc_web_server, tmpdir):
133 def test_clone_non_existing_path_hg(self, rc_web_server, tmpdir):
134 clone_url = rc_web_server.repo_clone_url('trololo')
134 clone_url = rc_web_server.repo_clone_url('trololo')
135 stdout, stderr = Command('/tmp').execute(
135 stdout, stderr = Command('/tmp').execute(
136 'hg clone', clone_url, tmpdir.strpath)
136 'hg clone', clone_url, tmpdir.strpath)
137 assert 'HTTP Error 404: Not Found' in stderr
137 assert 'HTTP Error 404: Not Found' in stderr
138
138
139 def test_clone_non_existing_path_git(self, rc_web_server, tmpdir):
139 def test_clone_non_existing_path_git(self, rc_web_server, tmpdir):
140 clone_url = rc_web_server.repo_clone_url('trololo')
140 clone_url = rc_web_server.repo_clone_url('trololo')
141 stdout, stderr = Command('/tmp').execute('git clone', clone_url)
141 stdout, stderr = Command('/tmp').execute('git clone', clone_url)
142 assert 'not found' in stderr
142 assert 'not found' in stderr
143
143
144 def test_clone_hg_with_slashes(self, rc_web_server, tmpdir):
145 clone_url = rc_web_server.repo_clone_url('//' + HG_REPO)
146 stdout, stderr = Command('/tmp').execute('hg clone', clone_url, tmpdir.strpath)
147 assert 'HTTP Error 404: Not Found' in stderr
148
149 def test_clone_git_with_slashes(self, rc_web_server, tmpdir):
150 clone_url = rc_web_server.repo_clone_url('//' + GIT_REPO)
151 stdout, stderr = Command('/tmp').execute('git clone', clone_url)
152 assert 'not found' in stderr
153
144 def test_clone_existing_path_hg_not_in_database(
154 def test_clone_existing_path_hg_not_in_database(
145 self, rc_web_server, tmpdir, fs_repo_only):
155 self, rc_web_server, tmpdir, fs_repo_only):
146
156
147 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
157 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
148 clone_url = rc_web_server.repo_clone_url(db_name)
158 clone_url = rc_web_server.repo_clone_url(db_name)
149 stdout, stderr = Command('/tmp').execute(
159 stdout, stderr = Command('/tmp').execute(
150 'hg clone', clone_url, tmpdir.strpath)
160 'hg clone', clone_url, tmpdir.strpath)
151 assert 'HTTP Error 404: Not Found' in stderr
161 assert 'HTTP Error 404: Not Found' in stderr
152
162
153 def test_clone_existing_path_git_not_in_database(
163 def test_clone_existing_path_git_not_in_database(
154 self, rc_web_server, tmpdir, fs_repo_only):
164 self, rc_web_server, tmpdir, fs_repo_only):
155 db_name = fs_repo_only('not-in-db-git', repo_type='git')
165 db_name = fs_repo_only('not-in-db-git', repo_type='git')
156 clone_url = rc_web_server.repo_clone_url(db_name)
166 clone_url = rc_web_server.repo_clone_url(db_name)
157 stdout, stderr = Command('/tmp').execute(
167 stdout, stderr = Command('/tmp').execute(
158 'git clone', clone_url, tmpdir.strpath)
168 'git clone', clone_url, tmpdir.strpath)
159 assert 'not found' in stderr
169 assert 'not found' in stderr
160
170
161 def test_clone_existing_path_hg_not_in_database_different_scm(
171 def test_clone_existing_path_hg_not_in_database_different_scm(
162 self, rc_web_server, tmpdir, fs_repo_only):
172 self, rc_web_server, tmpdir, fs_repo_only):
163 db_name = fs_repo_only('not-in-db-git', repo_type='git')
173 db_name = fs_repo_only('not-in-db-git', repo_type='git')
164 clone_url = rc_web_server.repo_clone_url(db_name)
174 clone_url = rc_web_server.repo_clone_url(db_name)
165 stdout, stderr = Command('/tmp').execute(
175 stdout, stderr = Command('/tmp').execute(
166 'hg clone', clone_url, tmpdir.strpath)
176 'hg clone', clone_url, tmpdir.strpath)
167 assert 'HTTP Error 404: Not Found' in stderr
177 assert 'HTTP Error 404: Not Found' in stderr
168
178
169 def test_clone_existing_path_git_not_in_database_different_scm(
179 def test_clone_existing_path_git_not_in_database_different_scm(
170 self, rc_web_server, tmpdir, fs_repo_only):
180 self, rc_web_server, tmpdir, fs_repo_only):
171 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
181 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
172 clone_url = rc_web_server.repo_clone_url(db_name)
182 clone_url = rc_web_server.repo_clone_url(db_name)
173 stdout, stderr = Command('/tmp').execute(
183 stdout, stderr = Command('/tmp').execute(
174 'git clone', clone_url, tmpdir.strpath)
184 'git clone', clone_url, tmpdir.strpath)
175 assert 'not found' in stderr
185 assert 'not found' in stderr
176
186
177 def test_clone_non_existing_store_path_hg(self, rc_web_server, tmpdir, user_util):
187 def test_clone_non_existing_store_path_hg(self, rc_web_server, tmpdir, user_util):
178 repo = user_util.create_repo()
188 repo = user_util.create_repo()
179 clone_url = rc_web_server.repo_clone_url(repo.repo_name)
189 clone_url = rc_web_server.repo_clone_url(repo.repo_name)
180
190
181 # Damage repo by removing it's folder
191 # Damage repo by removing it's folder
182 RepoModel()._delete_filesystem_repo(repo)
192 RepoModel()._delete_filesystem_repo(repo)
183
193
184 stdout, stderr = Command('/tmp').execute(
194 stdout, stderr = Command('/tmp').execute(
185 'hg clone', clone_url, tmpdir.strpath)
195 'hg clone', clone_url, tmpdir.strpath)
186 assert 'HTTP Error 404: Not Found' in stderr
196 assert 'HTTP Error 404: Not Found' in stderr
187
197
188 def test_clone_non_existing_store_path_git(self, rc_web_server, tmpdir, user_util):
198 def test_clone_non_existing_store_path_git(self, rc_web_server, tmpdir, user_util):
189 repo = user_util.create_repo(repo_type='git')
199 repo = user_util.create_repo(repo_type='git')
190 clone_url = rc_web_server.repo_clone_url(repo.repo_name)
200 clone_url = rc_web_server.repo_clone_url(repo.repo_name)
191
201
192 # Damage repo by removing it's folder
202 # Damage repo by removing it's folder
193 RepoModel()._delete_filesystem_repo(repo)
203 RepoModel()._delete_filesystem_repo(repo)
194
204
195 stdout, stderr = Command('/tmp').execute(
205 stdout, stderr = Command('/tmp').execute(
196 'git clone', clone_url, tmpdir.strpath)
206 'git clone', clone_url, tmpdir.strpath)
197 assert 'not found' in stderr
207 assert 'not found' in stderr
198
208
199 def test_push_new_file_hg(self, rc_web_server, tmpdir):
209 def test_push_new_file_hg(self, rc_web_server, tmpdir):
200 clone_url = rc_web_server.repo_clone_url(HG_REPO)
210 clone_url = rc_web_server.repo_clone_url(HG_REPO)
201 stdout, stderr = Command('/tmp').execute(
211 stdout, stderr = Command('/tmp').execute(
202 'hg clone', clone_url, tmpdir.strpath)
212 'hg clone', clone_url, tmpdir.strpath)
203
213
204 stdout, stderr = _add_files_and_push(
214 stdout, stderr = _add_files_and_push(
205 'hg', tmpdir.strpath, clone_url=clone_url)
215 'hg', tmpdir.strpath, clone_url=clone_url)
206
216
207 assert 'pushing to' in stdout
217 assert 'pushing to' in stdout
208 assert 'size summary' in stdout
218 assert 'size summary' in stdout
209
219
210 def test_push_new_file_git(self, rc_web_server, tmpdir):
220 def test_push_new_file_git(self, rc_web_server, tmpdir):
211 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
221 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
212 stdout, stderr = Command('/tmp').execute(
222 stdout, stderr = Command('/tmp').execute(
213 'git clone', clone_url, tmpdir.strpath)
223 'git clone', clone_url, tmpdir.strpath)
214
224
215 # commit some stuff into this repo
225 # commit some stuff into this repo
216 stdout, stderr = _add_files_and_push(
226 stdout, stderr = _add_files_and_push(
217 'git', tmpdir.strpath, clone_url=clone_url)
227 'git', tmpdir.strpath, clone_url=clone_url)
218
228
219 _check_proper_git_push(stdout, stderr)
229 _check_proper_git_push(stdout, stderr)
220
230
221 def test_push_invalidates_cache(self, rc_web_server, tmpdir):
231 def test_push_invalidates_cache(self, rc_web_server, tmpdir):
222 hg_repo = Repository.get_by_repo_name(HG_REPO)
232 hg_repo = Repository.get_by_repo_name(HG_REPO)
223
233
224 # init cache objects
234 # init cache objects
225 CacheKey.delete_all_cache()
235 CacheKey.delete_all_cache()
226 cache_namespace_uid = 'cache_push_test.{}'.format(hg_repo.repo_id)
236 cache_namespace_uid = 'cache_push_test.{}'.format(hg_repo.repo_id)
227 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
237 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
228 repo_id=hg_repo.repo_id)
238 repo_id=hg_repo.repo_id)
229
239
230 inv_context_manager = rc_cache.InvalidationContext(
240 inv_context_manager = rc_cache.InvalidationContext(
231 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
241 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
232
242
233 with inv_context_manager as invalidation_context:
243 with inv_context_manager as invalidation_context:
234 # __enter__ will create and register cache objects
244 # __enter__ will create and register cache objects
235 pass
245 pass
236
246
237 # clone to init cache
247 # clone to init cache
238 clone_url = rc_web_server.repo_clone_url(hg_repo.repo_name)
248 clone_url = rc_web_server.repo_clone_url(hg_repo.repo_name)
239 stdout, stderr = Command('/tmp').execute(
249 stdout, stderr = Command('/tmp').execute(
240 'hg clone', clone_url, tmpdir.strpath)
250 'hg clone', clone_url, tmpdir.strpath)
241
251
242 cache_keys = hg_repo.cache_keys
252 cache_keys = hg_repo.cache_keys
243 assert cache_keys != []
253 assert cache_keys != []
244 for key in cache_keys:
254 for key in cache_keys:
245 assert key.cache_active is True
255 assert key.cache_active is True
246
256
247 # PUSH that should trigger invalidation cache
257 # PUSH that should trigger invalidation cache
248 stdout, stderr = _add_files_and_push(
258 stdout, stderr = _add_files_and_push(
249 'hg', tmpdir.strpath, clone_url=clone_url, files_no=1)
259 'hg', tmpdir.strpath, clone_url=clone_url, files_no=1)
250
260
251 # flush...
261 # flush...
252 Session().commit()
262 Session().commit()
253 hg_repo = Repository.get_by_repo_name(HG_REPO)
263 hg_repo = Repository.get_by_repo_name(HG_REPO)
254 cache_keys = hg_repo.cache_keys
264 cache_keys = hg_repo.cache_keys
255 assert cache_keys != []
265 assert cache_keys != []
256 for key in cache_keys:
266 for key in cache_keys:
257 # keys should be marked as not active
267 # keys should be marked as not active
258 assert key.cache_active is False
268 assert key.cache_active is False
259
269
260 def test_push_wrong_credentials_hg(self, rc_web_server, tmpdir):
270 def test_push_wrong_credentials_hg(self, rc_web_server, tmpdir):
261 clone_url = rc_web_server.repo_clone_url(HG_REPO)
271 clone_url = rc_web_server.repo_clone_url(HG_REPO)
262 stdout, stderr = Command('/tmp').execute(
272 stdout, stderr = Command('/tmp').execute(
263 'hg clone', clone_url, tmpdir.strpath)
273 'hg clone', clone_url, tmpdir.strpath)
264
274
265 push_url = rc_web_server.repo_clone_url(
275 push_url = rc_web_server.repo_clone_url(
266 HG_REPO, user='bad', passwd='name')
276 HG_REPO, user='bad', passwd='name')
267 stdout, stderr = _add_files_and_push(
277 stdout, stderr = _add_files_and_push(
268 'hg', tmpdir.strpath, clone_url=push_url)
278 'hg', tmpdir.strpath, clone_url=push_url)
269
279
270 assert 'abort: authorization failed' in stderr
280 assert 'abort: authorization failed' in stderr
271
281
272 def test_push_wrong_credentials_git(self, rc_web_server, tmpdir):
282 def test_push_wrong_credentials_git(self, rc_web_server, tmpdir):
273 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
283 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
274 stdout, stderr = Command('/tmp').execute(
284 stdout, stderr = Command('/tmp').execute(
275 'git clone', clone_url, tmpdir.strpath)
285 'git clone', clone_url, tmpdir.strpath)
276
286
277 push_url = rc_web_server.repo_clone_url(
287 push_url = rc_web_server.repo_clone_url(
278 GIT_REPO, user='bad', passwd='name')
288 GIT_REPO, user='bad', passwd='name')
279 stdout, stderr = _add_files_and_push(
289 stdout, stderr = _add_files_and_push(
280 'git', tmpdir.strpath, clone_url=push_url)
290 'git', tmpdir.strpath, clone_url=push_url)
281
291
282 assert 'fatal: Authentication failed' in stderr
292 assert 'fatal: Authentication failed' in stderr
283
293
284 def test_push_back_to_wrong_url_hg(self, rc_web_server, tmpdir):
294 def test_push_back_to_wrong_url_hg(self, rc_web_server, tmpdir):
285 clone_url = rc_web_server.repo_clone_url(HG_REPO)
295 clone_url = rc_web_server.repo_clone_url(HG_REPO)
286 stdout, stderr = Command('/tmp').execute(
296 stdout, stderr = Command('/tmp').execute(
287 'hg clone', clone_url, tmpdir.strpath)
297 'hg clone', clone_url, tmpdir.strpath)
288
298
289 stdout, stderr = _add_files_and_push(
299 stdout, stderr = _add_files_and_push(
290 'hg', tmpdir.strpath,
300 'hg', tmpdir.strpath,
291 clone_url=rc_web_server.repo_clone_url('not-existing'))
301 clone_url=rc_web_server.repo_clone_url('not-existing'))
292
302
293 assert 'HTTP Error 404: Not Found' in stderr
303 assert 'HTTP Error 404: Not Found' in stderr
294
304
295 def test_push_back_to_wrong_url_git(self, rc_web_server, tmpdir):
305 def test_push_back_to_wrong_url_git(self, rc_web_server, tmpdir):
296 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
306 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
297 stdout, stderr = Command('/tmp').execute(
307 stdout, stderr = Command('/tmp').execute(
298 'git clone', clone_url, tmpdir.strpath)
308 'git clone', clone_url, tmpdir.strpath)
299
309
300 stdout, stderr = _add_files_and_push(
310 stdout, stderr = _add_files_and_push(
301 'git', tmpdir.strpath,
311 'git', tmpdir.strpath,
302 clone_url=rc_web_server.repo_clone_url('not-existing'))
312 clone_url=rc_web_server.repo_clone_url('not-existing'))
303
313
304 assert 'not found' in stderr
314 assert 'not found' in stderr
305
315
306 def test_ip_restriction_hg(self, rc_web_server, tmpdir):
316 def test_ip_restriction_hg(self, rc_web_server, tmpdir):
307 user_model = UserModel()
317 user_model = UserModel()
308 try:
318 try:
309 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
319 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
310 Session().commit()
320 Session().commit()
311 time.sleep(2)
321 time.sleep(2)
312 clone_url = rc_web_server.repo_clone_url(HG_REPO)
322 clone_url = rc_web_server.repo_clone_url(HG_REPO)
313 stdout, stderr = Command('/tmp').execute(
323 stdout, stderr = Command('/tmp').execute(
314 'hg clone', clone_url, tmpdir.strpath)
324 'hg clone', clone_url, tmpdir.strpath)
315 assert 'abort: HTTP Error 403: Forbidden' in stderr
325 assert 'abort: HTTP Error 403: Forbidden' in stderr
316 finally:
326 finally:
317 # release IP restrictions
327 # release IP restrictions
318 for ip in UserIpMap.getAll():
328 for ip in UserIpMap.getAll():
319 UserIpMap.delete(ip.ip_id)
329 UserIpMap.delete(ip.ip_id)
320 Session().commit()
330 Session().commit()
321
331
322 time.sleep(2)
332 time.sleep(2)
323
333
324 stdout, stderr = Command('/tmp').execute(
334 stdout, stderr = Command('/tmp').execute(
325 'hg clone', clone_url, tmpdir.strpath)
335 'hg clone', clone_url, tmpdir.strpath)
326 _check_proper_clone(stdout, stderr, 'hg')
336 _check_proper_clone(stdout, stderr, 'hg')
327
337
328 def test_ip_restriction_git(self, rc_web_server, tmpdir):
338 def test_ip_restriction_git(self, rc_web_server, tmpdir):
329 user_model = UserModel()
339 user_model = UserModel()
330 try:
340 try:
331 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
341 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
332 Session().commit()
342 Session().commit()
333 time.sleep(2)
343 time.sleep(2)
334 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
344 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
335 stdout, stderr = Command('/tmp').execute(
345 stdout, stderr = Command('/tmp').execute(
336 'git clone', clone_url, tmpdir.strpath)
346 'git clone', clone_url, tmpdir.strpath)
337 msg = "The requested URL returned error: 403"
347 msg = "The requested URL returned error: 403"
338 assert msg in stderr
348 assert msg in stderr
339 finally:
349 finally:
340 # release IP restrictions
350 # release IP restrictions
341 for ip in UserIpMap.getAll():
351 for ip in UserIpMap.getAll():
342 UserIpMap.delete(ip.ip_id)
352 UserIpMap.delete(ip.ip_id)
343 Session().commit()
353 Session().commit()
344
354
345 time.sleep(2)
355 time.sleep(2)
346
356
347 cmd = Command('/tmp')
357 cmd = Command('/tmp')
348 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
358 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
349 cmd.assert_returncode_success()
359 cmd.assert_returncode_success()
350 _check_proper_clone(stdout, stderr, 'git')
360 _check_proper_clone(stdout, stderr, 'git')
351
361
352 def test_clone_by_auth_token(
362 def test_clone_by_auth_token(
353 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
363 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
354 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
364 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
355 'egg:rhodecode-enterprise-ce#rhodecode'])
365 'egg:rhodecode-enterprise-ce#rhodecode'])
356
366
357 user = user_util.create_user()
367 user = user_util.create_user()
358 token = user.auth_tokens[1]
368 token = user.auth_tokens[1]
359
369
360 clone_url = rc_web_server.repo_clone_url(
370 clone_url = rc_web_server.repo_clone_url(
361 HG_REPO, user=user.username, passwd=token)
371 HG_REPO, user=user.username, passwd=token)
362
372
363 stdout, stderr = Command('/tmp').execute(
373 stdout, stderr = Command('/tmp').execute(
364 'hg clone', clone_url, tmpdir.strpath)
374 'hg clone', clone_url, tmpdir.strpath)
365 _check_proper_clone(stdout, stderr, 'hg')
375 _check_proper_clone(stdout, stderr, 'hg')
366
376
367 def test_clone_by_auth_token_expired(
377 def test_clone_by_auth_token_expired(
368 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
378 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
369 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
379 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
370 'egg:rhodecode-enterprise-ce#rhodecode'])
380 'egg:rhodecode-enterprise-ce#rhodecode'])
371
381
372 user = user_util.create_user()
382 user = user_util.create_user()
373 auth_token = AuthTokenModel().create(
383 auth_token = AuthTokenModel().create(
374 user.user_id, 'test-token', -10, AuthTokenModel.cls.ROLE_VCS)
384 user.user_id, 'test-token', -10, AuthTokenModel.cls.ROLE_VCS)
375 token = auth_token.api_key
385 token = auth_token.api_key
376
386
377 clone_url = rc_web_server.repo_clone_url(
387 clone_url = rc_web_server.repo_clone_url(
378 HG_REPO, user=user.username, passwd=token)
388 HG_REPO, user=user.username, passwd=token)
379
389
380 stdout, stderr = Command('/tmp').execute(
390 stdout, stderr = Command('/tmp').execute(
381 'hg clone', clone_url, tmpdir.strpath)
391 'hg clone', clone_url, tmpdir.strpath)
382 assert 'abort: authorization failed' in stderr
392 assert 'abort: authorization failed' in stderr
383
393
384 def test_clone_by_auth_token_bad_role(
394 def test_clone_by_auth_token_bad_role(
385 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
395 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
386 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
396 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
387 'egg:rhodecode-enterprise-ce#rhodecode'])
397 'egg:rhodecode-enterprise-ce#rhodecode'])
388
398
389 user = user_util.create_user()
399 user = user_util.create_user()
390 auth_token = AuthTokenModel().create(
400 auth_token = AuthTokenModel().create(
391 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API)
401 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API)
392 token = auth_token.api_key
402 token = auth_token.api_key
393
403
394 clone_url = rc_web_server.repo_clone_url(
404 clone_url = rc_web_server.repo_clone_url(
395 HG_REPO, user=user.username, passwd=token)
405 HG_REPO, user=user.username, passwd=token)
396
406
397 stdout, stderr = Command('/tmp').execute(
407 stdout, stderr = Command('/tmp').execute(
398 'hg clone', clone_url, tmpdir.strpath)
408 'hg clone', clone_url, tmpdir.strpath)
399 assert 'abort: authorization failed' in stderr
409 assert 'abort: authorization failed' in stderr
400
410
401 def test_clone_by_auth_token_user_disabled(
411 def test_clone_by_auth_token_user_disabled(
402 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
412 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
403 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
413 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
404 'egg:rhodecode-enterprise-ce#rhodecode'])
414 'egg:rhodecode-enterprise-ce#rhodecode'])
405 user = user_util.create_user()
415 user = user_util.create_user()
406 user.active = False
416 user.active = False
407 Session().add(user)
417 Session().add(user)
408 Session().commit()
418 Session().commit()
409 token = user.auth_tokens[1]
419 token = user.auth_tokens[1]
410
420
411 clone_url = rc_web_server.repo_clone_url(
421 clone_url = rc_web_server.repo_clone_url(
412 HG_REPO, user=user.username, passwd=token)
422 HG_REPO, user=user.username, passwd=token)
413
423
414 stdout, stderr = Command('/tmp').execute(
424 stdout, stderr = Command('/tmp').execute(
415 'hg clone', clone_url, tmpdir.strpath)
425 'hg clone', clone_url, tmpdir.strpath)
416 assert 'abort: authorization failed' in stderr
426 assert 'abort: authorization failed' in stderr
417
427
418 def test_clone_by_auth_token_with_scope(
428 def test_clone_by_auth_token_with_scope(
419 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
429 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
420 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
430 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
421 'egg:rhodecode-enterprise-ce#rhodecode'])
431 'egg:rhodecode-enterprise-ce#rhodecode'])
422 user = user_util.create_user()
432 user = user_util.create_user()
423 auth_token = AuthTokenModel().create(
433 auth_token = AuthTokenModel().create(
424 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
434 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
425 token = auth_token.api_key
435 token = auth_token.api_key
426
436
427 # manually set scope
437 # manually set scope
428 auth_token.repo = Repository.get_by_repo_name(HG_REPO)
438 auth_token.repo = Repository.get_by_repo_name(HG_REPO)
429 Session().add(auth_token)
439 Session().add(auth_token)
430 Session().commit()
440 Session().commit()
431
441
432 clone_url = rc_web_server.repo_clone_url(
442 clone_url = rc_web_server.repo_clone_url(
433 HG_REPO, user=user.username, passwd=token)
443 HG_REPO, user=user.username, passwd=token)
434
444
435 stdout, stderr = Command('/tmp').execute(
445 stdout, stderr = Command('/tmp').execute(
436 'hg clone', clone_url, tmpdir.strpath)
446 'hg clone', clone_url, tmpdir.strpath)
437 _check_proper_clone(stdout, stderr, 'hg')
447 _check_proper_clone(stdout, stderr, 'hg')
438
448
439 def test_clone_by_auth_token_with_wrong_scope(
449 def test_clone_by_auth_token_with_wrong_scope(
440 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
450 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
441 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
451 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
442 'egg:rhodecode-enterprise-ce#rhodecode'])
452 'egg:rhodecode-enterprise-ce#rhodecode'])
443 user = user_util.create_user()
453 user = user_util.create_user()
444 auth_token = AuthTokenModel().create(
454 auth_token = AuthTokenModel().create(
445 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
455 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
446 token = auth_token.api_key
456 token = auth_token.api_key
447
457
448 # manually set scope
458 # manually set scope
449 auth_token.repo = Repository.get_by_repo_name(GIT_REPO)
459 auth_token.repo = Repository.get_by_repo_name(GIT_REPO)
450 Session().add(auth_token)
460 Session().add(auth_token)
451 Session().commit()
461 Session().commit()
452
462
453 clone_url = rc_web_server.repo_clone_url(
463 clone_url = rc_web_server.repo_clone_url(
454 HG_REPO, user=user.username, passwd=token)
464 HG_REPO, user=user.username, passwd=token)
455
465
456 stdout, stderr = Command('/tmp').execute(
466 stdout, stderr = Command('/tmp').execute(
457 'hg clone', clone_url, tmpdir.strpath)
467 'hg clone', clone_url, tmpdir.strpath)
458 assert 'abort: authorization failed' in stderr
468 assert 'abort: authorization failed' in stderr
459
469
General Comments 0
You need to be logged in to leave comments. Login now