Show More
@@ -0,0 +1,38 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | # This program is free software: you can redistribute it and/or modify | |||
|
3 | # it under the terms of the GNU General Public License as published by | |||
|
4 | # the Free Software Foundation, either version 3 of the License, or | |||
|
5 | # (at your option) any later version. | |||
|
6 | # | |||
|
7 | # This program is distributed in the hope that it will be useful, | |||
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
10 | # GNU General Public License for more details. | |||
|
11 | # | |||
|
12 | # You should have received a copy of the GNU General Public License | |||
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
14 | """ | |||
|
15 | kallithea.lib.middleware.permanent_repo_url | |||
|
16 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |||
|
17 | ||||
|
18 | middleware to handle permanent repo URLs, replacing PATH_INFO '/_123/yada' with | |||
|
19 | '/name/of/repo/yada' after looking 123 up in the database. | |||
|
20 | """ | |||
|
21 | ||||
|
22 | ||||
|
23 | from kallithea.lib.utils import safe_str, fix_repo_id_name | |||
|
24 | ||||
|
25 | ||||
|
26 | class PermanentRepoUrl(object): | |||
|
27 | ||||
|
28 | def __init__(self, app, config): | |||
|
29 | self.application = app | |||
|
30 | self.config = config | |||
|
31 | ||||
|
32 | def __call__(self, environ, start_response): | |||
|
33 | path_info = environ['PATH_INFO'] | |||
|
34 | if path_info.startswith('/'): # it must | |||
|
35 | path_info = '/' + safe_str(fix_repo_id_name(path_info[1:])) | |||
|
36 | environ['PATH_INFO'] = path_info | |||
|
37 | ||||
|
38 | return self.application(environ, start_response) |
@@ -30,6 +30,7 b' from alembic.migration import MigrationC' | |||||
30 | from sqlalchemy import create_engine |
|
30 | from sqlalchemy import create_engine | |
31 | import mercurial |
|
31 | import mercurial | |
32 |
|
32 | |||
|
33 | from kallithea.lib.middleware.permanent_repo_url import PermanentRepoUrl | |||
33 | from kallithea.lib.middleware.https_fixup import HttpsFixup |
|
34 | from kallithea.lib.middleware.https_fixup import HttpsFixup | |
34 | from kallithea.lib.middleware.simplegit import SimpleGit |
|
35 | from kallithea.lib.middleware.simplegit import SimpleGit | |
35 | from kallithea.lib.middleware.simplehg import SimpleHg |
|
36 | from kallithea.lib.middleware.simplehg import SimpleHg | |
@@ -208,6 +209,8 b' def setup_application(app):' | |||||
208 | # Enable https redirects based on HTTP_X_URL_SCHEME set by proxy |
|
209 | # Enable https redirects based on HTTP_X_URL_SCHEME set by proxy | |
209 | if any(asbool(config.get(x)) for x in ['https_fixup', 'force_https', 'use_htsts']): |
|
210 | if any(asbool(config.get(x)) for x in ['https_fixup', 'force_https', 'use_htsts']): | |
210 | app = HttpsFixup(app, config) |
|
211 | app = HttpsFixup(app, config) | |
|
212 | ||||
|
213 | app = PermanentRepoUrl(app, config) | |||
211 | return app |
|
214 | return app | |
212 |
|
215 | |||
213 |
|
216 |
@@ -33,28 +33,18 b' def make_map(config):' | |||||
33 | rmap.minimization = False |
|
33 | rmap.minimization = False | |
34 | rmap.explicit = False |
|
34 | rmap.explicit = False | |
35 |
|
35 | |||
36 |
from kallithea.lib.utils import |
|
36 | from kallithea.lib.utils import is_valid_repo, is_valid_repo_group | |
37 | get_repo_by_id) |
|
|||
38 |
|
37 | |||
39 | def check_repo(environ, match_dict): |
|
38 | def check_repo(environ, match_dict): | |
40 | """ |
|
39 | """ | |
41 |
|
|
40 | Check for valid repository for proper 404 handling. | |
42 |
|
41 | Also, a bit of side effect modifying match_dict ... | ||
43 | :param environ: |
|
|||
44 | :param match_dict: |
|
|||
45 | """ |
|
42 | """ | |
46 | repo_name = match_dict.get('repo_name') |
|
|||
47 |
|
||||
48 | if match_dict.get('f_path'): |
|
43 | if match_dict.get('f_path'): | |
49 | # fix for multiple initial slashes that causes errors |
|
44 | # fix for multiple initial slashes that causes errors | |
50 | match_dict['f_path'] = match_dict['f_path'].lstrip('/') |
|
45 | match_dict['f_path'] = match_dict['f_path'].lstrip('/') | |
51 |
|
46 | |||
52 | by_id_match = get_repo_by_id(repo_name) |
|
47 | return is_valid_repo(match_dict['repo_name'], config['base_path']) | |
53 | if by_id_match: |
|
|||
54 | repo_name = by_id_match |
|
|||
55 | match_dict['repo_name'] = repo_name |
|
|||
56 |
|
||||
57 | return is_valid_repo(repo_name, config['base_path']) |
|
|||
58 |
|
48 | |||
59 | def check_group(environ, match_dict): |
|
49 | def check_group(environ, match_dict): | |
60 | """ |
|
50 | """ |
@@ -266,23 +266,6 b' class BaseVCSController(object):' | |||||
266 | def _handle_request(self, environ, start_response): |
|
266 | def _handle_request(self, environ, start_response): | |
267 | raise NotImplementedError() |
|
267 | raise NotImplementedError() | |
268 |
|
268 | |||
269 | def _get_by_id(self, repo_name): |
|
|||
270 | """ |
|
|||
271 | Gets a special pattern _<ID> from clone url and tries to replace it |
|
|||
272 | with a repository_name for support of _<ID> permanent URLs |
|
|||
273 |
|
||||
274 | :param repo_name: |
|
|||
275 | """ |
|
|||
276 |
|
||||
277 | data = repo_name.split('/') |
|
|||
278 | if len(data) >= 2: |
|
|||
279 | from kallithea.lib.utils import get_repo_by_id |
|
|||
280 | by_id_match = get_repo_by_id(repo_name) |
|
|||
281 | if by_id_match: |
|
|||
282 | data[1] = safe_str(by_id_match) |
|
|||
283 |
|
||||
284 | return '/'.join(data) |
|
|||
285 |
|
||||
286 | def _check_permission(self, action, authuser, repo_name): |
|
269 | def _check_permission(self, action, authuser, repo_name): | |
287 | """ |
|
270 | """ | |
288 | Checks permissions using action (push/pull) user and repository |
|
271 | Checks permissions using action (push/pull) user and repository |
@@ -141,14 +141,11 b' class SimpleGit(BaseVCSController):' | |||||
141 | :param environ: environ where PATH_INFO is stored |
|
141 | :param environ: environ where PATH_INFO is stored | |
142 | """ |
|
142 | """ | |
143 | try: |
|
143 | try: | |
144 | environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO']) |
|
144 | return GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1) | |
145 | repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1) |
|
|||
146 | except Exception: |
|
145 | except Exception: | |
147 | log.error(traceback.format_exc()) |
|
146 | log.error(traceback.format_exc()) | |
148 | raise |
|
147 | raise | |
149 |
|
148 | |||
150 | return repo_name |
|
|||
151 |
|
||||
152 | def __get_action(self, environ): |
|
149 | def __get_action(self, environ): | |
153 | """ |
|
150 | """ | |
154 | Maps Git request commands into a pull or push command. |
|
151 | Maps Git request commands into a pull or push command. |
@@ -167,16 +167,13 b' class SimpleHg(BaseVCSController):' | |||||
167 | :param environ: environ where PATH_INFO is stored |
|
167 | :param environ: environ where PATH_INFO is stored | |
168 | """ |
|
168 | """ | |
169 | try: |
|
169 | try: | |
170 | environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO']) |
|
170 | path_info = environ['PATH_INFO'] | |
171 | repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:]) |
|
171 | if path_info.startswith('/'): | |
172 | if repo_name.endswith('/'): |
|
172 | return path_info[1:].rstrip('/') | |
173 | repo_name = repo_name.rstrip('/') |
|
|||
174 | except Exception: |
|
173 | except Exception: | |
175 | log.error(traceback.format_exc()) |
|
174 | log.error(traceback.format_exc()) | |
176 | raise |
|
175 | raise | |
177 |
|
176 | |||
178 | return repo_name |
|
|||
179 |
|
||||
180 | def __get_action(self, environ): |
|
177 | def __get_action(self, environ): | |
181 | """ |
|
178 | """ | |
182 | Maps Mercurial request commands into 'pull' or 'push'. |
|
179 | Maps Mercurial request commands into 'pull' or 'push'. |
@@ -78,29 +78,35 b' def get_user_group_slug(request):' | |||||
78 | return None |
|
78 | return None | |
79 |
|
79 | |||
80 |
|
80 | |||
81 | def _extract_id_from_repo_name(repo_name): |
|
81 | def _get_permanent_id(s): | |
82 | if repo_name.startswith('/'): |
|
82 | """Helper for decoding stable URLs with repo ID. For a string like '_123' | |
83 | repo_name = repo_name.lstrip('/') |
|
83 | return 123. | |
84 | by_id_match = re.match(r'^_(\d{1,})', repo_name) |
|
84 | """ | |
85 | if by_id_match: |
|
85 | by_id_match = re.match(r'^_(\d+)$', s) | |
86 | return by_id_match.groups()[0] |
|
86 | if by_id_match is None: | |
|
87 | return None | |||
|
88 | return int(by_id_match.group(1)) | |||
87 |
|
89 | |||
88 |
|
90 | |||
89 |
def |
|
91 | def fix_repo_id_name(path): | |
90 | """ |
|
92 | """ | |
91 | Extracts repo_name by id from special urls. Example url is _11/repo_name |
|
93 | Rewrite repo_name for _<ID> permanent URLs. | |
92 |
|
94 | |||
93 | :param repo_name: |
|
95 | Given a path, if the first path element is like _<ID>, return the path with | |
94 | :return: repo_name if matched else None |
|
96 | this part expanded to the corresponding full repo name, else return the | |
|
97 | provided path. | |||
95 | """ |
|
98 | """ | |
96 | _repo_id = _extract_id_from_repo_name(repo_name) |
|
99 | first, rest = path, '' | |
97 | if _repo_id: |
|
100 | if '/' in path: | |
|
101 | first, rest_ = path.split('/', 1) | |||
|
102 | rest = '/' + rest_ | |||
|
103 | repo_id = _get_permanent_id(first) | |||
|
104 | if repo_id is not None: | |||
98 | from kallithea.model.db import Repository |
|
105 | from kallithea.model.db import Repository | |
99 |
repo = Repository.get( |
|
106 | repo = Repository.get(repo_id) | |
100 | if repo: |
|
107 | if repo is not None: | |
101 | # TODO: return repo instead of reponame? or would that be a layering violation? |
|
108 | return repo.repo_name + rest | |
102 | return repo.repo_name |
|
109 | return path | |
103 | return None |
|
|||
104 |
|
110 | |||
105 |
|
111 | |||
106 | def action_logger(user, action, repo, ipaddr='', commit=False): |
|
112 | def action_logger(user, action, repo, ipaddr='', commit=False): |
@@ -532,26 +532,32 b' class TestLibs(TestController):' | |||||
532 |
|
532 | |||
533 | @parametrize('test,expected', [ |
|
533 | @parametrize('test,expected', [ | |
534 | ("", None), |
|
534 | ("", None), | |
535 |
("/_2", |
|
535 | ("/_2", None), | |
536 |
("_2", |
|
536 | ("_2", 2), | |
537 |
(" |
|
537 | ("_2/", None), | |
538 | ("_2/", '2'), |
|
538 | ]) | |
539 |
|
539 | def test_get_permanent_id(self, test, expected): | ||
540 | ("/_21", '21'), |
|
540 | from kallithea.lib.utils import _get_permanent_id | |
541 | ("_21", '21'), |
|
541 | extracted = _get_permanent_id(test) | |
542 | ("/_21/", '21'), |
|
542 | assert extracted == expected, 'url:%s, got:`%s` expected: `%s`' % (test, _test, expected) | |
543 | ("_21/", '21'), |
|
|||
544 |
|
543 | |||
545 | ("/_21/foobar", '21'), |
|
544 | @parametrize('test,expected', [ | |
546 |
(" |
|
545 | ("", ""), | |
547 |
("/ |
|
546 | ("/", "/"), | |
548 | ("_21/prefix/foo", '21'), |
|
547 | ("/_ID", '/_ID'), | |
|
548 | ("ID", "ID"), | |||
|
549 | ("_ID", 'NAME'), | |||
|
550 | ("_ID/", 'NAME/'), | |||
|
551 | ("_ID/1/2", 'NAME/1/2'), | |||
|
552 | ("_IDa", '_IDa'), | |||
549 | ]) |
|
553 | ]) | |
550 |
def test_ |
|
554 | def test_fix_repo_id_name(self, test, expected): | |
551 | from kallithea.lib.utils import _extract_id_from_repo_name |
|
555 | repo = Repository.get_by_repo_name(HG_REPO) | |
552 | _test = _extract_id_from_repo_name(test) |
|
556 | test = test.replace('ID', str(repo.repo_id)) | |
553 | assert _test == expected, 'url:%s, got:`%s` expected: `%s`' % (test, _test, expected) |
|
557 | expected = expected.replace('NAME', repo.repo_name).replace('ID', str(repo.repo_id)) | |
554 |
|
558 | from kallithea.lib.utils import fix_repo_id_name | ||
|
559 | replaced = fix_repo_id_name(test) | |||
|
560 | assert replaced == expected, 'url:%s, got:`%s` expected: `%s`' % (test, replaced, expected) | |||
555 |
|
561 | |||
556 | @parametrize('canonical,test,expected', [ |
|
562 | @parametrize('canonical,test,expected', [ | |
557 | ('http://www.example.org/', '/abc/xyz', 'http://www.example.org/abc/xyz'), |
|
563 | ('http://www.example.org/', '/abc/xyz', 'http://www.example.org/abc/xyz'), |
General Comments 0
You need to be logged in to leave comments.
Login now