Show More
@@ -0,0 +1,360 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | """ | |
|
3 | rhodecode.lib.compat | |
|
4 | ~~~~~~~~~~~~~~~~~~~~ | |
|
5 | ||
|
6 | Python backward compatibility functions and common libs | |
|
7 | ||
|
8 | ||
|
9 | :created_on: Oct 7, 2011 | |
|
10 | :author: marcink | |
|
11 | :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com> | |
|
12 | :license: GPLv3, see COPYING for more details. | |
|
13 | """ | |
|
14 | # This program is free software: you can redistribute it and/or modify | |
|
15 | # it under the terms of the GNU General Public License as published by | |
|
16 | # the Free Software Foundation, either version 3 of the License, or | |
|
17 | # (at your option) any later version. | |
|
18 | # | |
|
19 | # This program is distributed in the hope that it will be useful, | |
|
20 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
21 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
22 | # GNU General Public License for more details. | |
|
23 | # | |
|
24 | # You should have received a copy of the GNU General Public License | |
|
25 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
26 | ||
|
27 | #============================================================================== | |
|
28 | # json | |
|
29 | #============================================================================== | |
|
30 | try: | |
|
31 | import json | |
|
32 | except ImportError: | |
|
33 | import simplejson as json | |
|
34 | ||
|
35 | ||
|
36 | #============================================================================== | |
|
37 | # izip_longest | |
|
38 | #============================================================================== | |
|
39 | try: | |
|
40 | from itertools import izip_longest | |
|
41 | except ImportError: | |
|
42 | import itertools | |
|
43 | ||
|
44 | def izip_longest(*args, **kwds): # noqa | |
|
45 | fillvalue = kwds.get("fillvalue") | |
|
46 | ||
|
47 | def sentinel(counter=([fillvalue] * (len(args) - 1)).pop): | |
|
48 | yield counter() # yields the fillvalue, or raises IndexError | |
|
49 | ||
|
50 | fillers = itertools.repeat(fillvalue) | |
|
51 | iters = [itertools.chain(it, sentinel(), fillers) | |
|
52 | for it in args] | |
|
53 | try: | |
|
54 | for tup in itertools.izip(*iters): | |
|
55 | yield tup | |
|
56 | except IndexError: | |
|
57 | pass | |
|
58 | ||
|
59 | ||
|
60 | #============================================================================== | |
|
61 | # OrderedDict | |
|
62 | #============================================================================== | |
|
63 | ||
|
64 | # Python Software Foundation License | |
|
65 | ||
|
66 | # XXX: it feels like using the class with "is" and "is not" instead of "==" and | |
|
67 | # "!=" should be faster. | |
|
68 | class _Nil(object): | |
|
69 | ||
|
70 | def __repr__(self): | |
|
71 | return "nil" | |
|
72 | ||
|
73 | def __eq__(self, other): | |
|
74 | if (isinstance(other, _Nil)): | |
|
75 | return True | |
|
76 | else: | |
|
77 | return NotImplemented | |
|
78 | ||
|
79 | def __ne__(self, other): | |
|
80 | if (isinstance(other, _Nil)): | |
|
81 | return False | |
|
82 | else: | |
|
83 | return NotImplemented | |
|
84 | ||
|
85 | _nil = _Nil() | |
|
86 | ||
|
87 | class _odict(object): | |
|
88 | """Ordered dict data structure, with O(1) complexity for dict operations | |
|
89 | that modify one element. | |
|
90 | ||
|
91 | Overwriting values doesn't change their original sequential order. | |
|
92 | """ | |
|
93 | ||
|
94 | def _dict_impl(self): | |
|
95 | return None | |
|
96 | ||
|
97 | def __init__(self, data=(), **kwds): | |
|
98 | """This doesn't accept keyword initialization as normal dicts to avoid | |
|
99 | a trap - inside a function or method the keyword args are accessible | |
|
100 | only as a dict, without a defined order, so their original order is | |
|
101 | lost. | |
|
102 | """ | |
|
103 | if kwds: | |
|
104 | raise TypeError("__init__() of ordered dict takes no keyword " | |
|
105 | "arguments to avoid an ordering trap.") | |
|
106 | self._dict_impl().__init__(self) | |
|
107 | # If you give a normal dict, then the order of elements is undefined | |
|
108 | if hasattr(data, "iteritems"): | |
|
109 | for key, val in data.iteritems(): | |
|
110 | self[key] = val | |
|
111 | else: | |
|
112 | for key, val in data: | |
|
113 | self[key] = val | |
|
114 | ||
|
115 | # Double-linked list header | |
|
116 | def _get_lh(self): | |
|
117 | dict_impl = self._dict_impl() | |
|
118 | if not hasattr(self, '_lh'): | |
|
119 | dict_impl.__setattr__(self, '_lh', _nil) | |
|
120 | return dict_impl.__getattribute__(self, '_lh') | |
|
121 | ||
|
122 | def _set_lh(self, val): | |
|
123 | self._dict_impl().__setattr__(self, '_lh', val) | |
|
124 | ||
|
125 | lh = property(_get_lh, _set_lh) | |
|
126 | ||
|
127 | # Double-linked list tail | |
|
128 | def _get_lt(self): | |
|
129 | dict_impl = self._dict_impl() | |
|
130 | if not hasattr(self, '_lt'): | |
|
131 | dict_impl.__setattr__(self, '_lt', _nil) | |
|
132 | return dict_impl.__getattribute__(self, '_lt') | |
|
133 | ||
|
134 | def _set_lt(self, val): | |
|
135 | self._dict_impl().__setattr__(self, '_lt', val) | |
|
136 | ||
|
137 | lt = property(_get_lt, _set_lt) | |
|
138 | ||
|
139 | def __getitem__(self, key): | |
|
140 | return self._dict_impl().__getitem__(self, key)[1] | |
|
141 | ||
|
142 | def __setitem__(self, key, val): | |
|
143 | dict_impl = self._dict_impl() | |
|
144 | try: | |
|
145 | dict_impl.__getitem__(self, key)[1] = val | |
|
146 | except KeyError, e: | |
|
147 | new = [dict_impl.__getattribute__(self, 'lt'), val, _nil] | |
|
148 | dict_impl.__setitem__(self, key, new) | |
|
149 | if dict_impl.__getattribute__(self, 'lt') == _nil: | |
|
150 | dict_impl.__setattr__(self, 'lh', key) | |
|
151 | else: | |
|
152 | dict_impl.__getitem__( | |
|
153 | self, dict_impl.__getattribute__(self, 'lt'))[2] = key | |
|
154 | dict_impl.__setattr__(self, 'lt', key) | |
|
155 | ||
|
156 | def __delitem__(self, key): | |
|
157 | dict_impl = self._dict_impl() | |
|
158 | pred, _ , succ = self._dict_impl().__getitem__(self, key) | |
|
159 | if pred == _nil: | |
|
160 | dict_impl.__setattr__(self, 'lh', succ) | |
|
161 | else: | |
|
162 | dict_impl.__getitem__(self, pred)[2] = succ | |
|
163 | if succ == _nil: | |
|
164 | dict_impl.__setattr__(self, 'lt', pred) | |
|
165 | else: | |
|
166 | dict_impl.__getitem__(self, succ)[0] = pred | |
|
167 | dict_impl.__delitem__(self, key) | |
|
168 | ||
|
169 | def __contains__(self, key): | |
|
170 | return key in self.keys() | |
|
171 | ||
|
172 | def __len__(self): | |
|
173 | return len(self.keys()) | |
|
174 | ||
|
175 | def __str__(self): | |
|
176 | pairs = ("%r: %r" % (k, v) for k, v in self.iteritems()) | |
|
177 | return "{%s}" % ", ".join(pairs) | |
|
178 | ||
|
179 | def __repr__(self): | |
|
180 | if self: | |
|
181 | pairs = ("(%r, %r)" % (k, v) for k, v in self.iteritems()) | |
|
182 | return "odict([%s])" % ", ".join(pairs) | |
|
183 | else: | |
|
184 | return "odict()" | |
|
185 | ||
|
186 | def get(self, k, x=None): | |
|
187 | if k in self: | |
|
188 | return self._dict_impl().__getitem__(self, k)[1] | |
|
189 | else: | |
|
190 | return x | |
|
191 | ||
|
192 | def __iter__(self): | |
|
193 | dict_impl = self._dict_impl() | |
|
194 | curr_key = dict_impl.__getattribute__(self, 'lh') | |
|
195 | while curr_key != _nil: | |
|
196 | yield curr_key | |
|
197 | curr_key = dict_impl.__getitem__(self, curr_key)[2] | |
|
198 | ||
|
199 | iterkeys = __iter__ | |
|
200 | ||
|
201 | def keys(self): | |
|
202 | return list(self.iterkeys()) | |
|
203 | ||
|
204 | def itervalues(self): | |
|
205 | dict_impl = self._dict_impl() | |
|
206 | curr_key = dict_impl.__getattribute__(self, 'lh') | |
|
207 | while curr_key != _nil: | |
|
208 | _, val, curr_key = dict_impl.__getitem__(self, curr_key) | |
|
209 | yield val | |
|
210 | ||
|
211 | def values(self): | |
|
212 | return list(self.itervalues()) | |
|
213 | ||
|
214 | def iteritems(self): | |
|
215 | dict_impl = self._dict_impl() | |
|
216 | curr_key = dict_impl.__getattribute__(self, 'lh') | |
|
217 | while curr_key != _nil: | |
|
218 | _, val, next_key = dict_impl.__getitem__(self, curr_key) | |
|
219 | yield curr_key, val | |
|
220 | curr_key = next_key | |
|
221 | ||
|
222 | def items(self): | |
|
223 | return list(self.iteritems()) | |
|
224 | ||
|
225 | def sort(self, cmp=None, key=None, reverse=False): | |
|
226 | items = [(k, v) for k, v in self.items()] | |
|
227 | if cmp is not None: | |
|
228 | items = sorted(items, cmp=cmp) | |
|
229 | elif key is not None: | |
|
230 | items = sorted(items, key=key) | |
|
231 | else: | |
|
232 | items = sorted(items, key=lambda x: x[1]) | |
|
233 | if reverse: | |
|
234 | items.reverse() | |
|
235 | self.clear() | |
|
236 | self.__init__(items) | |
|
237 | ||
|
238 | def clear(self): | |
|
239 | dict_impl = self._dict_impl() | |
|
240 | dict_impl.clear(self) | |
|
241 | dict_impl.__setattr__(self, 'lh', _nil) | |
|
242 | dict_impl.__setattr__(self, 'lt', _nil) | |
|
243 | ||
|
244 | def copy(self): | |
|
245 | return self.__class__(self) | |
|
246 | ||
|
247 | def update(self, data=(), **kwds): | |
|
248 | if kwds: | |
|
249 | raise TypeError("update() of ordered dict takes no keyword " | |
|
250 | "arguments to avoid an ordering trap.") | |
|
251 | if hasattr(data, "iteritems"): | |
|
252 | data = data.iteritems() | |
|
253 | for key, val in data: | |
|
254 | self[key] = val | |
|
255 | ||
|
256 | def setdefault(self, k, x=None): | |
|
257 | try: | |
|
258 | return self[k] | |
|
259 | except KeyError: | |
|
260 | self[k] = x | |
|
261 | return x | |
|
262 | ||
|
263 | def pop(self, k, x=_nil): | |
|
264 | try: | |
|
265 | val = self[k] | |
|
266 | del self[k] | |
|
267 | return val | |
|
268 | except KeyError: | |
|
269 | if x == _nil: | |
|
270 | raise | |
|
271 | return x | |
|
272 | ||
|
273 | def popitem(self): | |
|
274 | try: | |
|
275 | dict_impl = self._dict_impl() | |
|
276 | key = dict_impl.__getattribute__(self, 'lt') | |
|
277 | return key, self.pop(key) | |
|
278 | except KeyError: | |
|
279 | raise KeyError("'popitem(): ordered dictionary is empty'") | |
|
280 | ||
|
281 | def riterkeys(self): | |
|
282 | """To iterate on keys in reversed order. | |
|
283 | """ | |
|
284 | dict_impl = self._dict_impl() | |
|
285 | curr_key = dict_impl.__getattribute__(self, 'lt') | |
|
286 | while curr_key != _nil: | |
|
287 | yield curr_key | |
|
288 | curr_key = dict_impl.__getitem__(self, curr_key)[0] | |
|
289 | ||
|
290 | __reversed__ = riterkeys | |
|
291 | ||
|
292 | def rkeys(self): | |
|
293 | """List of the keys in reversed order. | |
|
294 | """ | |
|
295 | return list(self.riterkeys()) | |
|
296 | ||
|
297 | def ritervalues(self): | |
|
298 | """To iterate on values in reversed order. | |
|
299 | """ | |
|
300 | dict_impl = self._dict_impl() | |
|
301 | curr_key = dict_impl.__getattribute__(self, 'lt') | |
|
302 | while curr_key != _nil: | |
|
303 | curr_key, val, _ = dict_impl.__getitem__(self, curr_key) | |
|
304 | yield val | |
|
305 | ||
|
306 | def rvalues(self): | |
|
307 | """List of the values in reversed order. | |
|
308 | """ | |
|
309 | return list(self.ritervalues()) | |
|
310 | ||
|
311 | def riteritems(self): | |
|
312 | """To iterate on (key, value) in reversed order. | |
|
313 | """ | |
|
314 | dict_impl = self._dict_impl() | |
|
315 | curr_key = dict_impl.__getattribute__(self, 'lt') | |
|
316 | while curr_key != _nil: | |
|
317 | pred_key, val, _ = dict_impl.__getitem__(self, curr_key) | |
|
318 | yield curr_key, val | |
|
319 | curr_key = pred_key | |
|
320 | ||
|
321 | def ritems(self): | |
|
322 | """List of the (key, value) in reversed order. | |
|
323 | """ | |
|
324 | return list(self.riteritems()) | |
|
325 | ||
|
326 | def firstkey(self): | |
|
327 | if self: | |
|
328 | return self._dict_impl().__getattribute__(self, 'lh') | |
|
329 | else: | |
|
330 | raise KeyError("'firstkey(): ordered dictionary is empty'") | |
|
331 | ||
|
332 | def lastkey(self): | |
|
333 | if self: | |
|
334 | return self._dict_impl().__getattribute__(self, 'lt') | |
|
335 | else: | |
|
336 | raise KeyError("'lastkey(): ordered dictionary is empty'") | |
|
337 | ||
|
338 | def as_dict(self): | |
|
339 | return self._dict_impl()(self.items()) | |
|
340 | ||
|
341 | def _repr(self): | |
|
342 | """_repr(): low level repr of the whole data contained in the odict. | |
|
343 | Useful for debugging. | |
|
344 | """ | |
|
345 | dict_impl = self._dict_impl() | |
|
346 | form = "odict low level repr lh,lt,data: %r, %r, %s" | |
|
347 | return form % (dict_impl.__getattribute__(self, 'lh'), | |
|
348 | dict_impl.__getattribute__(self, 'lt'), | |
|
349 | dict_impl.__repr__(self)) | |
|
350 | ||
|
351 | class OrderedDict(_odict, dict): | |
|
352 | ||
|
353 | def _dict_impl(self): | |
|
354 | return dict | |
|
355 | ||
|
356 | ||
|
357 | #============================================================================== | |
|
358 | # OrderedSet | |
|
359 | #============================================================================== | |
|
360 | from sqlalchemy.util import OrderedSet |
@@ -0,0 +1,189 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | """ | |
|
3 | rhodecode.tests.test_hg_operations | |
|
4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
|
5 | ||
|
6 | Test suite for making push/pull operations | |
|
7 | ||
|
8 | :created_on: Dec 30, 2010 | |
|
9 | :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com> | |
|
10 | :license: GPLv3, see COPYING for more details. | |
|
11 | """ | |
|
12 | # This program is free software: you can redistribute it and/or modify | |
|
13 | # it under the terms of the GNU General Public License as published by | |
|
14 | # the Free Software Foundation, either version 3 of the License, or | |
|
15 | # (at your option) any later version. | |
|
16 | # | |
|
17 | # This program is distributed in the hope that it will be useful, | |
|
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
20 | # GNU General Public License for more details. | |
|
21 | # | |
|
22 | # You should have received a copy of the GNU General Public License | |
|
23 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
24 | ||
|
25 | import os | |
|
26 | import sys | |
|
27 | import shutil | |
|
28 | import logging | |
|
29 | from os.path import join as jn | |
|
30 | from os.path import dirname as dn | |
|
31 | ||
|
32 | from tempfile import _RandomNameSequence | |
|
33 | from subprocess import Popen, PIPE | |
|
34 | ||
|
35 | from paste.deploy import appconfig | |
|
36 | from pylons import config | |
|
37 | from sqlalchemy import engine_from_config | |
|
38 | ||
|
39 | from rhodecode.lib.utils import add_cache | |
|
40 | from rhodecode.model import init_model | |
|
41 | from rhodecode.model import meta | |
|
42 | from rhodecode.model.db import User, Repository | |
|
43 | from rhodecode.lib.auth import get_crypt_password | |
|
44 | ||
|
45 | from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO | |
|
46 | from rhodecode.config.environment import load_environment | |
|
47 | ||
|
48 | rel_path = dn(dn(dn(os.path.abspath(__file__)))) | |
|
49 | conf = appconfig('config:development.ini', relative_to=rel_path) | |
|
50 | load_environment(conf.global_conf, conf.local_conf) | |
|
51 | ||
|
52 | add_cache(conf) | |
|
53 | ||
|
54 | USER = 'test_admin' | |
|
55 | PASS = 'test12' | |
|
56 | HOST = '127.0.0.1:5000' | |
|
57 | DEBUG = True | |
|
58 | log = logging.getLogger(__name__) | |
|
59 | ||
|
60 | ||
|
61 | class Command(object): | |
|
62 | ||
|
63 | def __init__(self, cwd): | |
|
64 | self.cwd = cwd | |
|
65 | ||
|
66 | def execute(self, cmd, *args): | |
|
67 | """Runs command on the system with given ``args``. | |
|
68 | """ | |
|
69 | ||
|
70 | command = cmd + ' ' + ' '.join(args) | |
|
71 | log.debug('Executing %s' % command) | |
|
72 | if DEBUG: | |
|
73 | print command | |
|
74 | p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd) | |
|
75 | stdout, stderr = p.communicate() | |
|
76 | if DEBUG: | |
|
77 | print stdout, stderr | |
|
78 | return stdout, stderr | |
|
79 | ||
|
80 | def get_session(): | |
|
81 | engine = engine_from_config(conf, 'sqlalchemy.db1.') | |
|
82 | init_model(engine) | |
|
83 | sa = meta.Session() | |
|
84 | return sa | |
|
85 | ||
|
86 | ||
|
87 | def create_test_user(force=True): | |
|
88 | print 'creating test user' | |
|
89 | sa = get_session() | |
|
90 | ||
|
91 | user = sa.query(User).filter(User.username == USER).scalar() | |
|
92 | ||
|
93 | if force and user is not None: | |
|
94 | print 'removing current user' | |
|
95 | for repo in sa.query(Repository).filter(Repository.user == user).all(): | |
|
96 | sa.delete(repo) | |
|
97 | sa.delete(user) | |
|
98 | sa.commit() | |
|
99 | ||
|
100 | if user is None or force: | |
|
101 | print 'creating new one' | |
|
102 | new_usr = User() | |
|
103 | new_usr.username = USER | |
|
104 | new_usr.password = get_crypt_password(PASS) | |
|
105 | new_usr.email = 'mail@mail.com' | |
|
106 | new_usr.name = 'test' | |
|
107 | new_usr.lastname = 'lasttestname' | |
|
108 | new_usr.active = True | |
|
109 | new_usr.admin = True | |
|
110 | sa.add(new_usr) | |
|
111 | sa.commit() | |
|
112 | ||
|
113 | print 'done' | |
|
114 | ||
|
115 | ||
|
116 | def create_test_repo(force=True): | |
|
117 | print 'creating test repo' | |
|
118 | from rhodecode.model.repo import RepoModel | |
|
119 | sa = get_session() | |
|
120 | ||
|
121 | user = sa.query(User).filter(User.username == USER).scalar() | |
|
122 | if user is None: | |
|
123 | raise Exception('user not found') | |
|
124 | ||
|
125 | ||
|
126 | repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar() | |
|
127 | ||
|
128 | if repo is None: | |
|
129 | print 'repo not found creating' | |
|
130 | ||
|
131 | form_data = {'repo_name':HG_REPO, | |
|
132 | 'repo_type':'hg', | |
|
133 | 'private':False, | |
|
134 | 'clone_uri':'' } | |
|
135 | rm = RepoModel(sa) | |
|
136 | rm.base_path = '/home/hg' | |
|
137 | rm.create(form_data, user) | |
|
138 | ||
|
139 | print 'done' | |
|
140 | ||
|
141 | def set_anonymous_access(enable=True): | |
|
142 | sa = get_session() | |
|
143 | user = sa.query(User).filter(User.username == 'default').one() | |
|
144 | user.active = enable | |
|
145 | sa.add(user) | |
|
146 | sa.commit() | |
|
147 | ||
|
148 | def get_anonymous_access(): | |
|
149 | sa = get_session() | |
|
150 | return sa.query(User).filter(User.username == 'default').one().active | |
|
151 | ||
|
152 | ||
|
153 | #============================================================================== | |
|
154 | # TESTS | |
|
155 | #============================================================================== | |
|
156 | def test_clone_with_credentials(no_errors=False, repo=HG_REPO): | |
|
157 | cwd = path = jn(TESTS_TMP_PATH, repo) | |
|
158 | ||
|
159 | ||
|
160 | try: | |
|
161 | shutil.rmtree(path, ignore_errors=True) | |
|
162 | os.makedirs(path) | |
|
163 | #print 'made dirs %s' % jn(path) | |
|
164 | except OSError: | |
|
165 | raise | |
|
166 | ||
|
167 | ||
|
168 | clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \ | |
|
169 | {'user':USER, | |
|
170 | 'pass':PASS, | |
|
171 | 'host':HOST, | |
|
172 | 'cloned_repo':repo, | |
|
173 | 'dest':path + _RandomNameSequence().next()} | |
|
174 | ||
|
175 | stdout, stderr = Command(cwd).execute('hg clone', clone_url) | |
|
176 | ||
|
177 | if no_errors is False: | |
|
178 | assert """adding file changes""" in stdout, 'no messages about cloning' | |
|
179 | assert """abort""" not in stderr , 'got error from clone' | |
|
180 | ||
|
181 | if __name__ == '__main__': | |
|
182 | try: | |
|
183 | create_test_user(force=False) | |
|
184 | ||
|
185 | for i in range(int(sys.argv[2])): | |
|
186 | test_clone_with_credentials(repo=sys.argv[1]) | |
|
187 | ||
|
188 | except Exception, e: | |
|
189 | sys.exit('stop on %s' % e) |
@@ -2,10 +2,11 b'' | |||
|
2 | 2 | Welcome to RhodeCode (RhodiumCode) documentation! |
|
3 | 3 | ================================================= |
|
4 | 4 | |
|
5 |
``RhodeCode`` |
|
|
5 | ``RhodeCode`` is a Pylons framework based Mercurial repository | |
|
6 | 6 | browser/management tool with a built in push/pull server and full text search. |
|
7 | 7 | It works on http/https and has a built in permission/authentication system with |
|
8 | the ability to authenticate via LDAP. | |
|
8 | the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also supports | |
|
9 | simple API so it's easy integrable with existing systems. | |
|
9 | 10 | |
|
10 | 11 | RhodeCode is similar in some respects to github or bitbucket_, |
|
11 | 12 | however RhodeCode can be run as standalone hosted application on your own server. |
@@ -6,11 +6,12 b' API' | |||
|
6 | 6 | |
|
7 | 7 | |
|
8 | 8 | Starting from RhodeCode version 1.2 a simple API was implemented. |
|
9 |
There's |
|
|
10 | with JSON protocol both ways. | |
|
9 | There's a single schema for calling all api methods. API is implemented | |
|
10 | with JSON protocol both ways. An url to send API request in RhodeCode is | |
|
11 | <your_server>/_admin/api | |
|
11 | 12 | |
|
12 | 13 | |
|
13 |
|
|
|
14 | All clients need to send JSON data in such format:: | |
|
14 | 15 | |
|
15 | 16 | { |
|
16 | 17 | "api_key":"<api_key>", |
@@ -18,16 +19,20 b' Clients need to send JSON data in such f' | |||
|
18 | 19 | "args":{"<arg_key>":"<arg_val>"} |
|
19 | 20 | } |
|
20 | 21 | |
|
21 | Simply provide api_key for access and permission validation | |
|
22 | method is name of method to call | |
|
23 | and args is an key:value list of arguments to pass to method | |
|
22 | Example call for autopulling remotes repos using curl:: | |
|
23 | curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}' | |
|
24 | ||
|
25 | Simply provide | |
|
26 | - *api_key* for access and permission validation. | |
|
27 | - *method* is name of method to call | |
|
28 | - *args* is an key:value list of arguments to pass to method | |
|
24 | 29 | |
|
25 | 30 | .. note:: |
|
26 | 31 | |
|
27 | 32 | api_key can be found in your user account page |
|
28 | 33 | |
|
29 | 34 | |
|
30 |
|
|
|
35 | RhodeCode API will return always a JSON formatted answer:: | |
|
31 | 36 | |
|
32 | 37 | { |
|
33 | 38 | "result": "<result>", |
@@ -35,7 +40,7 b' And will receive JSON formatted answer::' | |||
|
35 | 40 | } |
|
36 | 41 | |
|
37 | 42 | All responses from API will be `HTTP/1.0 200 OK`, if there's an error while |
|
38 |
calling api |
|
|
43 | calling api *error* key from response will contain failure description | |
|
39 | 44 | and result will be null. |
|
40 | 45 | |
|
41 | 46 | API METHODS |
@@ -46,11 +51,61 b' pull' | |||
|
46 | 51 | ---- |
|
47 | 52 | |
|
48 | 53 | Pulls given repo from remote location. Can be used to automatically keep |
|
49 |
remote repos upto date. This command can be executed only using a |
|
|
50 | api_key | |
|
54 | remote repos up to date. This command can be executed only using api_key | |
|
55 | belonging to user with admin rights | |
|
51 | 56 | |
|
52 | :: | |
|
53 | ||
|
57 | INPUT:: | |
|
58 | ||
|
59 | api_key:"<api_key>" | |
|
54 | 60 | method: "pull" |
|
55 | 61 |
|
|
56 | 62 | |
|
63 | OUTPUT:: | |
|
64 | ||
|
65 | result:"Pulled from <repo_name>" | |
|
66 | error:null | |
|
67 | ||
|
68 | ||
|
69 | create_user | |
|
70 | ----------- | |
|
71 | ||
|
72 | Creates new user in RhodeCode. This command can be executed only using api_key | |
|
73 | belonging to user with admin rights | |
|
74 | ||
|
75 | INPUT:: | |
|
76 | ||
|
77 | api_key:"<api_key>" | |
|
78 | method: "create_user" | |
|
79 | args: {"username": "<username>", | |
|
80 | "password": "<password>", | |
|
81 | "active": "<bool>", | |
|
82 | "admin": "<bool>", | |
|
83 | "name": "<firstname>", | |
|
84 | "lastname": "<lastname>", | |
|
85 | "email": "<useremail>"} | |
|
86 | ||
|
87 | OUTPUT:: | |
|
88 | ||
|
89 | result:{"id": <newuserid>, | |
|
90 | "msg":"created new user <username>"} | |
|
91 | error:null | |
|
92 | ||
|
93 | ||
|
94 | create_users_group | |
|
95 | ------------------ | |
|
96 | ||
|
97 | creates new users group. This command can be executed only using api_key | |
|
98 | belonging to user with admin rights | |
|
99 | ||
|
100 | INPUT:: | |
|
101 | ||
|
102 | api_key:"<api_key>" | |
|
103 | method: "create_user" | |
|
104 | args: {"name": "<groupname>", | |
|
105 | "active":"<bool>"} | |
|
106 | ||
|
107 | OUTPUT:: | |
|
108 | ||
|
109 | result:{"id": <newusersgroupid>, | |
|
110 | "msg":"created new users group <groupname>"} | |
|
111 | error:null |
@@ -3,7 +3,8 b'' | |||
|
3 | 3 | Changelog |
|
4 | 4 | ========= |
|
5 | 5 | |
|
6 | 1.2.0 (**2011-XX-XX**) | |
|
6 | ||
|
7 | 1.3.0 (**XXXX-XX-XX**) | |
|
7 | 8 | ====================== |
|
8 | 9 | |
|
9 | 10 | :status: in-progress |
@@ -12,6 +13,29 b' 1.2.0 (**2011-XX-XX**)' | |||
|
12 | 13 | news |
|
13 | 14 | ---- |
|
14 | 15 | |
|
16 | fixes | |
|
17 | ----- | |
|
18 | ||
|
19 | 1.2.1 (**2011-10-08**) | |
|
20 | ====================== | |
|
21 | ||
|
22 | news | |
|
23 | ---- | |
|
24 | ||
|
25 | ||
|
26 | fixes | |
|
27 | ----- | |
|
28 | ||
|
29 | - fixed problems with basic auth and push problems | |
|
30 | - gui fixes | |
|
31 | - fixed logger | |
|
32 | ||
|
33 | 1.2.0 (**2011-10-07**) | |
|
34 | ====================== | |
|
35 | ||
|
36 | news | |
|
37 | ---- | |
|
38 | ||
|
15 | 39 | - implemented #47 repository groups |
|
16 | 40 | - implemented #89 Can setup google analytics code from settings menu |
|
17 | 41 | - implemented #91 added nicer looking archive urls with more download options |
|
1 | NO CONTENT: modified file, binary diff hidden |
|
1 | NO CONTENT: modified file, binary diff hidden |
|
1 | NO CONTENT: modified file, binary diff hidden |
@@ -25,9 +25,9 b'' | |||
|
25 | 25 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
26 | 26 | import platform |
|
27 | 27 | |
|
28 |
VERSION = (1, |
|
|
28 | VERSION = (1, 3, 0, 'beta') | |
|
29 | 29 | __version__ = '.'.join((str(each) for each in VERSION[:4])) |
|
30 |
__dbversion__ = |
|
|
30 | __dbversion__ = 4 #defines current db version for migrations | |
|
31 | 31 | __platform__ = platform.system() |
|
32 | 32 | __license__ = 'GPLv3' |
|
33 | 33 |
@@ -51,6 +51,9 b' def make_app(global_conf, full_stack=Tru' | |||
|
51 | 51 | from rhodecode.lib.profiler import ProfilingMiddleware |
|
52 | 52 | app = ProfilingMiddleware(app) |
|
53 | 53 | |
|
54 | ||
|
55 | # we want our low level middleware to get to the request ASAP. We don't | |
|
56 | # need any pylons stack middleware in them | |
|
54 | 57 | app = SimpleHg(app, config) |
|
55 | 58 | app = SimpleGit(app, config) |
|
56 | 59 | |
@@ -77,6 +80,7 b' def make_app(global_conf, full_stack=Tru' | |||
|
77 | 80 | app = Cascade([static_app, app]) |
|
78 | 81 | app = make_gzip_middleware(app, global_conf, compress_level=1) |
|
79 | 82 | |
|
83 | ||
|
80 | 84 | app.config = config |
|
81 | 85 | |
|
82 | 86 | return app |
@@ -7,7 +7,7 b' refer to the routes manual at http://rou' | |||
|
7 | 7 | """ |
|
8 | 8 | from __future__ import with_statement |
|
9 | 9 | from routes import Mapper |
|
10 | from rhodecode.lib.utils import check_repo_fast as cr | |
|
10 | ||
|
11 | 11 | |
|
12 | 12 | # prefix for non repository related links needs to be prefixed with `/` |
|
13 | 13 | ADMIN_PREFIX = '/_admin' |
@@ -19,23 +19,36 b' def make_map(config):' | |||
|
19 | 19 | always_scan=config['debug']) |
|
20 | 20 | rmap.minimization = False |
|
21 | 21 | rmap.explicit = False |
|
22 | ||
|
22 | ||
|
23 | from rhodecode.lib.utils import is_valid_repo | |
|
24 | from rhodecode.lib.utils import is_valid_repos_group | |
|
25 | ||
|
23 | 26 | def check_repo(environ, match_dict): |
|
24 | 27 | """ |
|
25 | 28 | check for valid repository for proper 404 handling |
|
29 | ||
|
26 | 30 | :param environ: |
|
27 | 31 | :param match_dict: |
|
28 | 32 | """ |
|
33 | ||
|
29 | 34 | repo_name = match_dict.get('repo_name') |
|
30 |
return |
|
|
35 | return is_valid_repo(repo_name, config['base_path']) | |
|
36 | ||
|
37 | def check_group(environ, match_dict): | |
|
38 | """ | |
|
39 | check for valid repositories group for proper 404 handling | |
|
40 | ||
|
41 | :param environ: | |
|
42 | :param match_dict: | |
|
43 | """ | |
|
44 | repos_group_name = match_dict.get('group_name') | |
|
45 | ||
|
46 | return is_valid_repos_group(repos_group_name, config['base_path']) | |
|
31 | 47 | |
|
32 | 48 | |
|
33 | 49 | def check_int(environ, match_dict): |
|
34 | 50 | return match_dict.get('id').isdigit() |
|
35 | 51 | |
|
36 | ||
|
37 | ||
|
38 | ||
|
39 | 52 | # The ErrorController route (handles 404/500 error pages); it should |
|
40 | 53 | # likely stay at the top, ensuring it can always be resolved |
|
41 | 54 | rmap.connect('/error/{action}', controller='error') |
@@ -319,6 +332,14 b' def make_map(config):' | |||
|
319 | 332 | #========================================================================== |
|
320 | 333 | # REPOSITORY ROUTES |
|
321 | 334 | #========================================================================== |
|
335 | rmap.connect('summary_home', '/{repo_name:.*}', | |
|
336 | controller='summary', | |
|
337 | conditions=dict(function=check_repo)) | |
|
338 | ||
|
339 | # rmap.connect('repo_group_home', '/{group_name:.*}', | |
|
340 | # controller='admin/repos_groups',action="show_by_name", | |
|
341 | # conditions=dict(function=check_group)) | |
|
342 | ||
|
322 | 343 | rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}', |
|
323 | 344 | controller='changeset', revision='tip', |
|
324 | 345 | conditions=dict(function=check_repo)) |
@@ -328,9 +349,6 b' def make_map(config):' | |||
|
328 | 349 | controller='changeset', action='raw_changeset', |
|
329 | 350 | revision='tip', conditions=dict(function=check_repo)) |
|
330 | 351 | |
|
331 | rmap.connect('summary_home', '/{repo_name:.*}', | |
|
332 | controller='summary', conditions=dict(function=check_repo)) | |
|
333 | ||
|
334 | 352 | rmap.connect('summary_home', '/{repo_name:.*}/summary', |
|
335 | 353 | controller='summary', conditions=dict(function=check_repo)) |
|
336 | 354 |
@@ -23,19 +23,21 b'' | |||
|
23 | 23 | # You should have received a copy of the GNU General Public License |
|
24 | 24 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
25 | 25 | |
|
26 | import logging | |
|
27 | import traceback | |
|
28 | import formencode | |
|
26 | 29 | from formencode import htmlfill |
|
30 | ||
|
27 | 31 | from pylons import request, session, tmpl_context as c, url |
|
28 | 32 | from pylons.controllers.util import abort, redirect |
|
29 | 33 | from pylons.i18n.translation import _ |
|
34 | ||
|
30 | 35 | from rhodecode.lib import helpers as h |
|
31 | 36 | from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator |
|
32 | 37 | from rhodecode.lib.base import BaseController, render |
|
33 |
from rhodecode.model.forms import |
|
|
38 | from rhodecode.model.forms import DefaultPermissionsForm | |
|
34 | 39 | from rhodecode.model.permission import PermissionModel |
|
35 |
from rhodecode.model. |
|
|
36 | import formencode | |
|
37 | import logging | |
|
38 | import traceback | |
|
40 | from rhodecode.model.db import User | |
|
39 | 41 | |
|
40 | 42 | log = logging.getLogger(__name__) |
|
41 | 43 | |
@@ -142,7 +144,7 b' class PermissionsController(BaseControll' | |||
|
142 | 144 | c.create_choices = self.create_choices |
|
143 | 145 | |
|
144 | 146 | if id == 'default': |
|
145 |
default_user = User |
|
|
147 | default_user = User.get_by_username('default') | |
|
146 | 148 | defaults = {'_method': 'put', |
|
147 | 149 | 'anonymous': default_user.active} |
|
148 | 150 |
@@ -89,7 +89,7 b' class ReposController(BaseController):' | |||
|
89 | 89 | """ |
|
90 | 90 | self.__load_defaults() |
|
91 | 91 | |
|
92 | c.repo_info = db_repo = Repository.by_repo_name(repo_name) | |
|
92 | c.repo_info = db_repo = Repository.get_by_repo_name(repo_name) | |
|
93 | 93 | repo = scm_repo = db_repo.scm_instance |
|
94 | 94 | |
|
95 | 95 | if c.repo_info is None: |
@@ -101,7 +101,7 b' class ReposController(BaseController):' | |||
|
101 | 101 | |
|
102 | 102 | return redirect(url('repos')) |
|
103 | 103 | |
|
104 | c.default_user_id = User.by_username('default').user_id | |
|
104 | c.default_user_id = User.get_by_username('default').user_id | |
|
105 | 105 | c.in_public_journal = self.sa.query(UserFollowing)\ |
|
106 | 106 | .filter(UserFollowing.user_id == c.default_user_id)\ |
|
107 | 107 | .filter(UserFollowing.follows_repository == c.repo_info).scalar() |
@@ -381,8 +381,8 b' class ReposController(BaseController):' | |||
|
381 | 381 | token = get_token() |
|
382 | 382 | if cur_token == token: |
|
383 | 383 | try: |
|
384 | repo_id = Repository.by_repo_name(repo_name).repo_id | |
|
385 | user_id = User.by_username('default').user_id | |
|
384 | repo_id = Repository.get_by_repo_name(repo_name).repo_id | |
|
385 | user_id = User.get_by_username('default').user_id | |
|
386 | 386 | self.scm_model.toggle_following_repo(repo_id, user_id) |
|
387 | 387 | h.flash(_('Updated repository visibility in public journal'), |
|
388 | 388 | category='success') |
@@ -46,6 +46,7 b' from rhodecode.model.forms import UserFo' | |||
|
46 | 46 | ApplicationUiSettingsForm |
|
47 | 47 | from rhodecode.model.scm import ScmModel |
|
48 | 48 | from rhodecode.model.user import UserModel |
|
49 | from rhodecode.model.db import User | |
|
49 | 50 | |
|
50 | 51 | log = logging.getLogger(__name__) |
|
51 | 52 | |
@@ -299,7 +300,7 b' class SettingsController(BaseController)' | |||
|
299 | 300 | """ |
|
300 | 301 | # url('admin_settings_my_account') |
|
301 | 302 | |
|
302 |
c.user = User |
|
|
303 | c.user = User.get(self.rhodecode_user.user_id) | |
|
303 | 304 | all_repos = self.sa.query(Repository)\ |
|
304 | 305 | .filter(Repository.user_id == c.user.user_id)\ |
|
305 | 306 | .order_by(func.lower(Repository.repo_name)).all() |
@@ -340,8 +341,7 b' class SettingsController(BaseController)' | |||
|
340 | 341 | category='success') |
|
341 | 342 | |
|
342 | 343 | except formencode.Invalid, errors: |
|
343 |
c.user = |
|
|
344 | c.user = UserModel().get(self.rhodecode_user.user_id, cache=False) | |
|
344 | c.user = User.get(self.rhodecode_user.user_id) | |
|
345 | 345 | all_repos = self.sa.query(Repository)\ |
|
346 | 346 | .filter(Repository.user_id == c.user.user_id)\ |
|
347 | 347 | .order_by(func.lower(Repository.repo_name))\ |
@@ -26,10 +26,12 b'' | |||
|
26 | 26 | # MA 02110-1301, USA. |
|
27 | 27 | |
|
28 | 28 | import inspect |
|
29 | import json | |
|
30 | 29 | import logging |
|
31 | 30 | import types |
|
32 | 31 | import urllib |
|
32 | import traceback | |
|
33 | ||
|
34 | from rhodecode.lib.compat import izip_longest, json | |
|
33 | 35 | |
|
34 | 36 | from paste.response import replace_header |
|
35 | 37 | |
@@ -39,7 +41,7 b' from pylons.controllers.util import Resp' | |||
|
39 | 41 | from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \ |
|
40 | 42 | HTTPBadRequest, HTTPError |
|
41 | 43 | |
|
42 |
from rhodecode.model. |
|
|
44 | from rhodecode.model.db import User | |
|
43 | 45 | from rhodecode.lib.auth import AuthUser |
|
44 | 46 | |
|
45 | 47 | log = logging.getLogger('JSONRPC') |
@@ -85,10 +87,9 b' class JSONRPCController(WSGIController):' | |||
|
85 | 87 | Parse the request body as JSON, look up the method on the |
|
86 | 88 | controller and if it exists, dispatch to it. |
|
87 | 89 | """ |
|
88 | ||
|
89 | 90 | if 'CONTENT_LENGTH' not in environ: |
|
90 | 91 | log.debug("No Content-Length") |
|
91 |
return jsonrpc_error( |
|
|
92 | return jsonrpc_error(message="No Content-Length in request") | |
|
92 | 93 | else: |
|
93 | 94 | length = environ['CONTENT_LENGTH'] or 0 |
|
94 | 95 | length = int(environ['CONTENT_LENGTH']) |
@@ -96,20 +97,18 b' class JSONRPCController(WSGIController):' | |||
|
96 | 97 | |
|
97 | 98 | if length == 0: |
|
98 | 99 | log.debug("Content-Length is 0") |
|
99 |
return jsonrpc_error( |
|
|
100 | return jsonrpc_error(message="Content-Length is 0") | |
|
100 | 101 | |
|
101 | 102 | raw_body = environ['wsgi.input'].read(length) |
|
102 | 103 | |
|
103 | 104 | try: |
|
104 | 105 | json_body = json.loads(urllib.unquote_plus(raw_body)) |
|
105 |
except ValueError |
|
|
106 | except ValueError, e: | |
|
106 | 107 | #catch JSON errors Here |
|
107 | return jsonrpc_error("JSON parse error ERR:%s RAW:%r" \ | |
|
108 | return jsonrpc_error(message="JSON parse error ERR:%s RAW:%r" \ | |
|
108 | 109 | % (e, urllib.unquote_plus(raw_body))) |
|
109 | 110 | |
|
110 | ||
|
111 | 111 | #check AUTH based on API KEY |
|
112 | ||
|
113 | 112 | try: |
|
114 | 113 | self._req_api_key = json_body['api_key'] |
|
115 | 114 | self._req_method = json_body['method'] |
@@ -117,47 +116,61 b' class JSONRPCController(WSGIController):' | |||
|
117 | 116 | log.debug('method: %s, params: %s', |
|
118 | 117 | self._req_method, |
|
119 | 118 | self._req_params) |
|
120 |
except KeyError |
|
|
119 | except KeyError, e: | |
|
121 | 120 | return jsonrpc_error(message='Incorrect JSON query missing %s' % e) |
|
122 | 121 | |
|
123 | 122 | #check if we can find this session using api_key |
|
124 | 123 | try: |
|
125 | 124 | u = User.get_by_api_key(self._req_api_key) |
|
126 | 125 | auth_u = AuthUser(u.user_id, self._req_api_key) |
|
127 |
except Exception |
|
|
126 | except Exception, e: | |
|
128 | 127 | return jsonrpc_error(message='Invalid API KEY') |
|
129 | 128 | |
|
130 | 129 | self._error = None |
|
131 | 130 | try: |
|
132 | 131 | self._func = self._find_method() |
|
133 | 132 | except AttributeError, e: |
|
134 | return jsonrpc_error(str(e)) | |
|
133 | return jsonrpc_error(message=str(e)) | |
|
135 | 134 | |
|
136 | 135 | # now that we have a method, add self._req_params to |
|
137 | 136 | # self.kargs and dispatch control to WGIController |
|
138 |
arg |
|
|
137 | argspec = inspect.getargspec(self._func) | |
|
138 | arglist = argspec[0][1:] | |
|
139 | defaults = argspec[3] or [] | |
|
140 | default_empty = types.NotImplementedType | |
|
141 | ||
|
142 | kwarglist = list(izip_longest(reversed(arglist), reversed(defaults), | |
|
143 | fillvalue=default_empty)) | |
|
139 | 144 | |
|
140 | 145 | # this is little trick to inject logged in user for |
|
141 | 146 | # perms decorators to work they expect the controller class to have |
|
142 | # rhodecode_user set | |
|
147 | # rhodecode_user attribute set | |
|
143 | 148 | self.rhodecode_user = auth_u |
|
144 | 149 | |
|
145 | if 'user' not in arglist: | |
|
146 | return jsonrpc_error('This method [%s] does not support ' | |
|
147 | 'authentication (missing user param)' % | |
|
148 | self._func.__name__) | |
|
150 | # This attribute will need to be first param of a method that uses | |
|
151 | # api_key, which is translated to instance of user at that name | |
|
152 | USER_SESSION_ATTR = 'apiuser' | |
|
153 | ||
|
154 | if USER_SESSION_ATTR not in arglist: | |
|
155 | return jsonrpc_error(message='This method [%s] does not support ' | |
|
156 | 'authentication (missing %s param)' % | |
|
157 | (self._func.__name__, USER_SESSION_ATTR)) | |
|
149 | 158 | |
|
150 | 159 | # get our arglist and check if we provided them as args |
|
151 | for arg in arglist: | |
|
152 |
if arg == |
|
|
153 |
# |
|
|
154 | # checked before | |
|
160 | for arg, default in kwarglist: | |
|
161 | if arg == USER_SESSION_ATTR: | |
|
162 | # USER_SESSION_ATTR is something translated from api key and | |
|
163 | # this is checked before so we don't need validate it | |
|
155 | 164 | continue |
|
156 | 165 | |
|
157 | if not self._req_params or arg not in self._req_params: | |
|
158 | return jsonrpc_error('Missing %s arg in JSON DATA' % arg) | |
|
166 | # skip the required param check if it's default value is | |
|
167 | # NotImplementedType (default_empty) | |
|
168 | if not self._req_params or (type(default) == default_empty | |
|
169 | and arg not in self._req_params): | |
|
170 | return jsonrpc_error(message=('Missing non optional %s arg ' | |
|
171 | 'in JSON DATA') % arg) | |
|
159 | 172 | |
|
160 |
self._rpc_args = |
|
|
173 | self._rpc_args = {USER_SESSION_ATTR:u} | |
|
161 | 174 | self._rpc_args.update(self._req_params) |
|
162 | 175 | |
|
163 | 176 | self._rpc_args['action'] = self._req_method |
@@ -186,13 +199,13 b' class JSONRPCController(WSGIController):' | |||
|
186 | 199 | """ |
|
187 | 200 | try: |
|
188 | 201 | raw_response = self._inspect_call(self._func) |
|
189 | print raw_response | |
|
190 | 202 | if isinstance(raw_response, HTTPError): |
|
191 | 203 | self._error = str(raw_response) |
|
192 |
except JSONRPCError |
|
|
204 | except JSONRPCError, e: | |
|
193 | 205 | self._error = str(e) |
|
194 |
except Exception |
|
|
195 |
log. |
|
|
206 | except Exception, e: | |
|
207 | log.error('Encountered unhandled exception: %s' \ | |
|
208 | % traceback.format_exc()) | |
|
196 | 209 | json_exc = JSONRPCError('Internal server error') |
|
197 | 210 | self._error = str(json_exc) |
|
198 | 211 | |
@@ -226,3 +239,4 b' class JSONRPCController(WSGIController):' | |||
|
226 | 239 | return func |
|
227 | 240 | else: |
|
228 | 241 | raise AttributeError("No such method: %s" % self._req_method) |
|
242 |
@@ -1,7 +1,14 b'' | |||
|
1 | import traceback | |
|
2 | import logging | |
|
3 | ||
|
1 | 4 | from rhodecode.controllers.api import JSONRPCController, JSONRPCError |
|
2 | 5 | from rhodecode.lib.auth import HasPermissionAllDecorator |
|
3 | 6 | from rhodecode.model.scm import ScmModel |
|
4 | 7 | |
|
8 | from rhodecode.model.db import User, UsersGroup, Repository | |
|
9 | ||
|
10 | log = logging.getLogger(__name__) | |
|
11 | ||
|
5 | 12 | |
|
6 | 13 | class ApiController(JSONRPCController): |
|
7 | 14 | """ |
@@ -20,15 +27,18 b' class ApiController(JSONRPCController):' | |||
|
20 | 27 | """ |
|
21 | 28 | |
|
22 | 29 | @HasPermissionAllDecorator('hg.admin') |
|
23 | def pull(self, user, repo): | |
|
30 | def pull(self, apiuser, repo): | |
|
24 | 31 | """ |
|
25 | 32 | Dispatch pull action on given repo |
|
26 | 33 | |
|
27 | 34 | |
|
28 | param user: | |
|
29 | param repo: | |
|
35 | :param user: | |
|
36 | :param repo: | |
|
30 | 37 | """ |
|
31 | 38 | |
|
39 | if Repository.is_valid(repo) is False: | |
|
40 | raise JSONRPCError('Unknown repo "%s"' % repo) | |
|
41 | ||
|
32 | 42 | try: |
|
33 | 43 | ScmModel().pull_changes(repo, self.rhodecode_user.username) |
|
34 | 44 | return 'Pulled from %s' % repo |
@@ -36,5 +46,53 b' class ApiController(JSONRPCController):' | |||
|
36 | 46 | raise JSONRPCError('Unable to pull changes from "%s"' % repo) |
|
37 | 47 | |
|
38 | 48 | |
|
49 | @HasPermissionAllDecorator('hg.admin') | |
|
50 | def create_user(self, apiuser, username, password, active, admin, name, | |
|
51 | lastname, email): | |
|
52 | """ | |
|
53 | Creates new user | |
|
54 | ||
|
55 | :param apiuser: | |
|
56 | :param username: | |
|
57 | :param password: | |
|
58 | :param active: | |
|
59 | :param admin: | |
|
60 | :param name: | |
|
61 | :param lastname: | |
|
62 | :param email: | |
|
63 | """ | |
|
64 | ||
|
65 | form_data = dict(username=username, | |
|
66 | password=password, | |
|
67 | active=active, | |
|
68 | admin=admin, | |
|
69 | name=name, | |
|
70 | lastname=lastname, | |
|
71 | email=email) | |
|
72 | try: | |
|
73 | u = User.create(form_data) | |
|
74 | return {'id':u.user_id, | |
|
75 | 'msg':'created new user %s' % name} | |
|
76 | except Exception: | |
|
77 | log.error(traceback.format_exc()) | |
|
78 | raise JSONRPCError('failed to create user %s' % name) | |
|
39 | 79 | |
|
40 | 80 | |
|
81 | @HasPermissionAllDecorator('hg.admin') | |
|
82 | def create_users_group(self, apiuser, name, active): | |
|
83 | """ | |
|
84 | Creates an new usergroup | |
|
85 | ||
|
86 | :param name: | |
|
87 | :param active: | |
|
88 | """ | |
|
89 | form_data = {'users_group_name':name, | |
|
90 | 'users_group_active':active} | |
|
91 | try: | |
|
92 | ug = UsersGroup.create(form_data) | |
|
93 | return {'id':ug.users_group_id, | |
|
94 | 'msg':'created new users group %s' % name} | |
|
95 | except Exception: | |
|
96 | log.error(traceback.format_exc()) | |
|
97 | raise JSONRPCError('failed to create group %s' % name) | |
|
98 | No newline at end of file |
@@ -30,7 +30,7 b' import binascii' | |||
|
30 | 30 | |
|
31 | 31 | from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator |
|
32 | 32 | from rhodecode.lib.base import BaseRepoController, render |
|
33 |
from rhodecode.lib. |
|
|
33 | from rhodecode.lib.compat import OrderedDict | |
|
34 | 34 | from rhodecode.lib import safe_unicode |
|
35 | 35 | log = logging.getLogger(__name__) |
|
36 | 36 |
@@ -25,18 +25,13 b'' | |||
|
25 | 25 | |
|
26 | 26 | import logging |
|
27 | 27 | |
|
28 | try: | |
|
29 | import json | |
|
30 | except ImportError: | |
|
31 | #python 2.5 compatibility | |
|
32 | import simplejson as json | |
|
33 | ||
|
34 | 28 | from mercurial import graphmod |
|
35 | 29 | from pylons import request, session, tmpl_context as c |
|
36 | 30 | |
|
37 | 31 | from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator |
|
38 | 32 | from rhodecode.lib.base import BaseRepoController, render |
|
39 | 33 | from rhodecode.lib.helpers import RepoPage |
|
34 | from rhodecode.lib.compat import json | |
|
40 | 35 | |
|
41 | 36 | log = logging.getLogger(__name__) |
|
42 | 37 |
@@ -34,7 +34,7 b' import rhodecode.lib.helpers as h' | |||
|
34 | 34 | from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator |
|
35 | 35 | from rhodecode.lib.base import BaseRepoController, render |
|
36 | 36 | from rhodecode.lib.utils import EmptyChangeset |
|
37 |
from rhodecode.lib. |
|
|
37 | from rhodecode.lib.compat import OrderedDict | |
|
38 | 38 | |
|
39 | 39 | from vcs.exceptions import RepositoryError, ChangesetError, \ |
|
40 | 40 | ChangesetDoesNotExistError |
@@ -316,6 +316,13 b' class FilesController(BaseRepoController' | |||
|
316 | 316 | filename = file_obj.filename |
|
317 | 317 | content = file_obj.file |
|
318 | 318 | |
|
319 | #TODO: REMOVE THIS !! | |
|
320 | ################################ | |
|
321 | import ipdb;ipdb.set_trace() | |
|
322 | print 'setting ipdb debuggin for rhodecode.controllers.files.FilesController.add' | |
|
323 | ################################ | |
|
324 | ||
|
325 | ||
|
319 | 326 | node_path = os.path.join(location, filename) |
|
320 | 327 | author = self.rhodecode_user.full_contact |
|
321 | 328 |
@@ -64,8 +64,7 b' class LoginController(BaseController):' | |||
|
64 | 64 | c.form_result = login_form.to_python(dict(request.POST)) |
|
65 | 65 | #form checks for username/password, now we're authenticated |
|
66 | 66 | username = c.form_result['username'] |
|
67 | user = User.by_username(username, | |
|
68 | case_insensitive=True) | |
|
67 | user = User.get_by_username(username, case_insensitive=True) | |
|
69 | 68 | auth_user = AuthUser(user.user_id) |
|
70 | 69 | auth_user.set_authenticated() |
|
71 | 70 | session['rhodecode_user'] = auth_user |
@@ -95,8 +94,7 b' class LoginController(BaseController):' | |||
|
95 | 94 | def register(self): |
|
96 | 95 | user_model = UserModel() |
|
97 | 96 | c.auto_active = False |
|
98 |
for perm in |
|
|
99 | cache=False).user_perms: | |
|
97 | for perm in User.get_by_username('default').user_perms: | |
|
100 | 98 | if perm.permission.permission_name == 'hg.register.auto_activate': |
|
101 | 99 | c.auto_active = True |
|
102 | 100 | break |
@@ -39,18 +39,13 b' from rhodecode.model.repo import RepoMod' | |||
|
39 | 39 | from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator |
|
40 | 40 | from rhodecode.lib.base import BaseRepoController, render |
|
41 | 41 | from rhodecode.lib.utils import EmptyChangeset |
|
42 | from rhodecode.lib.odict import OrderedDict | |
|
43 | 42 | |
|
44 | 43 | from rhodecode.lib.celerylib import run_task |
|
45 | 44 | from rhodecode.lib.celerylib.tasks import get_commits_stats, \ |
|
46 | 45 | LANGUAGES_EXTENSIONS_MAP |
|
47 | 46 | from rhodecode.lib.helpers import RepoPage |
|
47 | from rhodecode.lib.compat import json, OrderedDict | |
|
48 | 48 | |
|
49 | try: | |
|
50 | import json | |
|
51 | except ImportError: | |
|
52 | #python 2.5 compatibility | |
|
53 | import simplejson as json | |
|
54 | 49 | log = logging.getLogger(__name__) |
|
55 | 50 | |
|
56 | 51 | |
@@ -139,9 +134,9 b' class SummaryController(BaseRepoControll' | |||
|
139 | 134 | c.commit_data = stats.commit_activity |
|
140 | 135 | c.overview_data = stats.commit_activity_combined |
|
141 | 136 | |
|
142 |
lang_stats = |
|
|
137 | lang_stats = ((x, {"count": y, | |
|
143 | 138 | "desc": LANGUAGES_EXTENSIONS_MAP.get(x)}) |
|
144 |
for x, y in lang_stats_d.items() |
|
|
139 | for x, y in lang_stats_d.items()) | |
|
145 | 140 | |
|
146 | 141 | c.trending_languages = json.dumps(OrderedDict( |
|
147 | 142 | sorted(lang_stats, reverse=True, |
@@ -28,7 +28,7 b' from pylons import tmpl_context as c' | |||
|
28 | 28 | |
|
29 | 29 | from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator |
|
30 | 30 | from rhodecode.lib.base import BaseRepoController, render |
|
31 |
from rhodecode.lib. |
|
|
31 | from rhodecode.lib.compat import OrderedDict | |
|
32 | 32 | |
|
33 | 33 | log = logging.getLogger(__name__) |
|
34 | 34 |
@@ -23,14 +23,6 b'' | |||
|
23 | 23 | # You should have received a copy of the GNU General Public License |
|
24 | 24 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
25 | 25 | |
|
26 | ||
|
27 | try: | |
|
28 | import json | |
|
29 | except ImportError: | |
|
30 | #python 2.5 compatibility | |
|
31 | import simplejson as json | |
|
32 | ||
|
33 | ||
|
34 | 26 | def __get_lem(): |
|
35 | 27 | from pygments import lexers |
|
36 | 28 | from string import lower |
@@ -157,44 +149,69 b' def generate_api_key(username, salt=None' | |||
|
157 | 149 | return hashlib.sha1(username + salt).hexdigest() |
|
158 | 150 | |
|
159 | 151 | |
|
160 |
def safe_unicode( |
|
|
152 | def safe_unicode(str_, from_encoding='utf8'): | |
|
161 | 153 | """ |
|
162 | safe unicode function. In case of UnicodeDecode error we try to return | |
|
163 | unicode with errors replaceed | |
|
154 | safe unicode function. Does few trick to turn str_ into unicode | |
|
155 | ||
|
156 | In case of UnicodeDecode error we try to return it with encoding detected | |
|
157 | by chardet library if it fails fallback to unicode with errors replaced | |
|
164 | 158 | |
|
165 |
:param |
|
|
159 | :param str_: string to decode | |
|
166 | 160 | :rtype: unicode |
|
167 | 161 | :returns: unicode object |
|
168 | 162 | """ |
|
163 | if isinstance(str_, unicode): | |
|
164 | return str_ | |
|
169 | 165 | |
|
170 | if isinstance(_str, unicode): | |
|
171 |
return |
|
|
166 | try: | |
|
167 | return unicode(str_) | |
|
168 | except UnicodeDecodeError: | |
|
169 | pass | |
|
170 | ||
|
171 | try: | |
|
172 | return unicode(str_, from_encoding) | |
|
173 | except UnicodeDecodeError: | |
|
174 | pass | |
|
172 | 175 | |
|
173 | 176 | try: |
|
174 | u_str = unicode(_str, from_encoding) | |
|
175 | except UnicodeDecodeError: | |
|
176 | u_str = unicode(_str, from_encoding, 'replace') | |
|
177 | ||
|
178 | return u_str | |
|
179 | ||
|
177 | import chardet | |
|
178 | encoding = chardet.detect(str_)['encoding'] | |
|
179 | if encoding is None: | |
|
180 | raise Exception() | |
|
181 | return str_.decode(encoding) | |
|
182 | except (ImportError, UnicodeDecodeError, Exception): | |
|
183 | return unicode(str_, from_encoding, 'replace') | |
|
180 | 184 | |
|
181 |
def safe_str( |
|
|
185 | def safe_str(unicode_, to_encoding='utf8'): | |
|
182 | 186 | """ |
|
183 | safe str function. In case of UnicodeEncode error we try to return | |
|
184 | str with errors replaceed | |
|
187 | safe str function. Does few trick to turn unicode_ into string | |
|
188 | ||
|
189 | In case of UnicodeEncodeError we try to return it with encoding detected | |
|
190 | by chardet library if it fails fallback to string with errors replaced | |
|
185 | 191 | |
|
186 |
:param |
|
|
192 | :param unicode_: unicode to encode | |
|
187 | 193 | :rtype: str |
|
188 | 194 | :returns: str object |
|
189 | 195 | """ |
|
190 | 196 | |
|
191 |
if isinstance( |
|
|
192 |
return |
|
|
197 | if isinstance(unicode_, str): | |
|
198 | return unicode_ | |
|
199 | ||
|
200 | try: | |
|
201 | return unicode_.encode(to_encoding) | |
|
202 | except UnicodeEncodeError: | |
|
203 | pass | |
|
193 | 204 | |
|
194 | 205 | try: |
|
195 | safe_str = str(_unicode) | |
|
196 | except UnicodeEncodeError: | |
|
197 | safe_str = _unicode.encode(to_encoding, 'replace') | |
|
206 | import chardet | |
|
207 | encoding = chardet.detect(unicode_)['encoding'] | |
|
208 | print encoding | |
|
209 | if encoding is None: | |
|
210 | raise UnicodeEncodeError() | |
|
211 | ||
|
212 | return unicode_.encode(encoding) | |
|
213 | except (ImportError, UnicodeEncodeError): | |
|
214 | return unicode_.encode(to_encoding, 'replace') | |
|
198 | 215 | |
|
199 | 216 | return safe_str |
|
200 | 217 | |
@@ -361,4 +378,4 b' def get_changeset_safe(repo, rev):' | |||
|
361 | 378 | except RepositoryError: |
|
362 | 379 | from rhodecode.lib.utils import EmptyChangeset |
|
363 | 380 | cs = EmptyChangeset(requested_revision=rev) |
|
364 | return cs No newline at end of file | |
|
381 | return cs |
@@ -6,8 +6,8 b'' | |||
|
6 | 6 | authentication and permission libraries |
|
7 | 7 | |
|
8 | 8 | :created_on: Apr 4, 2010 |
|
9 | :copyright: (c) 2010 by marcink. | |
|
10 |
:license: |
|
|
9 | :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com> | |
|
10 | :license: GPLv3, see COPYING for more details. | |
|
11 | 11 | """ |
|
12 | 12 | # This program is free software: you can redistribute it and/or modify |
|
13 | 13 | # it under the terms of the GNU General Public License as published by |
@@ -48,7 +48,7 b' from rhodecode.lib.auth_ldap import Auth' | |||
|
48 | 48 | |
|
49 | 49 | from rhodecode.model import meta |
|
50 | 50 | from rhodecode.model.user import UserModel |
|
51 | from rhodecode.model.db import Permission, RhodeCodeSettings | |
|
51 | from rhodecode.model.db import Permission, RhodeCodeSettings, User | |
|
52 | 52 | |
|
53 | 53 | log = logging.getLogger(__name__) |
|
54 | 54 | |
@@ -151,7 +151,7 b' def authenticate(username, password):' | |||
|
151 | 151 | """ |
|
152 | 152 | |
|
153 | 153 | user_model = UserModel() |
|
154 |
user = |
|
|
154 | user = User.get_by_username(username) | |
|
155 | 155 | |
|
156 | 156 | log.debug('Authenticating user using RhodeCode account') |
|
157 | 157 | if user is not None and not user.ldap_dn: |
@@ -170,8 +170,7 b' def authenticate(username, password):' | |||
|
170 | 170 | |
|
171 | 171 | else: |
|
172 | 172 | log.debug('Regular authentication failed') |
|
173 |
user_obj = |
|
|
174 | case_insensitive=True) | |
|
173 | user_obj = User.get_by_username(username, case_insensitive=True) | |
|
175 | 174 | |
|
176 | 175 | if user_obj is not None and not user_obj.ldap_dn: |
|
177 | 176 | log.debug('this user already exists as non ldap') |
@@ -252,7 +251,7 b' class AuthUser(object):' | |||
|
252 | 251 | |
|
253 | 252 | def propagate_data(self): |
|
254 | 253 | user_model = UserModel() |
|
255 |
self.anonymous_user = |
|
|
254 | self.anonymous_user = User.get_by_username('default') | |
|
256 | 255 | is_user_loaded = False |
|
257 | 256 | if self._api_key and self._api_key != self.anonymous_user.api_key: |
|
258 | 257 | #try go get user by api key |
@@ -269,7 +268,7 b' class AuthUser(object):' | |||
|
269 | 268 | self.username = self.username.partition('@')[0] |
|
270 | 269 | |
|
271 | 270 | log.debug('Auth User lookup by USER NAME %s', self.username) |
|
272 |
dbuser = |
|
|
271 | dbuser = User.get_by_username(self.username) | |
|
273 | 272 | if dbuser is not None and dbuser.active: |
|
274 | 273 | for k, v in dbuser.get_dict().items(): |
|
275 | 274 | setattr(self, k, v) |
@@ -7,8 +7,8 b'' | |||
|
7 | 7 | repositories and send it to backup server using RSA key via ssh. |
|
8 | 8 | |
|
9 | 9 | :created_on: Feb 28, 2010 |
|
10 | :copyright: (c) 2010 by marcink. | |
|
11 |
:license: |
|
|
10 | :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com> | |
|
11 | :license: GPLv3, see COPYING for more details. | |
|
12 | 12 | """ |
|
13 | 13 | # This program is free software: you can redistribute it and/or modify |
|
14 | 14 | # it under the terms of the GNU General Public License as published by |
@@ -76,7 +76,7 b' class BaseRepoController(BaseController)' | |||
|
76 | 76 | super(BaseRepoController, self).__before__() |
|
77 | 77 | if c.repo_name: |
|
78 | 78 | |
|
79 | c.rhodecode_db_repo = Repository.by_repo_name(c.repo_name) | |
|
79 | c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name) | |
|
80 | 80 | c.rhodecode_repo = c.rhodecode_db_repo.scm_instance |
|
81 | 81 | |
|
82 | 82 | if c.rhodecode_repo is None: |
@@ -43,7 +43,8 b' from rhodecode.lib.celerylib import run_' | |||
|
43 | 43 | from rhodecode.lib.helpers import person |
|
44 | 44 | from rhodecode.lib.smtp_mailer import SmtpMailer |
|
45 | 45 | from rhodecode.lib.utils import add_cache |
|
46 |
from rhodecode.lib. |
|
|
46 | from rhodecode.lib.compat import json, OrderedDict | |
|
47 | ||
|
47 | 48 | from rhodecode.model import init_model |
|
48 | 49 | from rhodecode.model import meta |
|
49 | 50 | from rhodecode.model.db import RhodeCodeUi, Statistics, Repository |
@@ -54,11 +55,7 b' from sqlalchemy import engine_from_confi' | |||
|
54 | 55 | |
|
55 | 56 | add_cache(config) |
|
56 | 57 | |
|
57 | try: | |
|
58 | import json | |
|
59 | except ImportError: | |
|
60 | #python 2.5 compatibility | |
|
61 | import simplejson as json | |
|
58 | ||
|
62 | 59 | |
|
63 | 60 | __all__ = ['whoosh_index', 'get_commits_stats', |
|
64 | 61 | 'reset_user_password', 'send_email'] |
@@ -160,7 +160,9 b' class DbManage(object):' | |||
|
160 | 160 | def step_3(self): |
|
161 | 161 | print ('Adding additional settings into RhodeCode db') |
|
162 | 162 | self.klass.fix_settings() |
|
163 | ||
|
163 | print ('Adding ldap defaults') | |
|
164 | self.klass.create_ldap_options(skip_existing=True) | |
|
165 | ||
|
164 | 166 | upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1) |
|
165 | 167 | |
|
166 | 168 | #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE |
@@ -307,7 +309,7 b' class DbManage(object):' | |||
|
307 | 309 | self.sa.rollback() |
|
308 | 310 | raise |
|
309 | 311 | |
|
310 | def create_ldap_options(self): | |
|
312 | def create_ldap_options(self,skip_existing=False): | |
|
311 | 313 | """Creates ldap settings""" |
|
312 | 314 | |
|
313 | 315 | try: |
@@ -319,6 +321,9 b' class DbManage(object):' | |||
|
319 | 321 | ('ldap_attr_login', ''), ('ldap_attr_firstname', ''), |
|
320 | 322 | ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]: |
|
321 | 323 | |
|
324 | if skip_existing and RhodeCodeSettings.get_by_name(k) != None: | |
|
325 | log.debug('Skipping option %s' % k) | |
|
326 | continue | |
|
322 | 327 | setting = RhodeCodeSettings(k, v) |
|
323 | 328 | self.sa.add(setting) |
|
324 | 329 | self.sa.commit() |
@@ -411,42 +416,30 b' class DbManage(object):' | |||
|
411 | 416 | |
|
412 | 417 | def create_user(self, username, password, email='', admin=False): |
|
413 | 418 | log.info('creating administrator user %s', username) |
|
414 | new_user = User() | |
|
415 |
|
|
|
416 | new_user.password = get_crypt_password(password) | |
|
417 | new_user.api_key = generate_api_key(username) | |
|
418 | new_user.name = 'RhodeCode' | |
|
419 | new_user.lastname = 'Admin' | |
|
420 | new_user.email = email | |
|
421 | new_user.admin = admin | |
|
422 | new_user.active = True | |
|
419 | ||
|
420 | form_data = dict(username=username, | |
|
421 | password=password, | |
|
422 | active=True, | |
|
423 | admin=admin, | |
|
424 | name='RhodeCode', | |
|
425 | lastname='Admin', | |
|
426 | email=email) | |
|
427 | User.create(form_data) | |
|
423 | 428 | |
|
424 | try: | |
|
425 | self.sa.add(new_user) | |
|
426 | self.sa.commit() | |
|
427 | except: | |
|
428 | self.sa.rollback() | |
|
429 | raise | |
|
430 | 429 | |
|
431 | 430 | def create_default_user(self): |
|
432 | 431 | log.info('creating default user') |
|
433 | 432 | #create default user for handling default permissions. |
|
434 | def_user = User() | |
|
435 | def_user.username = 'default' | |
|
436 | def_user.password = get_crypt_password(str(uuid.uuid1())[:8]) | |
|
437 | def_user.api_key = generate_api_key('default') | |
|
438 | def_user.name = 'Anonymous' | |
|
439 | def_user.lastname = 'User' | |
|
440 | def_user.email = 'anonymous@rhodecode.org' | |
|
441 | def_user.admin = False | |
|
442 | def_user.active = False | |
|
443 | try: | |
|
444 | self.sa.add(def_user) | |
|
445 | self.sa.commit() | |
|
446 | except: | |
|
447 | self.sa.rollback() | |
|
448 | raise | |
|
449 | 433 | |
|
434 | form_data = dict(username='default', | |
|
435 | password=str(uuid.uuid1())[:8], | |
|
436 | active=False, | |
|
437 | admin=False, | |
|
438 | name='Anonymous', | |
|
439 | lastname='User', | |
|
440 | email='anonymous@rhodecode.org') | |
|
441 | User.create(form_data) | |
|
442 | ||
|
450 | 443 | def create_permissions(self): |
|
451 | 444 | #module.(access|create|change|delete)_[name] |
|
452 | 445 | #module.(read|write|owner) |
@@ -76,6 +76,14 b' def upgrade(migrate_engine):' | |||
|
76 | 76 | #========================================================================== |
|
77 | 77 | from rhodecode.model.db import Repository |
|
78 | 78 | |
|
79 | #ADD clone_uri column# | |
|
80 | ||
|
81 | clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, | |
|
82 | assert_unicode=None), | |
|
83 | nullable=True, unique=False, default=None) | |
|
84 | ||
|
85 | clone_uri.create(Repository().__table__) | |
|
86 | ||
|
79 | 87 | #ADD downloads column# |
|
80 | 88 | enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True) |
|
81 | 89 | enable_downloads.create(Repository().__table__) |
@@ -92,21 +100,16 b' def upgrade(migrate_engine):' | |||
|
92 | 100 | group_id.create(Repository().__table__) |
|
93 | 101 | |
|
94 | 102 | |
|
95 | #ADD clone_uri column# | |
|
96 | ||
|
97 | clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, | |
|
98 | assert_unicode=None), | |
|
99 | nullable=True, unique=False, default=None) | |
|
100 | ||
|
101 | clone_uri.create(Repository().__table__) | |
|
102 | ||
|
103 | ||
|
104 | 103 | #========================================================================== |
|
105 | 104 | # Upgrade of `user_followings` table |
|
106 | 105 | #========================================================================== |
|
107 | 106 | |
|
108 | follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now) | |
|
109 | follows_from.create(Repository().__table__) | |
|
107 | from rhodecode.model.db import UserFollowing | |
|
108 | ||
|
109 | follows_from = Column('follows_from', DateTime(timezone=False), | |
|
110 | nullable=True, unique=None, | |
|
111 | default=datetime.datetime.now) | |
|
112 | follows_from.create(UserFollowing().__table__) | |
|
110 | 113 | |
|
111 | 114 | return |
|
112 | 115 |
@@ -6,8 +6,8 b'' | |||
|
6 | 6 | Set of custom exceptions used in RhodeCode |
|
7 | 7 | |
|
8 | 8 | :created_on: Nov 17, 2010 |
|
9 | :copyright: (c) 2010 by marcink. | |
|
10 |
:license: |
|
|
9 | :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com> | |
|
10 | :license: GPLv3, see COPYING for more details. | |
|
11 | 11 | """ |
|
12 | 12 | # This program is free software: you can redistribute it and/or modify |
|
13 | 13 | # it under the terms of the GNU General Public License as published by |
@@ -44,11 +44,11 b' class SimpleGitUploadPackHandler(dulserv' | |||
|
44 | 44 | get_tagged=self.get_tagged) |
|
45 | 45 | |
|
46 | 46 | # Do they want any objects? |
|
47 | if len(objects_iter) == 0: | |
|
47 | if objects_iter is None or len(objects_iter) == 0: | |
|
48 | 48 | return |
|
49 | 49 | |
|
50 | 50 | self.progress("counting objects: %d, done.\n" % len(objects_iter)) |
|
51 |
dulserver.write_pack_ |
|
|
51 | dulserver.write_pack_objects(dulserver.ProtocolFile(None, write), | |
|
52 | 52 | objects_iter, len(objects_iter)) |
|
53 | 53 | messages = [] |
|
54 | 54 | messages.append('thank you for using rhodecode') |
@@ -71,8 +71,8 b' from paste.httpheaders import REMOTE_USE' | |||
|
71 | 71 | |
|
72 | 72 | from rhodecode.lib import safe_str |
|
73 | 73 | from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware |
|
74 |
from rhodecode.lib.utils import invalidate_cache, |
|
|
75 |
from rhodecode.model. |
|
|
74 | from rhodecode.lib.utils import invalidate_cache, is_valid_repo | |
|
75 | from rhodecode.model.db import User | |
|
76 | 76 | |
|
77 | 77 | from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError |
|
78 | 78 | |
@@ -96,12 +96,10 b' class SimpleGit(object):' | |||
|
96 | 96 | def __init__(self, application, config): |
|
97 | 97 | self.application = application |
|
98 | 98 | self.config = config |
|
99 | #authenticate this git request using | |
|
99 | # base path of repo locations | |
|
100 | self.basepath = self.config['base_path'] | |
|
101 | #authenticate this mercurial request using authfunc | |
|
100 | 102 | self.authenticate = AuthBasicAuthenticator('', authfunc) |
|
101 | self.ipaddr = '0.0.0.0' | |
|
102 | self.repo_name = None | |
|
103 | self.username = None | |
|
104 | self.action = None | |
|
105 | 103 | |
|
106 | 104 | def __call__(self, environ, start_response): |
|
107 | 105 | if not is_git(environ): |
@@ -109,31 +107,34 b' class SimpleGit(object):' | |||
|
109 | 107 | |
|
110 | 108 | proxy_key = 'HTTP_X_REAL_IP' |
|
111 | 109 | def_key = 'REMOTE_ADDR' |
|
112 |
|
|
|
110 | ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0')) | |
|
111 | username = None | |
|
113 | 112 | # skip passing error to error controller |
|
114 | 113 | environ['pylons.status_code_redirect'] = True |
|
115 | 114 | |
|
116 | 115 | #====================================================================== |
|
116 | # EXTRACT REPOSITORY NAME FROM ENV | |
|
117 | #====================================================================== | |
|
118 | try: | |
|
119 | repo_name = self.__get_repository(environ) | |
|
120 | log.debug('Extracted repo name is %s' % repo_name) | |
|
121 | except: | |
|
122 | return HTTPInternalServerError()(environ, start_response) | |
|
123 | ||
|
124 | #====================================================================== | |
|
117 | 125 | # GET ACTION PULL or PUSH |
|
118 | 126 | #====================================================================== |
|
119 |
|
|
|
120 | try: | |
|
121 | #================================================================== | |
|
122 | # GET REPOSITORY NAME | |
|
123 | #================================================================== | |
|
124 | self.repo_name = self.__get_repository(environ) | |
|
125 | except: | |
|
126 | return HTTPInternalServerError()(environ, start_response) | |
|
127 | action = self.__get_action(environ) | |
|
127 | 128 | |
|
128 | 129 | #====================================================================== |
|
129 | 130 | # CHECK ANONYMOUS PERMISSION |
|
130 | 131 | #====================================================================== |
|
131 |
if |
|
|
132 | if action in ['pull', 'push']: | |
|
132 | 133 | anonymous_user = self.__get_user('default') |
|
133 |
|
|
|
134 |
anonymous_perm = self.__check_permission( |
|
|
134 | username = anonymous_user.username | |
|
135 | anonymous_perm = self.__check_permission(action, | |
|
135 | 136 | anonymous_user, |
|
136 |
|
|
|
137 | repo_name) | |
|
137 | 138 | |
|
138 | 139 | if anonymous_perm is not True or anonymous_user.active is False: |
|
139 | 140 | if anonymous_perm is not True: |
@@ -162,56 +163,66 b' class SimpleGit(object):' | |||
|
162 | 163 | # BASIC AUTH |
|
163 | 164 | #============================================================== |
|
164 | 165 | |
|
165 |
if |
|
|
166 | if action in ['pull', 'push']: | |
|
166 | 167 | username = REMOTE_USER(environ) |
|
167 | 168 | try: |
|
168 | 169 | user = self.__get_user(username) |
|
169 |
|
|
|
170 | username = user.username | |
|
170 | 171 | except: |
|
171 | 172 | log.error(traceback.format_exc()) |
|
172 | 173 | return HTTPInternalServerError()(environ, |
|
173 | 174 | start_response) |
|
174 | 175 | |
|
175 | 176 | #check permissions for this repository |
|
176 |
perm = self.__check_permission( |
|
|
177 |
|
|
|
177 | perm = self.__check_permission(action, user, | |
|
178 | repo_name) | |
|
178 | 179 | if perm is not True: |
|
179 | 180 | return HTTPForbidden()(environ, start_response) |
|
180 | 181 | |
|
181 |
|
|
|
182 |
|
|
|
183 |
|
|
|
184 |
|
|
|
182 | extras = {'ip': ipaddr, | |
|
183 | 'username': username, | |
|
184 | 'action': action, | |
|
185 | 'repository': repo_name} | |
|
185 | 186 | |
|
186 | 187 | #=================================================================== |
|
187 | 188 | # GIT REQUEST HANDLING |
|
188 | 189 | #=================================================================== |
|
189 | self.basepath = self.config['base_path'] | |
|
190 |
|
|
|
191 | #quick check if that dir exists... | |
|
192 | if check_repo_fast(self.repo_name, self.basepath): | |
|
190 | ||
|
191 | repo_path = safe_str(os.path.join(self.basepath, repo_name)) | |
|
192 | log.debug('Repository path is %s' % repo_path) | |
|
193 | ||
|
194 | # quick check if that dir exists... | |
|
195 | if is_valid_repo(repo_name, self.basepath) is False: | |
|
193 | 196 | return HTTPNotFound()(environ, start_response) |
|
197 | ||
|
194 | 198 | try: |
|
195 | app = self.__make_app() | |
|
196 | except: | |
|
199 | #invalidate cache on push | |
|
200 | if action == 'push': | |
|
201 | self.__invalidate_cache(repo_name) | |
|
202 | ||
|
203 | app = self.__make_app(repo_name, repo_path) | |
|
204 | return app(environ, start_response) | |
|
205 | except Exception: | |
|
197 | 206 | log.error(traceback.format_exc()) |
|
198 | 207 | return HTTPInternalServerError()(environ, start_response) |
|
199 | 208 | |
|
200 | #invalidate cache on push | |
|
201 | if self.action == 'push': | |
|
202 | self.__invalidate_cache(self.repo_name) | |
|
209 | def __make_app(self, repo_name, repo_path): | |
|
210 | """ | |
|
211 | Make an wsgi application using dulserver | |
|
212 | ||
|
213 | :param repo_name: name of the repository | |
|
214 | :param repo_path: full path to the repository | |
|
215 | """ | |
|
203 | 216 | |
|
204 | return app(environ, start_response) | |
|
205 | ||
|
206 | def __make_app(self): | |
|
207 | _d = {'/' + self.repo_name: Repo(self.repo_path)} | |
|
217 | _d = {'/' + repo_name: Repo(repo_path)} | |
|
208 | 218 | backend = dulserver.DictBackend(_d) |
|
209 | 219 | gitserve = HTTPGitApplication(backend) |
|
210 | 220 | |
|
211 | 221 | return gitserve |
|
212 | 222 | |
|
213 | 223 | def __check_permission(self, action, user, repo_name): |
|
214 | """Checks permissions using action (push/pull) user and repository | |
|
224 | """ | |
|
225 | Checks permissions using action (push/pull) user and repository | |
|
215 | 226 | name |
|
216 | 227 | |
|
217 | 228 | :param action: push or pull action |
@@ -235,7 +246,8 b' class SimpleGit(object):' | |||
|
235 | 246 | return True |
|
236 | 247 | |
|
237 | 248 | def __get_repository(self, environ): |
|
238 | """Get's repository name out of PATH_INFO header | |
|
249 | """ | |
|
250 | Get's repository name out of PATH_INFO header | |
|
239 | 251 | |
|
240 | 252 | :param environ: environ where PATH_INFO is stored |
|
241 | 253 | """ |
@@ -250,7 +262,7 b' class SimpleGit(object):' | |||
|
250 | 262 | return repo_name |
|
251 | 263 | |
|
252 | 264 | def __get_user(self, username): |
|
253 |
return User |
|
|
265 | return User.get_by_username(username) | |
|
254 | 266 | |
|
255 | 267 | def __get_action(self, environ): |
|
256 | 268 | """Maps git request commands into a pull or push command. |
@@ -274,3 +286,4 b' class SimpleGit(object):' | |||
|
274 | 286 | invalidate the cache to see the changes right away but only for |
|
275 | 287 | push requests""" |
|
276 | 288 | invalidate_cache('get_repo_cached_%s' % repo_name) |
|
289 |
@@ -29,8 +29,7 b' import logging' | |||
|
29 | 29 | import traceback |
|
30 | 30 | |
|
31 | 31 | from mercurial.error import RepoError |
|
32 | from mercurial.hgweb import hgweb | |
|
33 | from mercurial.hgweb.request import wsgiapplication | |
|
32 | from mercurial.hgweb import hgweb_mod | |
|
34 | 33 | |
|
35 | 34 | from paste.auth.basic import AuthBasicAuthenticator |
|
36 | 35 | from paste.httpheaders import REMOTE_USER, AUTH_TYPE |
@@ -38,8 +37,8 b' from paste.httpheaders import REMOTE_USE' | |||
|
38 | 37 | from rhodecode.lib import safe_str |
|
39 | 38 | from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware |
|
40 | 39 | from rhodecode.lib.utils import make_ui, invalidate_cache, \ |
|
41 |
|
|
|
42 |
from rhodecode.model. |
|
|
40 | is_valid_repo, ui_sections | |
|
41 | from rhodecode.model.db import User | |
|
43 | 42 | |
|
44 | 43 | from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError |
|
45 | 44 | |
@@ -61,12 +60,11 b' class SimpleHg(object):' | |||
|
61 | 60 | def __init__(self, application, config): |
|
62 | 61 | self.application = application |
|
63 | 62 | self.config = config |
|
63 | # base path of repo locations | |
|
64 | self.basepath = self.config['base_path'] | |
|
64 | 65 | #authenticate this mercurial request using authfunc |
|
65 | 66 | self.authenticate = AuthBasicAuthenticator('', authfunc) |
|
66 | 67 | self.ipaddr = '0.0.0.0' |
|
67 | self.repo_name = None | |
|
68 | self.username = None | |
|
69 | self.action = None | |
|
70 | 68 | |
|
71 | 69 | def __call__(self, environ, start_response): |
|
72 | 70 | if not is_mercurial(environ): |
@@ -74,31 +72,35 b' class SimpleHg(object):' | |||
|
74 | 72 | |
|
75 | 73 | proxy_key = 'HTTP_X_REAL_IP' |
|
76 | 74 | def_key = 'REMOTE_ADDR' |
|
77 |
|
|
|
75 | ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0')) | |
|
76 | ||
|
78 | 77 | # skip passing error to error controller |
|
79 | 78 | environ['pylons.status_code_redirect'] = True |
|
80 | 79 | |
|
81 | 80 | #====================================================================== |
|
81 | # EXTRACT REPOSITORY NAME FROM ENV | |
|
82 | #====================================================================== | |
|
83 | try: | |
|
84 | repo_name = environ['REPO_NAME'] = self.__get_repository(environ) | |
|
85 | log.debug('Extracted repo name is %s' % repo_name) | |
|
86 | except: | |
|
87 | return HTTPInternalServerError()(environ, start_response) | |
|
88 | ||
|
89 | #====================================================================== | |
|
82 | 90 | # GET ACTION PULL or PUSH |
|
83 | 91 | #====================================================================== |
|
84 |
|
|
|
85 | try: | |
|
86 | #================================================================== | |
|
87 | # GET REPOSITORY NAME | |
|
88 | #================================================================== | |
|
89 | self.repo_name = self.__get_repository(environ) | |
|
90 | except: | |
|
91 | return HTTPInternalServerError()(environ, start_response) | |
|
92 | action = self.__get_action(environ) | |
|
92 | 93 | |
|
93 | 94 | #====================================================================== |
|
94 | 95 | # CHECK ANONYMOUS PERMISSION |
|
95 | 96 | #====================================================================== |
|
96 |
if |
|
|
97 | if action in ['pull', 'push']: | |
|
97 | 98 | anonymous_user = self.__get_user('default') |
|
98 | self.username = anonymous_user.username | |
|
99 | anonymous_perm = self.__check_permission(self.action, | |
|
99 | ||
|
100 | username = anonymous_user.username | |
|
101 | anonymous_perm = self.__check_permission(action, | |
|
100 | 102 | anonymous_user, |
|
101 |
|
|
|
103 | repo_name) | |
|
102 | 104 | |
|
103 | 105 | if anonymous_perm is not True or anonymous_user.active is False: |
|
104 | 106 | if anonymous_perm is not True: |
@@ -127,43 +129,52 b' class SimpleHg(object):' | |||
|
127 | 129 | # BASIC AUTH |
|
128 | 130 | #============================================================== |
|
129 | 131 | |
|
130 |
if |
|
|
132 | if action in ['pull', 'push']: | |
|
131 | 133 | #Removing realm from username |
|
132 | 134 | username = REMOTE_USER(environ).partition('@')[0] |
|
133 | 135 | try: |
|
134 | 136 | user = self.__get_user(username) |
|
135 | 137 | if user is None: |
|
136 | 138 | return HTTPForbidden()(environ, start_response) |
|
137 |
|
|
|
139 | username = user.username | |
|
138 | 140 | except: |
|
139 | 141 | log.error(traceback.format_exc()) |
|
140 | 142 | return HTTPInternalServerError()(environ, |
|
141 | 143 | start_response) |
|
142 | 144 | |
|
143 | 145 | #check permissions for this repository |
|
144 |
perm = self.__check_permission( |
|
|
145 |
|
|
|
146 | perm = self.__check_permission(action, user, | |
|
147 | repo_name) | |
|
146 | 148 | if perm is not True: |
|
147 | 149 | return HTTPForbidden()(environ, start_response) |
|
148 | 150 | |
|
149 |
|
|
|
150 |
|
|
|
151 |
|
|
|
152 |
|
|
|
151 | extras = {'ip': ipaddr, | |
|
152 | 'username': username, | |
|
153 | 'action': action, | |
|
154 | 'repository': repo_name} | |
|
153 | 155 | |
|
154 | 156 | #====================================================================== |
|
155 | 157 | # MERCURIAL REQUEST HANDLING |
|
156 | 158 | #====================================================================== |
|
157 | environ['PATH_INFO'] = '/' # since we wrap into hgweb, reset the path | |
|
158 | self.baseui = make_ui('db') | |
|
159 | self.basepath = self.config['base_path'] | |
|
160 | self.repo_path = os.path.join(self.basepath, self.repo_name) | |
|
159 | ||
|
160 | repo_path = safe_str(os.path.join(self.basepath, repo_name)) | |
|
161 | log.debug('Repository path is %s' % repo_path) | |
|
162 | ||
|
163 | baseui = make_ui('db') | |
|
164 | self.__inject_extras(repo_path, baseui, extras) | |
|
165 | ||
|
161 | 166 | |
|
162 | #quick check if that dir exists... | |
|
163 |
if |
|
|
167 | # quick check if that dir exists... | |
|
168 | if is_valid_repo(repo_name, self.basepath) is False: | |
|
164 | 169 | return HTTPNotFound()(environ, start_response) |
|
170 | ||
|
165 | 171 | try: |
|
166 | app = wsgiapplication(self.__make_app) | |
|
172 | #invalidate cache on push | |
|
173 | if action == 'push': | |
|
174 | self.__invalidate_cache(repo_name) | |
|
175 | ||
|
176 | app = self.__make_app(repo_path, baseui, extras) | |
|
177 | return app(environ, start_response) | |
|
167 | 178 | except RepoError, e: |
|
168 | 179 | if str(e).find('not found') != -1: |
|
169 | 180 | return HTTPNotFound()(environ, start_response) |
@@ -171,19 +182,12 b' class SimpleHg(object):' | |||
|
171 | 182 | log.error(traceback.format_exc()) |
|
172 | 183 | return HTTPInternalServerError()(environ, start_response) |
|
173 | 184 | |
|
174 | #invalidate cache on push | |
|
175 | if self.action == 'push': | |
|
176 | self.__invalidate_cache(self.repo_name) | |
|
177 | ||
|
178 | return app(environ, start_response) | |
|
179 | ||
|
180 | def __make_app(self): | |
|
185 | def __make_app(self, repo_name, baseui, extras): | |
|
181 | 186 | """ |
|
182 | 187 | Make an wsgi application using hgweb, and inject generated baseui |
|
183 | 188 | instance, additionally inject some extras into ui object |
|
184 | 189 | """ |
|
185 | self.__inject_extras(self.baseui, self.extras) | |
|
186 | return hgweb(str(self.repo_path), baseui=self.baseui) | |
|
190 | return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui) | |
|
187 | 191 | |
|
188 | 192 | |
|
189 | 193 | def __check_permission(self, action, user, repo_name): |
@@ -228,7 +232,7 b' class SimpleHg(object):' | |||
|
228 | 232 | return repo_name |
|
229 | 233 | |
|
230 | 234 | def __get_user(self, username): |
|
231 |
return User |
|
|
235 | return User.get_by_username(username) | |
|
232 | 236 | |
|
233 | 237 | def __get_action(self, environ): |
|
234 | 238 | """ |
@@ -257,7 +261,7 b' class SimpleHg(object):' | |||
|
257 | 261 | push requests""" |
|
258 | 262 | invalidate_cache('get_repo_cached_%s' % repo_name) |
|
259 | 263 | |
|
260 | def __inject_extras(self, baseui, extras={}): | |
|
264 | def __inject_extras(self, repo_path, baseui, extras={}): | |
|
261 | 265 | """ |
|
262 | 266 | Injects some extra params into baseui instance |
|
263 | 267 | |
@@ -267,7 +271,10 b' class SimpleHg(object):' | |||
|
267 | 271 | :param extras: dict with extra params to put into baseui |
|
268 | 272 | """ |
|
269 | 273 | |
|
270 |
hgrc = os.path.join( |
|
|
274 | hgrc = os.path.join(repo_path, '.hg', 'hgrc') | |
|
275 | ||
|
276 | # make our hgweb quiet so it doesn't print output | |
|
277 | baseui.setconfig('ui', 'quiet', 'true') | |
|
271 | 278 | |
|
272 | 279 | #inject some additional parameters that will be available in ui |
|
273 | 280 | #for hooks |
@@ -281,3 +288,4 b' class SimpleHg(object):' | |||
|
281 | 288 | for section in ui_sections: |
|
282 | 289 | for k, v in repoui.configitems(section): |
|
283 | 290 | baseui.setconfig(section, k, v) |
|
291 |
@@ -6,9 +6,21 b'' | |||
|
6 | 6 | Simple smtp mailer used in RhodeCode |
|
7 | 7 | |
|
8 | 8 | :created_on: Sep 13, 2010 |
|
9 |
:copyright: ( |
|
|
10 |
:license: |
|
|
9 | :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com> | |
|
10 | :license: GPLv3, see COPYING for more details. | |
|
11 | 11 | """ |
|
12 | # This program is free software: you can redistribute it and/or modify | |
|
13 | # it under the terms of the GNU General Public License as published by | |
|
14 | # the Free Software Foundation, either version 3 of the License, or | |
|
15 | # (at your option) any later version. | |
|
16 | # | |
|
17 | # This program is distributed in the hope that it will be useful, | |
|
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
20 | # GNU General Public License for more details. | |
|
21 | # | |
|
22 | # You should have received a copy of the GNU General Public License | |
|
23 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
12 | 24 | |
|
13 | 25 | import logging |
|
14 | 26 | import smtplib |
@@ -33,23 +33,21 b' from os.path import dirname as dn, join ' | |||
|
33 | 33 | |
|
34 | 34 | from paste.script.command import Command, BadCommand |
|
35 | 35 | |
|
36 |
from |
|
|
37 | ||
|
38 | from mercurial import ui, config, hg | |
|
39 | from mercurial.error import RepoError | |
|
36 | from mercurial import ui, config | |
|
40 | 37 | |
|
41 | 38 | from webhelpers.text import collapse, remove_formatting, strip_tags |
|
42 | 39 | |
|
40 | from vcs import get_backend | |
|
43 | 41 | from vcs.backends.base import BaseChangeset |
|
44 | 42 | from vcs.utils.lazy import LazyProperty |
|
45 |
from vcs import get_ |
|
|
43 | from vcs.utils.helpers import get_scm | |
|
44 | from vcs.exceptions import VCSError | |
|
46 | 45 | |
|
47 | 46 | from rhodecode.model import meta |
|
48 | 47 | from rhodecode.model.caching_query import FromCache |
|
49 | 48 | from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group, \ |
|
50 | 49 | RhodeCodeSettings |
|
51 | 50 | from rhodecode.model.repo import RepoModel |
|
52 | from rhodecode.model.user import UserModel | |
|
53 | 51 | |
|
54 | 52 | log = logging.getLogger(__name__) |
|
55 | 53 | |
@@ -111,11 +109,10 b' def action_logger(user, action, repo, ip' | |||
|
111 | 109 | sa = meta.Session() |
|
112 | 110 | |
|
113 | 111 | try: |
|
114 | um = UserModel() | |
|
115 | 112 | if hasattr(user, 'user_id'): |
|
116 | 113 | user_obj = user |
|
117 | 114 | elif isinstance(user, basestring): |
|
118 |
user_obj = |
|
|
115 | user_obj = User.get_by_username(user) | |
|
119 | 116 | else: |
|
120 | 117 | raise Exception('You have to provide user object or username') |
|
121 | 118 | |
@@ -183,37 +180,40 b' def get_repos(path, recursive=False):' | |||
|
183 | 180 | return _get_repos(path) |
|
184 | 181 | |
|
185 | 182 | |
|
186 |
def |
|
|
183 | def is_valid_repo(repo_name, base_path): | |
|
187 | 184 | """ |
|
188 | Check given path for existence of directory | |
|
185 | Returns True if given path is a valid repository False otherwise | |
|
189 | 186 | :param repo_name: |
|
190 | 187 | :param base_path: |
|
191 | 188 | |
|
192 |
:return |
|
|
189 | :return True: if given path is a valid repository | |
|
193 | 190 | """ |
|
194 |
|
|
|
195 | return False | |
|
196 | return True | |
|
197 | ||
|
198 | ||
|
199 | def check_repo(repo_name, base_path, verify=True): | |
|
200 | ||
|
201 | repo_path = os.path.join(base_path, repo_name) | |
|
191 | full_path = os.path.join(base_path, repo_name) | |
|
202 | 192 | |
|
203 | 193 | try: |
|
204 | if not check_repo_fast(repo_name, base_path): | |
|
205 |
|
|
|
206 | r = hg.repository(ui.ui(), repo_path) | |
|
207 | if verify: | |
|
208 | hg.verify(r) | |
|
209 | #here we hnow that repo exists it was verified | |
|
210 | log.info('%s repo is already created', repo_name) | |
|
194 | get_scm(full_path) | |
|
195 | return True | |
|
196 | except VCSError: | |
|
211 | 197 | return False |
|
212 | except RepoError: | |
|
213 | #it means that there is no valid repo there... | |
|
214 | log.info('%s repo is free for creation', repo_name) | |
|
198 | ||
|
199 | def is_valid_repos_group(repos_group_name, base_path): | |
|
200 | """ | |
|
201 | Returns True if given path is a repos group False otherwise | |
|
202 | ||
|
203 | :param repo_name: | |
|
204 | :param base_path: | |
|
205 | """ | |
|
206 | full_path = os.path.join(base_path, repos_group_name) | |
|
207 | ||
|
208 | # check if it's not a repo | |
|
209 | if is_valid_repo(repos_group_name, base_path): | |
|
210 | return False | |
|
211 | ||
|
212 | # check if it's a valid path | |
|
213 | if os.path.isdir(full_path): | |
|
215 | 214 | return True |
|
216 | 215 | |
|
216 | return False | |
|
217 | 217 | |
|
218 | 218 | def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): |
|
219 | 219 | while True: |
@@ -38,12 +38,14 b' from beaker.cache import cache_region, r' | |||
|
38 | 38 | |
|
39 | 39 | from vcs import get_backend |
|
40 | 40 | from vcs.utils.helpers import get_scm |
|
41 |
from vcs.exceptions import |
|
|
41 | from vcs.exceptions import VCSError | |
|
42 | 42 | from vcs.utils.lazy import LazyProperty |
|
43 | from vcs.nodes import FileNode | |
|
44 | 43 | |
|
44 | from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \ | |
|
45 | generate_api_key | |
|
45 | 46 | from rhodecode.lib.exceptions import UsersGroupsAssignedException |
|
46 | from rhodecode.lib import str2bool, json, safe_str, get_changeset_safe | |
|
47 | from rhodecode.lib.compat import json | |
|
48 | ||
|
47 | 49 | from rhodecode.model.meta import Base, Session |
|
48 | 50 | from rhodecode.model.caching_query import FromCache |
|
49 | 51 | |
@@ -279,17 +281,16 b' class User(Base, BaseModel):' | |||
|
279 | 281 | return self.__class__.__name__ |
|
280 | 282 | |
|
281 | 283 | @classmethod |
|
282 | def by_username(cls, username, case_insensitive=False): | |
|
284 | def get_by_username(cls, username, case_insensitive=False): | |
|
283 | 285 | if case_insensitive: |
|
284 |
return Session.query(cls).filter(cls.username.like(username)). |
|
|
286 | return Session.query(cls).filter(cls.username.like(username)).scalar() | |
|
285 | 287 | else: |
|
286 |
return Session.query(cls).filter(cls.username == username). |
|
|
288 | return Session.query(cls).filter(cls.username == username).scalar() | |
|
287 | 289 | |
|
288 | 290 | @classmethod |
|
289 | 291 | def get_by_api_key(cls, api_key): |
|
290 | 292 | return Session.query(cls).filter(cls.api_key == api_key).one() |
|
291 | 293 | |
|
292 | ||
|
293 | 294 | def update_lastlogin(self): |
|
294 | 295 | """Update user lastlogin""" |
|
295 | 296 | |
@@ -298,6 +299,25 b' class User(Base, BaseModel):' | |||
|
298 | 299 | Session.commit() |
|
299 | 300 | log.debug('updated user %s lastlogin', self.username) |
|
300 | 301 | |
|
302 | @classmethod | |
|
303 | def create(cls, form_data): | |
|
304 | from rhodecode.lib.auth import get_crypt_password | |
|
305 | ||
|
306 | try: | |
|
307 | new_user = cls() | |
|
308 | for k, v in form_data.items(): | |
|
309 | if k == 'password': | |
|
310 | v = get_crypt_password(v) | |
|
311 | setattr(new_user, k, v) | |
|
312 | ||
|
313 | new_user.api_key = generate_api_key(form_data['username']) | |
|
314 | Session.add(new_user) | |
|
315 | Session.commit() | |
|
316 | return new_user | |
|
317 | except: | |
|
318 | log.error(traceback.format_exc()) | |
|
319 | Session.rollback() | |
|
320 | raise | |
|
301 | 321 | |
|
302 | 322 | class UserLog(Base, BaseModel): |
|
303 | 323 | __tablename__ = 'user_logs' |
@@ -362,6 +382,7 b' class UsersGroup(Base, BaseModel):' | |||
|
362 | 382 | |
|
363 | 383 | Session.add(new_users_group) |
|
364 | 384 | Session.commit() |
|
385 | return new_users_group | |
|
365 | 386 | except: |
|
366 | 387 | log.error(traceback.format_exc()) |
|
367 | 388 | Session.rollback() |
@@ -465,7 +486,7 b' class Repository(Base, BaseModel):' | |||
|
465 | 486 | self.repo_id, self.repo_name) |
|
466 | 487 | |
|
467 | 488 | @classmethod |
|
468 | def by_repo_name(cls, repo_name): | |
|
489 | def get_by_repo_name(cls, repo_name): | |
|
469 | 490 | q = Session.query(cls).filter(cls.repo_name == repo_name) |
|
470 | 491 | |
|
471 | 492 | q = q.options(joinedload(Repository.fork))\ |
@@ -478,6 +499,17 b' class Repository(Base, BaseModel):' | |||
|
478 | 499 | def get_repo_forks(cls, repo_id): |
|
479 | 500 | return Session.query(cls).filter(Repository.fork_id == repo_id) |
|
480 | 501 | |
|
502 | @classmethod | |
|
503 | def base_path(cls): | |
|
504 | """ | |
|
505 | Returns base path when all repos are stored | |
|
506 | ||
|
507 | :param cls: | |
|
508 | """ | |
|
509 | q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/') | |
|
510 | q.options(FromCache("sql_cache_short", "repository_repo_path")) | |
|
511 | return q.one().ui_value | |
|
512 | ||
|
481 | 513 | @property |
|
482 | 514 | def just_name(self): |
|
483 | 515 | return self.repo_name.split(os.sep)[-1] |
@@ -549,6 +581,19 b' class Repository(Base, BaseModel):' | |||
|
549 | 581 | |
|
550 | 582 | return baseui |
|
551 | 583 | |
|
584 | @classmethod | |
|
585 | def is_valid(cls, repo_name): | |
|
586 | """ | |
|
587 | returns True if given repo name is a valid filesystem repository | |
|
588 | ||
|
589 | @param cls: | |
|
590 | @param repo_name: | |
|
591 | """ | |
|
592 | from rhodecode.lib.utils import is_valid_repo | |
|
593 | ||
|
594 | return is_valid_repo(repo_name, cls.base_path()) | |
|
595 | ||
|
596 | ||
|
552 | 597 | #========================================================================== |
|
553 | 598 | # SCM PROPERTIES |
|
554 | 599 | #========================================================================== |
@@ -32,6 +32,7 b' from formencode.validators import Unicod' | |||
|
32 | 32 | from pylons.i18n.translation import _ |
|
33 | 33 | from webhelpers.pylonslib.secure_form import authentication_token |
|
34 | 34 | |
|
35 | from rhodecode.config.routing import ADMIN_PREFIX | |
|
35 | 36 | from rhodecode.lib.utils import repo_name_slug |
|
36 | 37 | from rhodecode.lib.auth import authenticate, get_crypt_password |
|
37 | 38 | from rhodecode.lib.exceptions import LdapImportError |
@@ -70,8 +71,7 b' def ValidUsername(edit, old_data):' | |||
|
70 | 71 | old_un = UserModel().get(old_data.get('user_id')).username |
|
71 | 72 | |
|
72 | 73 | if old_un != value or not edit: |
|
73 |
if User |
|
|
74 | case_insensitive=True): | |
|
74 | if User.get_by_username(value, case_insensitive=True): | |
|
75 | 75 | raise formencode.Invalid(_('This username already ' |
|
76 | 76 | 'exists') , value, state) |
|
77 | 77 | |
@@ -206,7 +206,7 b' class ValidAuth(formencode.validators.Fa' | |||
|
206 | 206 | def validate_python(self, value, state): |
|
207 | 207 | password = value['password'] |
|
208 | 208 | username = value['username'] |
|
209 |
user = User |
|
|
209 | user = User.get_by_username(username) | |
|
210 | 210 | |
|
211 | 211 | if authenticate(username, password): |
|
212 | 212 | return value |
@@ -241,7 +241,7 b' def ValidRepoName(edit, old_data):' | |||
|
241 | 241 | repo_name = value.get('repo_name') |
|
242 | 242 | |
|
243 | 243 | slug = repo_name_slug(repo_name) |
|
244 |
if slug in [ |
|
|
244 | if slug in [ADMIN_PREFIX, '']: | |
|
245 | 245 | e_dict = {'repo_name': _('This repository name is disallowed')} |
|
246 | 246 | raise formencode.Invalid('', value, state, error_dict=e_dict) |
|
247 | 247 | |
@@ -283,6 +283,19 b' def ValidRepoName(edit, old_data):' | |||
|
283 | 283 | def ValidForkName(): |
|
284 | 284 | class _ValidForkName(formencode.validators.FancyValidator): |
|
285 | 285 | def to_python(self, value, state): |
|
286 | ||
|
287 | repo_name = value.get('fork_name') | |
|
288 | ||
|
289 | slug = repo_name_slug(repo_name) | |
|
290 | if slug in [ADMIN_PREFIX, '']: | |
|
291 | e_dict = {'repo_name': _('This repository name is disallowed')} | |
|
292 | raise formencode.Invalid('', value, state, error_dict=e_dict) | |
|
293 | ||
|
294 | if RepoModel().get_by_repo_name(repo_name): | |
|
295 | e_dict = {'fork_name':_('This repository ' | |
|
296 | 'already exists')} | |
|
297 | raise formencode.Invalid('', value, state, | |
|
298 | error_dict=e_dict) | |
|
286 | 299 | return value |
|
287 | 300 | return _ValidForkName |
|
288 | 301 |
@@ -102,7 +102,7 b' class RepoModel(BaseModel):' | |||
|
102 | 102 | for member, perm, member_type in form_data['perms_updates']: |
|
103 | 103 | if member_type == 'user': |
|
104 | 104 | r2p = self.sa.query(RepoToPerm)\ |
|
105 | .filter(RepoToPerm.user == User.by_username(member))\ | |
|
105 | .filter(RepoToPerm.user == User.get_by_username(member))\ | |
|
106 | 106 | .filter(RepoToPerm.repository == cur_repo)\ |
|
107 | 107 | .one() |
|
108 | 108 | |
@@ -127,7 +127,7 b' class RepoModel(BaseModel):' | |||
|
127 | 127 | if member_type == 'user': |
|
128 | 128 | r2p = RepoToPerm() |
|
129 | 129 | r2p.repository = cur_repo |
|
130 | r2p.user = User.by_username(member) | |
|
130 | r2p.user = User.get_by_username(member) | |
|
131 | 131 | |
|
132 | 132 | r2p.permission = self.sa.query(Permission)\ |
|
133 | 133 | .filter(Permission. |
@@ -147,7 +147,7 b' class RepoModel(BaseModel):' | |||
|
147 | 147 | #update current repo |
|
148 | 148 | for k, v in form_data.items(): |
|
149 | 149 | if k == 'user': |
|
150 | cur_repo.user = User.by_username(v) | |
|
150 | cur_repo.user = User.get_by_username(v) | |
|
151 | 151 | elif k == 'repo_name': |
|
152 | 152 | cur_repo.repo_name = form_data['repo_name_full'] |
|
153 | 153 | elif k == 'repo_group': |
@@ -192,6 +192,9 b' class RepoModel(BaseModel):' | |||
|
192 | 192 | if k == 'repo_group': |
|
193 | 193 | k = 'group_id' |
|
194 | 194 | |
|
195 | if k == 'description': | |
|
196 | v = v or repo_name | |
|
197 | ||
|
195 | 198 | setattr(new_repo, k, v) |
|
196 | 199 | |
|
197 | 200 | if fork: |
@@ -205,8 +208,7 b' class RepoModel(BaseModel):' | |||
|
205 | 208 | #create default permission |
|
206 | 209 | repo_to_perm = RepoToPerm() |
|
207 | 210 | default = 'repository.read' |
|
208 |
for p in User |
|
|
209 | cache=False).user_perms: | |
|
211 | for p in User.get_by_username('default').user_perms: | |
|
210 | 212 | if p.permission.permission_name.startswith('repository.'): |
|
211 | 213 | default = p.permission.permission_name |
|
212 | 214 | break |
@@ -218,8 +220,7 b' class RepoModel(BaseModel):' | |||
|
218 | 220 | .one().permission_id |
|
219 | 221 | |
|
220 | 222 | repo_to_perm.repository = new_repo |
|
221 |
repo_to_perm.user_id = User |
|
|
222 | .get_by_username('default', cache=False).user_id | |
|
223 | repo_to_perm.user_id = User.get_by_username('default').user_id | |
|
223 | 224 | |
|
224 | 225 | self.sa.add(repo_to_perm) |
|
225 | 226 | |
@@ -301,7 +302,7 b' class RepoModel(BaseModel):' | |||
|
301 | 302 | :param parent_id: |
|
302 | 303 | :param clone_uri: |
|
303 | 304 | """ |
|
304 |
from rhodecode.lib.utils import |
|
|
305 | from rhodecode.lib.utils import is_valid_repo | |
|
305 | 306 | |
|
306 | 307 | if new_parent_id: |
|
307 | 308 | paths = Group.get(new_parent_id).full_path.split(Group.url_sep()) |
@@ -312,7 +313,7 b' class RepoModel(BaseModel):' | |||
|
312 | 313 | repo_path = os.path.join(*map(lambda x:safe_str(x), |
|
313 | 314 | [self.repos_path, new_parent_path, repo_name])) |
|
314 | 315 | |
|
315 |
if |
|
|
316 | if is_valid_repo(repo_path, self.repos_path) is False: | |
|
316 | 317 | log.info('creating repo %s in %s @ %s', repo_name, repo_path, |
|
317 | 318 | clone_uri) |
|
318 | 319 | backend = get_backend(alias) |
@@ -332,8 +333,8 b' class RepoModel(BaseModel):' | |||
|
332 | 333 | old_path = os.path.join(self.repos_path, old) |
|
333 | 334 | new_path = os.path.join(self.repos_path, new) |
|
334 | 335 | if os.path.isdir(new_path): |
|
335 |
raise Exception('Was trying to rename to already existing dir %s' |
|
|
336 |
|
|
|
336 | raise Exception('Was trying to rename to already existing dir %s' \ | |
|
337 | % new_path) | |
|
337 | 338 | shutil.move(old_path, new_path) |
|
338 | 339 | |
|
339 | 340 | def __delete_repo(self, repo): |
@@ -22,7 +22,6 b'' | |||
|
22 | 22 | # |
|
23 | 23 | # You should have received a copy of the GNU General Public License |
|
24 | 24 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
25 | import os | |
|
26 | 25 | import time |
|
27 | 26 | import traceback |
|
28 | 27 | import logging |
@@ -41,9 +40,8 b' from rhodecode.lib.auth import HasRepoPe' | |||
|
41 | 40 | from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \ |
|
42 | 41 | action_logger, EmptyChangeset |
|
43 | 42 | from rhodecode.model import BaseModel |
|
44 | from rhodecode.model.user import UserModel | |
|
45 | 43 | from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \ |
|
46 | UserFollowing, UserLog | |
|
44 | UserFollowing, UserLog, User | |
|
47 | 45 | |
|
48 | 46 | log = logging.getLogger(__name__) |
|
49 | 47 | |
@@ -283,7 +281,7 b' class ScmModel(BaseModel):' | |||
|
283 | 281 | return f is not None |
|
284 | 282 | |
|
285 | 283 | def is_following_user(self, username, user_id, cache=False): |
|
286 |
u = User |
|
|
284 | u = User.get_by_username(username) | |
|
287 | 285 | |
|
288 | 286 | f = self.sa.query(UserFollowing)\ |
|
289 | 287 | .filter(UserFollowing.follows_user == u)\ |
@@ -293,20 +291,24 b' class ScmModel(BaseModel):' | |||
|
293 | 291 | |
|
294 | 292 | def get_followers(self, repo_id): |
|
295 | 293 | if not isinstance(repo_id, int): |
|
296 | repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id') | |
|
294 | repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id') | |
|
297 | 295 | |
|
298 | 296 | return self.sa.query(UserFollowing)\ |
|
299 | 297 | .filter(UserFollowing.follows_repo_id == repo_id).count() |
|
300 | 298 | |
|
301 | 299 | def get_forks(self, repo_id): |
|
302 | 300 | if not isinstance(repo_id, int): |
|
303 | repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id') | |
|
301 | repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id') | |
|
304 | 302 | |
|
305 | 303 | return self.sa.query(Repository)\ |
|
306 | 304 | .filter(Repository.fork_id == repo_id).count() |
|
307 | 305 | |
|
308 | 306 | def pull_changes(self, repo_name, username): |
|
309 | dbrepo = Repository.by_repo_name(repo_name) | |
|
307 | dbrepo = Repository.get_by_repo_name(repo_name) | |
|
308 | clone_uri = dbrepo.clone_uri | |
|
309 | if not clone_uri: | |
|
310 | raise Exception("This repository doesn't have a clone uri") | |
|
311 | ||
|
310 | 312 | repo = dbrepo.scm_instance |
|
311 | 313 | try: |
|
312 | 314 | extras = {'ip': '', |
@@ -318,13 +320,12 b' class ScmModel(BaseModel):' | |||
|
318 | 320 | for k, v in extras.items(): |
|
319 | 321 | repo._repo.ui.setconfig('rhodecode_extras', k, v) |
|
320 | 322 | |
|
321 |
repo.pull( |
|
|
323 | repo.pull(clone_uri) | |
|
322 | 324 | self.mark_for_invalidation(repo_name) |
|
323 | 325 | except: |
|
324 | 326 | log.error(traceback.format_exc()) |
|
325 | 327 | raise |
|
326 | 328 | |
|
327 | ||
|
328 | 329 | def commit_change(self, repo, repo_name, cs, user, author, message, content, |
|
329 | 330 | f_path): |
|
330 | 331 | |
@@ -360,12 +361,12 b' class ScmModel(BaseModel):' | |||
|
360 | 361 | from vcs.backends.git import GitInMemoryChangeset as IMC |
|
361 | 362 | # decoding here will force that we have proper encoded values |
|
362 | 363 | # in any other case this will throw exceptions and deny commit |
|
363 | ||
|
364 | if isinstance(content,(basestring,)): | |
|
364 | ||
|
365 | if isinstance(content, (basestring,)): | |
|
365 | 366 | content = safe_str(content) |
|
366 | elif isinstance(content,file): | |
|
367 | elif isinstance(content, file): | |
|
367 | 368 | content = content.read() |
|
368 | ||
|
369 | ||
|
369 | 370 | message = safe_str(message) |
|
370 | 371 | path = safe_str(f_path) |
|
371 | 372 | author = safe_str(author) |
@@ -28,6 +28,7 b' import traceback' | |||
|
28 | 28 | |
|
29 | 29 | from pylons.i18n.translation import _ |
|
30 | 30 | |
|
31 | from rhodecode.lib import safe_unicode | |
|
31 | 32 | from rhodecode.model import BaseModel |
|
32 | 33 | from rhodecode.model.caching_query import FromCache |
|
33 | 34 | from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \ |
@@ -111,7 +112,7 b' class UserModel(BaseModel):' | |||
|
111 | 112 | new_user.api_key = generate_api_key(username) |
|
112 | 113 | new_user.email = attrs['email'] |
|
113 | 114 | new_user.active = True |
|
114 | new_user.ldap_dn = user_dn | |
|
115 | new_user.ldap_dn = safe_unicode(user_dn) | |
|
115 | 116 | new_user.name = attrs['name'] |
|
116 | 117 | new_user.lastname = attrs['lastname'] |
|
117 | 118 |
@@ -1466,7 +1466,7 b' clear:both;' | |||
|
1466 | 1466 | overflow:hidden; |
|
1467 | 1467 | text-align:right; |
|
1468 | 1468 | margin:0; |
|
1469 |
padding:10px 14px |
|
|
1469 | padding:10px 14px 0px 5px; | |
|
1470 | 1470 | } |
|
1471 | 1471 | |
|
1472 | 1472 | #quick_login div.form div.links { |
@@ -2555,7 +2555,7 b' border-top:1px solid #DDD;' | |||
|
2555 | 2555 | border-left:1px solid #c6c6c6; |
|
2556 | 2556 | border-right:1px solid #DDD; |
|
2557 | 2557 | border-bottom:1px solid #c6c6c6; |
|
2558 | color:#515151; | |
|
2558 | color:#515151 !important; | |
|
2559 | 2559 | outline:none; |
|
2560 | 2560 | margin:0; |
|
2561 | 2561 | padding:6px 12px; |
@@ -246,7 +246,7 b' class TestLoginController(TestController' | |||
|
246 | 246 | |
|
247 | 247 | # GOOD KEY |
|
248 | 248 | |
|
249 | key = User.by_username(username).api_key | |
|
249 | key = User.get_by_username(username).api_key | |
|
250 | 250 | |
|
251 | 251 | response = self.app.get(url(controller='login', |
|
252 | 252 | action='password_reset_confirmation', |
@@ -41,7 +41,7 b' class TestSummaryController(TestControll' | |||
|
41 | 41 | |
|
42 | 42 | |
|
43 | 43 | def _enable_stats(self): |
|
44 | r = Repository.by_repo_name(HG_REPO) | |
|
44 | r = Repository.get_by_repo_name(HG_REPO) | |
|
45 | 45 | r.enable_statistics = True |
|
46 | 46 | self.sa.add(r) |
|
47 | 47 | self.sa.commit() |
@@ -102,7 +102,7 b' def test_files_walk(limit=100):' | |||
|
102 | 102 | |
|
103 | 103 | repo = vcs.get_repo(jn(PROJECT_PATH, PROJECT)) |
|
104 | 104 | |
|
105 |
from rhodecode.lib. |
|
|
105 | from rhodecode.lib.compat import OrderedSet | |
|
106 | 106 | |
|
107 | 107 | paths_ = OrderedSet(['']) |
|
108 | 108 | try: |
@@ -6,13 +6,28 b'' | |||
|
6 | 6 | Test suite for making push/pull operations |
|
7 | 7 | |
|
8 | 8 | :created_on: Dec 30, 2010 |
|
9 | :copyright: (c) 2010 by marcink. | |
|
10 |
:license: |
|
|
9 | :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com> | |
|
10 | :license: GPLv3, see COPYING for more details. | |
|
11 | 11 | """ |
|
12 | # This program is free software: you can redistribute it and/or modify | |
|
13 | # it under the terms of the GNU General Public License as published by | |
|
14 | # the Free Software Foundation, either version 3 of the License, or | |
|
15 | # (at your option) any later version. | |
|
16 | # | |
|
17 | # This program is distributed in the hope that it will be useful, | |
|
18 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
19 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
20 | # GNU General Public License for more details. | |
|
21 | # | |
|
22 | # You should have received a copy of the GNU General Public License | |
|
23 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
12 | 24 | |
|
13 | 25 | import os |
|
26 | import time | |
|
27 | import sys | |
|
14 | 28 | import shutil |
|
15 | 29 | import logging |
|
30 | ||
|
16 | 31 | from os.path import join as jn |
|
17 | 32 | from os.path import dirname as dn |
|
18 | 33 | |
@@ -26,7 +41,7 b' from sqlalchemy import engine_from_confi' | |||
|
26 | 41 | from rhodecode.lib.utils import add_cache |
|
27 | 42 | from rhodecode.model import init_model |
|
28 | 43 | from rhodecode.model import meta |
|
29 | from rhodecode.model.db import User, Repository | |
|
44 | from rhodecode.model.db import User, Repository, UserLog | |
|
30 | 45 | from rhodecode.lib.auth import get_crypt_password |
|
31 | 46 | |
|
32 | 47 | from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO |
@@ -41,7 +56,8 b' add_cache(conf)' | |||
|
41 | 56 | USER = 'test_admin' |
|
42 | 57 | PASS = 'test12' |
|
43 | 58 | HOST = '127.0.0.1:5000' |
|
44 | DEBUG = True | |
|
59 | DEBUG = True if sys.argv[1:] else False | |
|
60 | print 'DEBUG:', DEBUG | |
|
45 | 61 | log = logging.getLogger(__name__) |
|
46 | 62 | |
|
47 | 63 | |
@@ -64,28 +80,44 b' class Command(object):' | |||
|
64 | 80 | print stdout, stderr |
|
65 | 81 | return stdout, stderr |
|
66 | 82 | |
|
83 | ||
|
84 | def test_wrapp(func): | |
|
85 | ||
|
86 | def __wrapp(*args, **kwargs): | |
|
87 | print '>>>%s' % func.__name__ | |
|
88 | try: | |
|
89 | res = func(*args, **kwargs) | |
|
90 | except Exception, e: | |
|
91 | print ('###############\n-' | |
|
92 | '--%s failed %s--\n' | |
|
93 | '###############\n' % (func.__name__, e)) | |
|
94 | sys.exit() | |
|
95 | print '++OK++' | |
|
96 | return res | |
|
97 | return __wrapp | |
|
98 | ||
|
67 | 99 | def get_session(): |
|
68 | 100 | engine = engine_from_config(conf, 'sqlalchemy.db1.') |
|
69 | 101 | init_model(engine) |
|
70 |
sa = meta.Session |
|
|
102 | sa = meta.Session | |
|
71 | 103 | return sa |
|
72 | 104 | |
|
73 | 105 | |
|
74 | 106 | def create_test_user(force=True): |
|
75 | print 'creating test user' | |
|
107 | print '\tcreating test user' | |
|
76 | 108 | sa = get_session() |
|
77 | 109 | |
|
78 | 110 | user = sa.query(User).filter(User.username == USER).scalar() |
|
79 | 111 | |
|
80 | 112 | if force and user is not None: |
|
81 | print 'removing current user' | |
|
113 | print '\tremoving current user' | |
|
82 | 114 | for repo in sa.query(Repository).filter(Repository.user == user).all(): |
|
83 | 115 | sa.delete(repo) |
|
84 | 116 | sa.delete(user) |
|
85 | 117 | sa.commit() |
|
86 | 118 | |
|
87 | 119 | if user is None or force: |
|
88 | print 'creating new one' | |
|
120 | print '\tcreating new one' | |
|
89 | 121 | new_usr = User() |
|
90 | 122 | new_usr.username = USER |
|
91 | 123 | new_usr.password = get_crypt_password(PASS) |
@@ -97,7 +129,7 b' def create_test_user(force=True):' | |||
|
97 | 129 | sa.add(new_usr) |
|
98 | 130 | sa.commit() |
|
99 | 131 | |
|
100 | print 'done' | |
|
132 | print '\tdone' | |
|
101 | 133 | |
|
102 | 134 | |
|
103 | 135 | def create_test_repo(force=True): |
@@ -112,7 +144,7 b' def create_test_repo(force=True):' | |||
|
112 | 144 | repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar() |
|
113 | 145 | |
|
114 | 146 | if repo is None: |
|
115 | print 'repo not found creating' | |
|
147 | print '\trepo not found creating' | |
|
116 | 148 | |
|
117 | 149 | form_data = {'repo_name':HG_REPO, |
|
118 | 150 | 'repo_type':'hg', |
@@ -126,19 +158,27 b' def create_test_repo(force=True):' | |||
|
126 | 158 | def set_anonymous_access(enable=True): |
|
127 | 159 | sa = get_session() |
|
128 | 160 | user = sa.query(User).filter(User.username == 'default').one() |
|
161 | sa.expire(user) | |
|
129 | 162 | user.active = enable |
|
130 | 163 | sa.add(user) |
|
131 | 164 | sa.commit() |
|
165 | sa.remove() | |
|
166 | import time;time.sleep(3) | |
|
167 | print '\tanonymous access is now:', enable | |
|
168 | ||
|
132 | 169 | |
|
133 | 170 | def get_anonymous_access(): |
|
134 | 171 | sa = get_session() |
|
135 |
|
|
|
172 | obj1 = sa.query(User).filter(User.username == 'default').one() | |
|
173 | sa.expire(obj1) | |
|
174 | return obj1.active | |
|
136 | 175 | |
|
137 | 176 | |
|
138 | 177 | #============================================================================== |
|
139 | 178 | # TESTS |
|
140 | 179 | #============================================================================== |
|
141 | def test_clone(no_errors=False): | |
|
180 | @test_wrapp | |
|
181 | def test_clone_with_credentials(no_errors=False): | |
|
142 | 182 | cwd = path = jn(TESTS_TMP_PATH, HG_REPO) |
|
143 | 183 | |
|
144 | 184 | try: |
@@ -148,6 +188,12 b' def test_clone(no_errors=False):' | |||
|
148 | 188 | except OSError: |
|
149 | 189 | raise |
|
150 | 190 | |
|
191 | print '\tchecking if anonymous access is enabled' | |
|
192 | anonymous_access = get_anonymous_access() | |
|
193 | if anonymous_access: | |
|
194 | print '\tenabled, disabling it ' | |
|
195 | set_anonymous_access(enable=False) | |
|
196 | time.sleep(1) | |
|
151 | 197 | |
|
152 | 198 | clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \ |
|
153 | 199 | {'user':USER, |
@@ -163,8 +209,8 b' def test_clone(no_errors=False):' | |||
|
163 | 209 | assert """abort""" not in stderr , 'got error from clone' |
|
164 | 210 | |
|
165 | 211 | |
|
166 | ||
|
167 |
def test_clone_anonymous |
|
|
212 | @test_wrapp | |
|
213 | def test_clone_anonymous(): | |
|
168 | 214 | cwd = path = jn(TESTS_TMP_PATH, HG_REPO) |
|
169 | 215 | |
|
170 | 216 | try: |
@@ -175,11 +221,12 b' def test_clone_anonymous_ok():' | |||
|
175 | 221 | raise |
|
176 | 222 | |
|
177 | 223 | |
|
178 | print 'checking if anonymous access is enabled' | |
|
224 | print '\tchecking if anonymous access is enabled' | |
|
179 | 225 | anonymous_access = get_anonymous_access() |
|
180 | 226 | if not anonymous_access: |
|
181 | print 'not enabled, enabling it ' | |
|
227 | print '\tnot enabled, enabling it ' | |
|
182 | 228 | set_anonymous_access(enable=True) |
|
229 | time.sleep(1) | |
|
183 | 230 | |
|
184 | 231 | clone_url = 'http://%(host)s/%(cloned_repo)s %(dest)s' % \ |
|
185 | 232 | {'user':USER, |
@@ -189,18 +236,16 b' def test_clone_anonymous_ok():' | |||
|
189 | 236 | 'dest':path} |
|
190 | 237 | |
|
191 | 238 | stdout, stderr = Command(cwd).execute('hg clone', clone_url) |
|
192 | print stdout, stderr | |
|
193 | ||
|
194 | 239 | |
|
195 | 240 | assert """adding file changes""" in stdout, 'no messages about cloning' |
|
196 | 241 | assert """abort""" not in stderr , 'got error from clone' |
|
197 | 242 | |
|
198 | 243 | #disable if it was enabled |
|
199 | 244 | if not anonymous_access: |
|
200 | print 'disabling anonymous access' | |
|
245 | print '\tdisabling anonymous access' | |
|
201 | 246 | set_anonymous_access(enable=False) |
|
202 | 247 | |
|
203 | ||
|
248 | @test_wrapp | |
|
204 | 249 | def test_clone_wrong_credentials(): |
|
205 | 250 | cwd = path = jn(TESTS_TMP_PATH, HG_REPO) |
|
206 | 251 | |
@@ -211,6 +256,11 b' def test_clone_wrong_credentials():' | |||
|
211 | 256 | except OSError: |
|
212 | 257 | raise |
|
213 | 258 | |
|
259 | print '\tchecking if anonymous access is enabled' | |
|
260 | anonymous_access = get_anonymous_access() | |
|
261 | if anonymous_access: | |
|
262 | print '\tenabled, disabling it ' | |
|
263 | set_anonymous_access(enable=False) | |
|
214 | 264 | |
|
215 | 265 | clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \ |
|
216 | 266 | {'user':USER + 'error', |
@@ -221,12 +271,14 b' def test_clone_wrong_credentials():' | |||
|
221 | 271 | |
|
222 | 272 | stdout, stderr = Command(cwd).execute('hg clone', clone_url) |
|
223 | 273 | |
|
224 |
|
|
|
274 | if not """abort: authorization failed""" in stderr: | |
|
275 | raise Exception('Failure') | |
|
225 | 276 | |
|
226 | ||
|
277 | @test_wrapp | |
|
227 | 278 | def test_pull(): |
|
228 | 279 | pass |
|
229 | 280 | |
|
281 | @test_wrapp | |
|
230 | 282 | def test_push_modify_file(f_name='setup.py'): |
|
231 | 283 | cwd = path = jn(TESTS_TMP_PATH, HG_REPO) |
|
232 | 284 | modified_file = jn(TESTS_TMP_PATH, HG_REPO, f_name) |
@@ -239,10 +291,11 b" def test_push_modify_file(f_name='setup." | |||
|
239 | 291 | |
|
240 | 292 | Command(cwd).execute('hg push %s' % jn(TESTS_TMP_PATH, HG_REPO)) |
|
241 | 293 | |
|
294 | @test_wrapp | |
|
242 | 295 | def test_push_new_file(commits=15, with_clone=True): |
|
243 | 296 | |
|
244 | 297 | if with_clone: |
|
245 | test_clone(no_errors=True) | |
|
298 | test_clone_with_credentials(no_errors=True) | |
|
246 | 299 | |
|
247 | 300 | cwd = path = jn(TESTS_TMP_PATH, HG_REPO) |
|
248 | 301 | added_file = jn(path, '%ssetupążźć.py' % _RandomNameSequence().next()) |
@@ -269,6 +322,7 b' def test_push_new_file(commits=15, with_' | |||
|
269 | 322 | |
|
270 | 323 | Command(cwd).execute('hg push --verbose --debug %s' % push_url) |
|
271 | 324 | |
|
325 | @test_wrapp | |
|
272 | 326 | def test_push_wrong_credentials(): |
|
273 | 327 | cwd = path = jn(TESTS_TMP_PATH, HG_REPO) |
|
274 | 328 | clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \ |
@@ -288,6 +342,7 b' def test_push_wrong_credentials():' | |||
|
288 | 342 | |
|
289 | 343 | Command(cwd).execute('hg push %s' % clone_url) |
|
290 | 344 | |
|
345 | @test_wrapp | |
|
291 | 346 | def test_push_wrong_path(): |
|
292 | 347 | cwd = path = jn(TESTS_TMP_PATH, HG_REPO) |
|
293 | 348 | added_file = jn(path, 'somefile.py') |
@@ -295,7 +350,7 b' def test_push_wrong_path():' | |||
|
295 | 350 | try: |
|
296 | 351 | shutil.rmtree(path, ignore_errors=True) |
|
297 | 352 | os.makedirs(path) |
|
298 | print 'made dirs %s' % jn(path) | |
|
353 | print '\tmade dirs %s' % jn(path) | |
|
299 | 354 | except OSError: |
|
300 | 355 | raise |
|
301 | 356 | |
@@ -318,20 +373,40 b' def test_push_wrong_path():' | |||
|
318 | 373 | 'dest':jn(TESTS_TMP_PATH, HG_REPO)} |
|
319 | 374 | |
|
320 | 375 | stdout, stderr = Command(cwd).execute('hg push %s' % clone_url) |
|
321 |
|
|
|
376 | if not """abort: HTTP Error 403: Forbidden""" in stderr: | |
|
377 | raise Exception('Failure') | |
|
378 | ||
|
379 | @test_wrapp | |
|
380 | def get_logs(): | |
|
381 | sa = get_session() | |
|
382 | return len(sa.query(UserLog).all()) | |
|
383 | ||
|
384 | @test_wrapp | |
|
385 | def test_logs(initial): | |
|
386 | sa = get_session() | |
|
387 | logs = sa.query(UserLog).all() | |
|
388 | operations = 7 | |
|
389 | if initial + operations != len(logs): | |
|
390 | raise Exception("missing number of logs %s vs %s" % (initial, len(logs))) | |
|
322 | 391 | |
|
323 | 392 | |
|
324 | 393 | if __name__ == '__main__': |
|
325 | 394 | create_test_user(force=False) |
|
326 | 395 | create_test_repo() |
|
327 | #test_push_modify_file() | |
|
328 | #test_clone() | |
|
329 | #test_clone_anonymous_ok() | |
|
396 | ||
|
397 | initial_logs = get_logs() | |
|
330 | 398 | |
|
331 | #test_clone_wrong_credentials() | |
|
399 | # test_push_modify_file() | |
|
400 | test_clone_with_credentials() | |
|
401 | test_clone_wrong_credentials() | |
|
332 | 402 | |
|
333 | test_pull() | |
|
403 | ||
|
334 | 404 | test_push_new_file(commits=2, with_clone=True) |
|
335 | 405 | |
|
336 | #test_push_wrong_path() | |
|
337 |
|
|
|
406 | test_clone_anonymous() | |
|
407 | test_push_wrong_path() | |
|
408 | ||
|
409 | ||
|
410 | test_push_wrong_credentials() | |
|
411 | ||
|
412 | test_logs(initial_logs) |
@@ -7,9 +7,21 b'' | |||
|
7 | 7 | Package for testing various lib/helper functions in rhodecode |
|
8 | 8 | |
|
9 | 9 | :created_on: Jun 9, 2011 |
|
10 |
:copyright: ( |
|
|
11 |
:license: |
|
|
10 | :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com> | |
|
11 | :license: GPLv3, see COPYING for more details. | |
|
12 | 12 | """ |
|
13 | # This program is free software: you can redistribute it and/or modify | |
|
14 | # it under the terms of the GNU General Public License as published by | |
|
15 | # the Free Software Foundation, either version 3 of the License, or | |
|
16 | # (at your option) any later version. | |
|
17 | # | |
|
18 | # This program is distributed in the hope that it will be useful, | |
|
19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
21 | # GNU General Public License for more details. | |
|
22 | # | |
|
23 | # You should have received a copy of the GNU General Public License | |
|
24 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
13 | 25 | |
|
14 | 26 | |
|
15 | 27 |
@@ -12,6 +12,7 b' if py_version < (2, 5):' | |||
|
12 | 12 | requirements = [ |
|
13 | 13 | "Pylons==1.0.0", |
|
14 | 14 | "WebHelpers>=1.2", |
|
15 | "formencode==1.2.4", | |
|
15 | 16 | "SQLAlchemy>=0.7.2,<0.8", |
|
16 | 17 | "Mako>=0.4.2", |
|
17 | 18 | "pygments>=1.4", |
@@ -22,12 +23,12 b' requirements = [' | |||
|
22 | 23 | "python-dateutil>=1.5.0,<2.0.0", |
|
23 | 24 | "dulwich>=0.8.0", |
|
24 | 25 | "vcs>=0.2.1.dev", |
|
25 |
"webob==1.0.8" |
|
|
26 | "webob==1.0.8" | |
|
26 | 27 | ] |
|
27 | 28 | |
|
28 | 29 | dependency_links = [ |
|
29 |
"https://secure.rhodecode.org/vcs/archive/default.zip#egg=vcs-0.2. |
|
|
30 |
"https://bitbucket.org/marcinkuzminski/vcs/get/default.zip#egg=vcs-0.2. |
|
|
30 | "https://secure.rhodecode.org/vcs/archive/default.zip#egg=vcs-0.2.2.dev", | |
|
31 | "https://bitbucket.org/marcinkuzminski/vcs/get/default.zip#egg=vcs-0.2.2.dev", | |
|
31 | 32 | ] |
|
32 | 33 | |
|
33 | 34 | classifiers = ['Development Status :: 4 - Beta', |
@@ -23,6 +23,7 b' pdebug = false' | |||
|
23 | 23 | #smtp_password = |
|
24 | 24 | #smtp_port = |
|
25 | 25 | #smtp_use_tls = false |
|
26 | #smtp_use_ssl = true | |
|
26 | 27 | |
|
27 | 28 | [server:main] |
|
28 | 29 | ##nr of threads to spawn |
@@ -49,6 +50,7 b' app_instance_uuid = develop-test' | |||
|
49 | 50 | cut_off_limit = 256000 |
|
50 | 51 | force_https = false |
|
51 | 52 | commit_parse_limit = 25 |
|
53 | use_gravatar = true | |
|
52 | 54 | |
|
53 | 55 | #################################### |
|
54 | 56 | ### CELERY CONFIG #### |
@@ -93,7 +95,6 b' beaker.cache.short_term.expire=60' | |||
|
93 | 95 | beaker.cache.long_term.type=memory |
|
94 | 96 | beaker.cache.long_term.expire=36000 |
|
95 | 97 | |
|
96 | ||
|
97 | 98 | beaker.cache.sql_cache_short.type=memory |
|
98 | 99 | beaker.cache.sql_cache_short.expire=10 |
|
99 | 100 | |
@@ -150,13 +151,13 b' sqlalchemy.convert_unicode = true' | |||
|
150 | 151 | ### LOGGING CONFIGURATION #### |
|
151 | 152 | ################################ |
|
152 | 153 | [loggers] |
|
153 | keys = root, routes, rhodecode, sqlalchemy,beaker,templates | |
|
154 | keys = root, routes, rhodecode, sqlalchemy, beaker, templates | |
|
154 | 155 | |
|
155 | 156 | [handlers] |
|
156 | 157 | keys = console |
|
157 | 158 | |
|
158 | 159 | [formatters] |
|
159 | keys = generic,color_formatter | |
|
160 | keys = generic, color_formatter | |
|
160 | 161 | |
|
161 | 162 | ############# |
|
162 | 163 | ## LOGGERS ## |
@@ -167,9 +168,10 b' handlers = console' | |||
|
167 | 168 | |
|
168 | 169 | [logger_routes] |
|
169 | 170 | level = ERROR |
|
170 |
handlers = |
|
|
171 | handlers = | |
|
171 | 172 | qualname = routes.middleware |
|
172 | 173 | # "level = DEBUG" logs the route matched and routing variables. |
|
174 | propagate = 1 | |
|
173 | 175 | |
|
174 | 176 | [logger_beaker] |
|
175 | 177 | level = DEBUG |
@@ -185,9 +187,9 b' propagate = 1' | |||
|
185 | 187 | |
|
186 | 188 | [logger_rhodecode] |
|
187 | 189 | level = ERROR |
|
188 |
handlers = |
|
|
190 | handlers = | |
|
189 | 191 | qualname = rhodecode |
|
190 |
propagate = |
|
|
192 | propagate = 1 | |
|
191 | 193 | |
|
192 | 194 | [logger_sqlalchemy] |
|
193 | 195 | level = ERROR |
@@ -203,7 +205,7 b' propagate = 0' | |||
|
203 | 205 | class = StreamHandler |
|
204 | 206 | args = (sys.stderr,) |
|
205 | 207 | level = NOTSET |
|
206 |
formatter = |
|
|
208 | formatter = generic | |
|
207 | 209 | |
|
208 | 210 | ################ |
|
209 | 211 | ## FORMATTERS ## |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now