##// END OF EJS Templates
release: Merge default into stable for release preparation
marcink -
r1610:1f5086cf merge stable
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,14 b''
1 .. _git-lfs-loc:
2
3 Change the |git| LFS storage Location
4 -------------------------------------
5
6 |RCE| manages |git| LFS files from the following default location
7 :file:`/home/{user}/repos/.cache/lfs_store`. If you wish to change this, use
8 the following steps:
9
10 1. Open :menuselection:`Admin --> Settings --> VCS` as super-admin.
11
12 In section called `Git Settings` you can change where the LFS
13 objects should be stored.
14
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,162 b''
1 |RCE| 4.7.0 |RNS|
2 -----------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2017-04-08
8
9
10 New Features
11 ^^^^^^^^^^^^
12
13 - Git: added support for Git LFS v2 protocol. RhodeCode now supports both
14 Mercurial Largefiles, and Git LFS for storing large binaries.
15 - Largefiles: detect Git LFS or Mercurial Largefiles objects in UI.
16 Those are now available for downloading together with showing their size.
17 - Files: Jupyter notebooks will be now rendered inside the file view. Including
18 MatJax support, and relative images.
19 - Files: render images inside the file view.
20 Instead of displaying binary message, render images icons and gifs
21 inside the file view page.
22 - Files: relative ULR support inside rendered files. It's now possible to
23 write Markup files and relative links will be handled from the RhodeCode
24 instance itself. Adds basic wiki functionality.
25 - Files: allow to show inline pdf in browser using embedded files from source code.
26 - Annotation: added shortcut links to browse the annotation view with previous
27 commits. Allows browsing history for each line from annotation view.
28 - Pull Requests: add explicit close action instead of close with status from
29 status selector. This allows closing of approved or rejected
30 pull requests, without performing a merge action.
31 - Authentication: LDAP now has an option to sync LDA groups using two
32 distinct ways. Either using rfc2307 or rfc2307bis. Increases compatibility
33 with different OpenLDAP and AD servers.
34 - Slack: updated slack integration to use the attachments for nicer formatting.
35 Added number of commits inside the message, changed UI for all Slack events.
36 - Authentication (EE edition only): added repository scope for VCS type auth
37 tokens. Each token can be now bound to particular repository for added security.
38 - User administration: added audit page to allow showing single user actions.
39 - API: implemented `get_user_audit_logs` method to fetch audit logs via API endpoint.
40 - User administration: It's now possible to edit user group membership from
41 user view.
42 - User groups administration: added managing and showing the group
43 synchronization in UI. It's now possible to enable manual group syncing on
44 already existing user groups from external sources such as LDAP/AD.
45 - Repositories: added new strip view allowing removing commits from repositories
46 via web interface for repository administrators.
47 - System Info: added info about workers and worker type.
48 Added more details about CPU. Expose workers of VCSServer in system info data.
49 Detect database migration errors.
50
51
52 General
53 ^^^^^^^
54
55 - Core: ported many views into pure pyramid code with python3.6 compatibility.
56 - Core: removed deprecated Pyro4 backend from Enterprise code.
57 - Maintenance: implemented maintenance view for Mercurial and GIT repositories.
58 For HG it will run `hg verify`, and for GIT a `git gc` command.
59 - Notifications: different approach with fixed/standard container. Floating
60 notifications no longer hide the menu when browsed on top of the page.
61 Also added option to remove single elements from stacked notifications.
62 - VCS server: exception-handling: better handling of remote exception and logging.
63 - VCS server: propagate hooks tracebacks to VCS server for easier debugging.
64 - Core: prevent `httplib3` logs to spam internal RhodeCode logs.
65 It often confuses people looking at those entries, misleading during debug.
66 - Mercurial: allow editing Largefiles store location from web interface.
67 - Git: allow editing GIT LFS store location from web interface.
68 - API: add get_method API call. This allows showing the method and it's parameter
69 from the CLI without reading the documentation.
70 In addition use it's mechanics to propose users other methods with close names
71 if the calling method is not found.
72 - UI: add timezone info into tooltips.
73 - Dependencies: bumped pyramid to 1.7.4
74 - Dependencies: bumped Mercurial version to 4.1.2
75
76
77 Security
78 ^^^^^^^^
79
80 - Hooks: added changes to propagate commit metadata on pre-push.
81 This allows easier implementation of checking hooks such as branch protection.
82 - Hooks: added new pretx hook to allow mercurial checks such as protected
83 branches, or force push.
84 - Auth: give owner of user group proper admin permissions to the user group.
85 This makes the behaviour consistent with repositories and repository groups.
86 And allows delegation of administration of those to other users.
87 - Password reset: strengthen security on password reset logic.
88 Generate token that has special password reset role.
89 Set 10 minute expiration for the token.
90 Add some logic to prevent brute forcing attacks.
91 Use more implicit messages to prevent user email discovery attacks.
92 - Core: added checks for password change for authenticated users in pure
93 Pyramid views. 2 views were still available and not forcing users to change
94 their passwords.
95 - Auth tokens: removed builtin auth-token for users.
96 Builtin token were non-removable, and always generated for new users. This
97 wasn't best practice for security as some users are strictly not allowed to
98 use tokens. From now on new users needs a new token generation in case they
99 want to use token based authentication.
100 - Auth tokens: don't generate builtin token for new users.
101 Also don't change them when password reset is made.
102 - Api: added last-activity into returned data of get_user api.
103
104
105 Performance
106 ^^^^^^^^^^^
107
108 - Mercurial: enabled new `Zstandard` compression algorithm available with
109 Mercurial 4.1.X. This allows faster, more CPU efficient clones when used
110 with new Mercurial clients.
111
112 - Users Admin: moved user admin to pyramid, and made it load users in chunks.
113 Fixed loading data to be lazy fetched, drastically improves speed of user
114 administration page in case of large amount of users.
115
116
117 Fixes
118 ^^^^^
119
120 - Search: goto commit search will now use a safe search option and never
121 throw any exceptions even if search is misconfigured
122 e.g. Elastic Search cluster is down.
123 - Events: fix a case for events called from API that couldn't fetch
124 registered user object.
125 - Comments: unlock submit if we use slash commands to set status.
126 - UI: fixed an issue with date of last change was not displayed correctly.
127 - Emails: added comment types (TODO/NOTE) into emails.
128 - Events: fix wrongly returned author data.
129 - Error middleware: read the instance title from cached object.
130 Reading from settings inside error handler can cause error hiding when
131 error_handler was caused by database errors.
132 - Pull requests: show version age component should use local dates instead of UTC.
133 - Pull requests: lock button when updating reviewers to forbid multi-submit
134 problems. Additionally fixed some small UI issues found in that view.
135 - Pull requests: forbid browsing versions on closed pull request.
136 - Pull requests: allow super-admins to delete pull requests instead of only owners.
137 - Diffs: support mercurial copy operation in diffs details.
138 - SVN: escape special chars to allow interactions with non-standard svn paths.
139 Path with special characters such as '#' will no longer trigger 404 errors.
140 - Data grids: fix some styling and processing text display.
141 - API: use consistent way to extract users, repos, repo groups and user groups
142 by id or name. Makes usage of Number vs String to differentiate if we pick
143 object ID or it's name this will allow editing of objects by either id or
144 it's name, including numeric string names.
145 - API: validate commit_id when using commit_comment API
146 - API: cleanup sessions enforce older_then must be a valid INT.
147
148
149 Upgrade notes
150 ^^^^^^^^^^^^^
151
152 - Auth-tokens: a builtin token will be migrated for all users into a custom
153 external token. We advise to inform users that the current builtin tokens
154 will now show as external ones. Builtin tokens were removed to allow expiring
155 ,or removing them. It's now possible to create users without any tokens.
156
157 From now on new users needs a new token generation in case they want to use
158 token based authentication.
159
160 - Hooks: we added via migration a pre transaction hook for Mercurial. If you're
161 using a custom code inside pre-push function of rcextensions make sure it
162 will not block your pushes.
@@ -0,0 +1,93 b''
1 .. _git-lfs-files:
2
3 |git| LFS Extension
4 ===================
5
6
7 Git Large File Storage (or LFS) is a new, open-source extension to Git that
8 aims to improve handling of large files. It does this by replacing large files
9 in your repository—such as graphics and videos—with simple text pointers.
10 |RC| Server includes an embedded LFS object store server, allowing storage of
11 large files without the need for an external object store.
12 Git LFS is disabled by default, globally, and for each individual repository.
13
14 .. note::
15
16 |RC| implements V2 API of Git LFS. Please make sure your git client is
17 using the latest version (2.0.X recommended) to leverage full feature set
18 of the V2 API.
19
20
21
22 Enabling Git LFS
23 ++++++++++++++++
24
25 Git LFS is disabled by default within |RC| Server.
26
27 To enable Git LFS Globally:
28
29 - Go to :menuselection:`Admin --> Settings --> VCS`
30
31 - Scroll down into `Git settings`
32
33 - Tick `Enable lfs extension`
34
35 - Save your settings.
36
37 Those settings apply globally to each repository that inherits from the defaults
38 You can leave `lfs extension` disabled globally, and only enable it per
39 repository that would use the lfs.
40
41
42 .. note::
43
44 You might want to adjust the global storage location at that point, however
45 we recommend leaving the default one created.
46
47
48 Installing and using the Git LFS command line client
49 ++++++++++++++++++++++++++++++++++++++++++++++++++++
50
51 Git LFS aims to integrate with the standard Git workflow as seamlessly
52 as possible. To push your first Git LFS files to an existing repository
53 Download and install the git-lfs command line client
54 Install the Git LFS filters::
55
56 git lfs install
57
58 This adds the following lines to the .gitconfig file located in your home directory::
59
60 [filter "lfs"]
61 clean = git-lfs clean %f
62 smudge = git-lfs smudge %f
63 required = true
64
65 The above change applies globally, so it is not necessary to run this for
66 each repository you work with. Choose the file types you would like LFS to
67 handle by executing the git lfs track command. The git lfs track command
68 creates or updates the .gitattributes file in your repository.
69 Change to your cloned repository, then execute git add to ensure updates
70 to the .gitattributes are later committed::
71
72 git lfs track "*.jpg"
73 git add .gitattributes
74
75 Add, commit, and push your changes as you normally would::
76
77 git add image.jpg
78 git commit -m "Added an image"
79 git push
80
81 When pushed, the Git tree updates to include a pointer to the file actual
82 file content. This pointer will include the SHA256 hash of the object and its
83 size in bytes. For example::
84
85 oid sha256:4fa32d6f9b1461c4a53618a47324ee43e36ce7ceaea2ad440cc811a7e6881be1
86 size 2580390
87
88
89 The object itself will be uploaded to a separate location via the Git LFS Batch API.
90 The transfer is validated and authorized by |RC| server itself.
91
92 If give repository has Git LFS disabled, a proper message will be sent back to
93 the client and upload of LFS objects will be forbidden.
@@ -0,0 +1,12 b''
1 diff -rup celery-2.2.10-orig/setup.py celery-2.2.10/setup.py
2 --- celery-2.2.10-orig/setup.py 2017-02-25 15:30:34.000000000 +0100
3 +++ celery-2.2.10/setup.py 2017-02-25 15:30:34.000000000 +0100
4 @@ -48,7 +48,7 @@ try:
5 except ImportError:
6 install_requires.append("importlib")
7 install_requires.extend([
8 - "python-dateutil>=1.5.0,<2.0.0",
9 + "python-dateutil>=1.5.0,<2.2.0",
10 "anyjson>=0.3.1",
11 "kombu>=1.1.2,<2.0.0",
12 "pyparsing>=1.5.0,<2.0.0",
@@ -0,0 +1,58 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 import pytest
23
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25
26
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestGetMethod(object):
29 def test_get_methods_no_matches(self):
30 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
31 response = api_call(self.app, params)
32
33 expected = []
34 assert_ok(id_, expected, given=response.body)
35
36 def test_get_methods(self):
37 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
38 response = api_call(self.app, params)
39
40 expected = ['changeset_comment', 'comment_pull_request',
41 'comment_commit']
42 assert_ok(id_, expected, given=response.body)
43
44 def test_get_methods_on_single_match(self):
45 id_, params = build_data(self.apikey, 'get_method', pattern='*comment_commit*')
46 response = api_call(self.app, params)
47
48 expected = ['comment_commit',
49 {'apiuser': '<RequiredType>',
50 'comment_type': "<Optional:u'note'>",
51 'commit_id': '<RequiredType>',
52 'message': '<RequiredType>',
53 'repoid': '<RequiredType>',
54 'request': '<RequiredType>',
55 'resolves_comment_id': '<Optional:None>',
56 'status': '<Optional:None>',
57 'userid': '<Optional:<OptionalAttr:apiuser>>'}]
58 assert_ok(id_, expected, given=response.body)
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,163 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import time
22 import logging
23 from pylons import tmpl_context as c
24 from pyramid.httpexceptions import HTTPFound
25
26 from rhodecode.lib import helpers as h
27 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int
28 from rhodecode.model import repo
29 from rhodecode.model.db import User
30 from rhodecode.model.scm import ScmModel
31
32 log = logging.getLogger(__name__)
33
34
35 ADMIN_PREFIX = '/_admin'
36 STATIC_FILE_PREFIX = '/_static'
37
38
39 class TemplateArgs(StrictAttributeDict):
40 pass
41
42
43 class BaseAppView(object):
44
45 def __init__(self, context, request):
46 self.request = request
47 self.context = context
48 self.session = request.session
49 self._rhodecode_user = request.user # auth user
50 self._rhodecode_db_user = self._rhodecode_user.get_instance()
51 self._maybe_needs_password_change(
52 request.matched_route.name, self._rhodecode_db_user)
53
54 def _maybe_needs_password_change(self, view_name, user_obj):
55 log.debug('Checking if user %s needs password change on view %s',
56 user_obj, view_name)
57 skip_user_views = [
58 'logout', 'login',
59 'my_account_password', 'my_account_password_update'
60 ]
61
62 if not user_obj:
63 return
64
65 if user_obj.username == User.DEFAULT_USER:
66 return
67
68 now = time.time()
69 should_change = user_obj.user_data.get('force_password_change')
70 change_after = safe_int(should_change) or 0
71 if should_change and now > change_after:
72 log.debug('User %s requires password change', user_obj)
73 h.flash('You are required to change your password', 'warning',
74 ignore_duplicate=True)
75
76 if view_name not in skip_user_views:
77 raise HTTPFound(
78 self.request.route_path('my_account_password'))
79
80 def _get_local_tmpl_context(self):
81 c = TemplateArgs()
82 c.auth_user = self.request.user
83 return c
84
85 def _register_global_c(self, tmpl_args):
86 """
87 Registers attributes to pylons global `c`
88 """
89 # TODO(marcink): remove once pyramid migration is finished
90 for k, v in tmpl_args.items():
91 setattr(c, k, v)
92
93 def _get_template_context(self, tmpl_args):
94 self._register_global_c(tmpl_args)
95
96 local_tmpl_args = {
97 'defaults': {},
98 'errors': {},
99 }
100 local_tmpl_args.update(tmpl_args)
101 return local_tmpl_args
102
103 def load_default_context(self):
104 """
105 example:
106
107 def load_default_context(self):
108 c = self._get_local_tmpl_context()
109 c.custom_var = 'foobar'
110 self._register_global_c(c)
111 return c
112 """
113 raise NotImplementedError('Needs implementation in view class')
114
115
116 class RepoAppView(BaseAppView):
117
118 def __init__(self, context, request):
119 super(RepoAppView, self).__init__(context, request)
120 self.db_repo = request.db_repo
121 self.db_repo_name = self.db_repo.repo_name
122 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
123
124 def _get_local_tmpl_context(self):
125 c = super(RepoAppView, self)._get_local_tmpl_context()
126 # register common vars for this type of view
127 c.rhodecode_db_repo = self.db_repo
128 c.repo_name = self.db_repo_name
129 c.repository_pull_requests = self.db_repo_pull_requests
130 return c
131
132
133 class RepoRoutePredicate(object):
134 def __init__(self, val, config):
135 self.val = val
136
137 def text(self):
138 return 'repo_route = %s' % self.val
139
140 phash = text
141
142 def __call__(self, info, request):
143 repo_name = info['match']['repo_name']
144 repo_model = repo.RepoModel()
145 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
146 # if we match quickly from database, short circuit the operation,
147 # and validate repo based on the type.
148 if by_name_match:
149 # register this as request object we can re-use later
150 request.db_repo = by_name_match
151 return True
152
153 by_id_match = repo_model.get_repo_by_id(repo_name)
154 if by_id_match:
155 request.db_repo = by_id_match
156 return True
157
158 return False
159
160
161 def includeme(config):
162 config.add_route_predicate(
163 'repo_route', RepoRoutePredicate)
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,142 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.model.db import User, UserApiKeys
24
25 from rhodecode.tests import (
26 TestController, TEST_USER_REGULAR_LOGIN, assert_session_flash)
27 from rhodecode.tests.fixture import Fixture
28
29 fixture = Fixture()
30
31
32 def route_path(name, params=None, **kwargs):
33 import urllib
34 from rhodecode.apps._base import ADMIN_PREFIX
35
36 base_url = {
37 'users':
38 ADMIN_PREFIX + '/users',
39 'users_data':
40 ADMIN_PREFIX + '/users_data',
41 'edit_user_auth_tokens':
42 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens',
43 'edit_user_auth_tokens_add':
44 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/new',
45 'edit_user_auth_tokens_delete':
46 ADMIN_PREFIX + '/users/{user_id}/edit/auth_tokens/delete',
47 }[name].format(**kwargs)
48
49 if params:
50 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
51 return base_url
52
53
54 class TestAdminUsersView(TestController):
55
56 def test_show_users(self):
57 self.log_user()
58 self.app.get(route_path('users'))
59
60 def test_show_users_data(self, xhr_header):
61 self.log_user()
62 response = self.app.get(route_path(
63 'users_data'), extra_environ=xhr_header)
64
65 all_users = User.query().filter(
66 User.username != User.DEFAULT_USER).count()
67 assert response.json['recordsTotal'] == all_users
68
69 def test_show_users_data_filtered(self, xhr_header):
70 self.log_user()
71 response = self.app.get(route_path(
72 'users_data', params={'search[value]': 'empty_search'}),
73 extra_environ=xhr_header)
74
75 all_users = User.query().filter(
76 User.username != User.DEFAULT_USER).count()
77 assert response.json['recordsTotal'] == all_users
78 assert response.json['recordsFiltered'] == 0
79
80 def test_auth_tokens_default_user(self):
81 self.log_user()
82 user = User.get_default_user()
83 response = self.app.get(
84 route_path('edit_user_auth_tokens', user_id=user.user_id),
85 status=302)
86
87 def test_auth_tokens(self):
88 self.log_user()
89
90 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
91 response = self.app.get(
92 route_path('edit_user_auth_tokens', user_id=user.user_id))
93 for token in user.auth_tokens:
94 response.mustcontain(token)
95 response.mustcontain('never')
96
97 @pytest.mark.parametrize("desc, lifetime", [
98 ('forever', -1),
99 ('5mins', 60*5),
100 ('30days', 60*60*24*30),
101 ])
102 def test_add_auth_token(self, desc, lifetime, user_util):
103 self.log_user()
104 user = user_util.create_user()
105 user_id = user.user_id
106
107 response = self.app.post(
108 route_path('edit_user_auth_tokens_add', user_id=user_id),
109 {'description': desc, 'lifetime': lifetime,
110 'csrf_token': self.csrf_token})
111 assert_session_flash(response, 'Auth token successfully created')
112
113 response = response.follow()
114 user = User.get(user_id)
115 for auth_token in user.auth_tokens:
116 response.mustcontain(auth_token)
117
118 def test_delete_auth_token(self, user_util):
119 self.log_user()
120 user = user_util.create_user()
121 user_id = user.user_id
122 keys = user.extra_auth_tokens
123 assert 2 == len(keys)
124
125 response = self.app.post(
126 route_path('edit_user_auth_tokens_add', user_id=user_id),
127 {'description': 'desc', 'lifetime': -1,
128 'csrf_token': self.csrf_token})
129 assert_session_flash(response, 'Auth token successfully created')
130 response.follow()
131
132 # now delete our key
133 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
134 assert 3 == len(keys)
135
136 response = self.app.post(
137 route_path('edit_user_auth_tokens_delete', user_id=user_id),
138 {'del_auth_token': keys[0].api_key, 'csrf_token': self.csrf_token})
139
140 assert_session_flash(response, 'Auth token successfully deleted')
141 keys = UserApiKeys.query().filter(UserApiKeys.user_id == user_id).all()
142 assert 2 == len(keys)
@@ -0,0 +1,317 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
25
26 from rhodecode.lib.helpers import Page
27 from rhodecode_tools.lib.ext_json import json
28
29 from rhodecode.apps._base import BaseAppView
30 from rhodecode.lib.auth import (
31 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib.utils import PartialRenderer
34 from rhodecode.lib.utils2 import safe_int, safe_unicode
35 from rhodecode.model.auth_token import AuthTokenModel
36 from rhodecode.model.user import UserModel
37 from rhodecode.model.user_group import UserGroupModel
38 from rhodecode.model.db import User, or_
39 from rhodecode.model.meta import Session
40
41 log = logging.getLogger(__name__)
42
43
44 class AdminUsersView(BaseAppView):
45 ALLOW_SCOPED_TOKENS = False
46 """
47 This view has alternative version inside EE, if modified please take a look
48 in there as well.
49 """
50
51 def load_default_context(self):
52 c = self._get_local_tmpl_context()
53 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
54 self._register_global_c(c)
55 return c
56
57 def _redirect_for_default_user(self, username):
58 _ = self.request.translate
59 if username == User.DEFAULT_USER:
60 h.flash(_("You can't edit this user"), category='warning')
61 # TODO(marcink): redirect to 'users' admin panel once this
62 # is a pyramid view
63 raise HTTPFound('/')
64
65 def _extract_ordering(self, request):
66 column_index = safe_int(request.GET.get('order[0][column]'))
67 order_dir = request.GET.get(
68 'order[0][dir]', 'desc')
69 order_by = request.GET.get(
70 'columns[%s][data][sort]' % column_index, 'name_raw')
71
72 # translate datatable to DB columns
73 order_by = {
74 'first_name': 'name',
75 'last_name': 'lastname',
76 }.get(order_by) or order_by
77
78 search_q = request.GET.get('search[value]')
79 return search_q, order_by, order_dir
80
81 def _extract_chunk(self, request):
82 start = safe_int(request.GET.get('start'), 0)
83 length = safe_int(request.GET.get('length'), 25)
84 draw = safe_int(request.GET.get('draw'))
85 return draw, start, length
86
87 @HasPermissionAllDecorator('hg.admin')
88 @view_config(
89 route_name='users', request_method='GET',
90 renderer='rhodecode:templates/admin/users/users.mako')
91 def users_list(self):
92 c = self.load_default_context()
93 return self._get_template_context(c)
94
95 @HasPermissionAllDecorator('hg.admin')
96 @view_config(
97 # renderer defined below
98 route_name='users_data', request_method='GET', renderer='json',
99 xhr=True)
100 def users_list_data(self):
101 draw, start, limit = self._extract_chunk(self.request)
102 search_q, order_by, order_dir = self._extract_ordering(self.request)
103
104 _render = PartialRenderer('data_table/_dt_elements.mako')
105
106 def user_actions(user_id, username):
107 return _render("user_actions", user_id, username)
108
109 users_data_total_count = User.query()\
110 .filter(User.username != User.DEFAULT_USER) \
111 .count()
112
113 # json generate
114 base_q = User.query().filter(User.username != User.DEFAULT_USER)
115
116 if search_q:
117 like_expression = u'%{}%'.format(safe_unicode(search_q))
118 base_q = base_q.filter(or_(
119 User.username.ilike(like_expression),
120 User._email.ilike(like_expression),
121 User.name.ilike(like_expression),
122 User.lastname.ilike(like_expression),
123 ))
124
125 users_data_total_filtered_count = base_q.count()
126
127 sort_col = getattr(User, order_by, None)
128 if sort_col and order_dir == 'asc':
129 base_q = base_q.order_by(sort_col.asc().nullslast())
130 elif sort_col:
131 base_q = base_q.order_by(sort_col.desc().nullslast())
132
133 base_q = base_q.offset(start).limit(limit)
134 users_list = base_q.all()
135
136 users_data = []
137 for user in users_list:
138 users_data.append({
139 "username": h.gravatar_with_user(user.username),
140 "email": user.email,
141 "first_name": h.escape(user.name),
142 "last_name": h.escape(user.lastname),
143 "last_login": h.format_date(user.last_login),
144 "last_activity": h.format_date(user.last_activity),
145 "active": h.bool2icon(user.active),
146 "active_raw": user.active,
147 "admin": h.bool2icon(user.admin),
148 "extern_type": user.extern_type,
149 "extern_name": user.extern_name,
150 "action": user_actions(user.user_id, user.username),
151 })
152
153 data = ({
154 'draw': draw,
155 'data': users_data,
156 'recordsTotal': users_data_total_count,
157 'recordsFiltered': users_data_total_filtered_count,
158 })
159
160 return data
161
162 @LoginRequired()
163 @HasPermissionAllDecorator('hg.admin')
164 @view_config(
165 route_name='edit_user_auth_tokens', request_method='GET',
166 renderer='rhodecode:templates/admin/users/user_edit.mako')
167 def auth_tokens(self):
168 _ = self.request.translate
169 c = self.load_default_context()
170
171 user_id = self.request.matchdict.get('user_id')
172 c.user = User.get_or_404(user_id, pyramid_exc=True)
173 self._redirect_for_default_user(c.user.username)
174
175 c.active = 'auth_tokens'
176
177 c.lifetime_values = [
178 (str(-1), _('forever')),
179 (str(5), _('5 minutes')),
180 (str(60), _('1 hour')),
181 (str(60 * 24), _('1 day')),
182 (str(60 * 24 * 30), _('1 month')),
183 ]
184 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
185 c.role_values = [
186 (x, AuthTokenModel.cls._get_role_name(x))
187 for x in AuthTokenModel.cls.ROLES]
188 c.role_options = [(c.role_values, _("Role"))]
189 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
190 c.user.user_id, show_expired=True)
191 return self._get_template_context(c)
192
193 def maybe_attach_token_scope(self, token):
194 # implemented in EE edition
195 pass
196
197 @LoginRequired()
198 @HasPermissionAllDecorator('hg.admin')
199 @CSRFRequired()
200 @view_config(
201 route_name='edit_user_auth_tokens_add', request_method='POST')
202 def auth_tokens_add(self):
203 _ = self.request.translate
204 c = self.load_default_context()
205
206 user_id = self.request.matchdict.get('user_id')
207 c.user = User.get_or_404(user_id, pyramid_exc=True)
208 self._redirect_for_default_user(c.user.username)
209
210 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
211 description = self.request.POST.get('description')
212 role = self.request.POST.get('role')
213
214 token = AuthTokenModel().create(
215 c.user.user_id, description, lifetime, role)
216 self.maybe_attach_token_scope(token)
217 Session().commit()
218
219 h.flash(_("Auth token successfully created"), category='success')
220 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
221
222 @LoginRequired()
223 @HasPermissionAllDecorator('hg.admin')
224 @CSRFRequired()
225 @view_config(
226 route_name='edit_user_auth_tokens_delete', request_method='POST')
227 def auth_tokens_delete(self):
228 _ = self.request.translate
229 c = self.load_default_context()
230
231 user_id = self.request.matchdict.get('user_id')
232 c.user = User.get_or_404(user_id, pyramid_exc=True)
233 self._redirect_for_default_user(c.user.username)
234
235 del_auth_token = self.request.POST.get('del_auth_token')
236
237 if del_auth_token:
238 AuthTokenModel().delete(del_auth_token, c.user.user_id)
239 Session().commit()
240 h.flash(_("Auth token successfully deleted"), category='success')
241
242 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
243
244 @LoginRequired()
245 @HasPermissionAllDecorator('hg.admin')
246 @view_config(
247 route_name='edit_user_groups_management', request_method='GET',
248 renderer='rhodecode:templates/admin/users/user_edit.mako')
249 def groups_management(self):
250 c = self.load_default_context()
251
252 user_id = self.request.matchdict.get('user_id')
253 c.user = User.get_or_404(user_id, pyramid_exc=True)
254 c.data = c.user.group_member
255 self._redirect_for_default_user(c.user.username)
256 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group) for group in c.user.group_member]
257 c.groups = json.dumps(groups)
258 c.active = 'groups'
259
260 return self._get_template_context(c)
261
262 @LoginRequired()
263 @HasPermissionAllDecorator('hg.admin')
264 @view_config(
265 route_name='edit_user_groups_management_updates', request_method='POST')
266 def groups_management_updates(self):
267 _ = self.request.translate
268 c = self.load_default_context()
269
270 user_id = self.request.matchdict.get('user_id')
271 c.user = User.get_or_404(user_id, pyramid_exc=True)
272 self._redirect_for_default_user(c.user.username)
273
274 users_groups = set(self.request.POST.getall('users_group_id'))
275 users_groups_model = []
276
277 for ugid in users_groups:
278 users_groups_model.append(UserGroupModel().get_group(safe_int(ugid)))
279 user_group_model = UserGroupModel()
280 user_group_model.change_groups(c.user, users_groups_model)
281
282 Session().commit()
283 c.active = 'user_groups_management'
284 h.flash(_("Groups successfully changed"), category='success')
285
286 return HTTPFound(h.route_path(
287 'edit_user_groups_management', user_id=user_id))
288
289 @LoginRequired()
290 @HasPermissionAllDecorator('hg.admin')
291 @view_config(
292 route_name='edit_user_audit_logs', request_method='GET',
293 renderer='rhodecode:templates/admin/users/user_edit.mako')
294 def user_audit_logs(self):
295 _ = self.request.translate
296 c = self.load_default_context()
297
298 user_id = self.request.matchdict.get('user_id')
299 c.user = User.get_or_404(user_id, pyramid_exc=True)
300 self._redirect_for_default_user(c.user.username)
301 c.active = 'audit'
302
303 p = safe_int(self.request.GET.get('page', 1), 1)
304
305 filter_term = self.request.GET.get('filter')
306 c.user_log = UserModel().get_user_log(c.user, filter_term)
307
308 def url_generator(**kw):
309 if filter_term:
310 kw['filter'] = filter_term
311 return self.request.current_route_path(_query=kw)
312
313 c.user_log = Page(c.user_log, page=p, items_per_page=10,
314 url=url_generator)
315 c.filter_term = filter_term
316 return self._get_template_context(c)
317
@@ -0,0 +1,52 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 from rhodecode.apps._base import ADMIN_PREFIX
23
24
25 def includeme(config):
26
27 config.add_route(
28 name='my_account_profile',
29 pattern=ADMIN_PREFIX + '/my_account/profile')
30
31 config.add_route(
32 name='my_account_password',
33 pattern=ADMIN_PREFIX + '/my_account/password')
34
35 config.add_route(
36 name='my_account_password_update',
37 pattern=ADMIN_PREFIX + '/my_account/password')
38
39 config.add_route(
40 name='my_account_auth_tokens',
41 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
42 config.add_route(
43 name='my_account_auth_tokens_add',
44 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new',
45 )
46 config.add_route(
47 name='my_account_auth_tokens_delete',
48 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete',
49 )
50
51 # Scan module for configuration decorators.
52 config.scan()
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -0,0 +1,111 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.model.db import User
25 from rhodecode.tests import (
26 TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
27 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, assert_session_flash)
28 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.utils import AssertResponse
30
31 fixture = Fixture()
32
33
34 def route_path(name, **kwargs):
35 return {
36 'my_account_auth_tokens':
37 ADMIN_PREFIX + '/my_account/auth_tokens',
38 'my_account_auth_tokens_add':
39 ADMIN_PREFIX + '/my_account/auth_tokens/new',
40 'my_account_auth_tokens_delete':
41 ADMIN_PREFIX + '/my_account/auth_tokens/delete',
42 }[name].format(**kwargs)
43
44
45 class TestMyAccountAuthTokens(TestController):
46
47 def test_my_account_auth_tokens(self):
48 usr = self.log_user('test_regular2', 'test12')
49 user = User.get(usr['user_id'])
50 response = self.app.get(route_path('my_account_auth_tokens'))
51 for token in user.auth_tokens:
52 response.mustcontain(token)
53 response.mustcontain('never')
54
55 def test_my_account_add_auth_tokens_wrong_csrf(self, user_util):
56 user = user_util.create_user(password='qweqwe')
57 self.log_user(user.username, 'qweqwe')
58
59 self.app.post(
60 route_path('my_account_auth_tokens_add'),
61 {'description': 'desc', 'lifetime': -1}, status=403)
62
63 @pytest.mark.parametrize("desc, lifetime", [
64 ('forever', -1),
65 ('5mins', 60*5),
66 ('30days', 60*60*24*30),
67 ])
68 def test_my_account_add_auth_tokens(self, desc, lifetime, user_util):
69 user = user_util.create_user(password='qweqwe')
70 user_id = user.user_id
71 self.log_user(user.username, 'qweqwe')
72
73 response = self.app.post(
74 route_path('my_account_auth_tokens_add'),
75 {'description': desc, 'lifetime': lifetime,
76 'csrf_token': self.csrf_token})
77 assert_session_flash(response, 'Auth token successfully created')
78
79 response = response.follow()
80 user = User.get(user_id)
81 for auth_token in user.auth_tokens:
82 response.mustcontain(auth_token)
83
84 def test_my_account_delete_auth_token(self, user_util):
85 user = user_util.create_user(password='qweqwe')
86 user_id = user.user_id
87 self.log_user(user.username, 'qweqwe')
88
89 user = User.get(user_id)
90 keys = user.extra_auth_tokens
91 assert 2 == len(keys)
92
93 response = self.app.post(
94 route_path('my_account_auth_tokens_add'),
95 {'description': 'desc', 'lifetime': -1,
96 'csrf_token': self.csrf_token})
97 assert_session_flash(response, 'Auth token successfully created')
98 response.follow()
99
100 user = User.get(user_id)
101 keys = user.extra_auth_tokens
102 assert 3 == len(keys)
103
104 response = self.app.post(
105 route_path('my_account_auth_tokens_delete'),
106 {'del_auth_token': keys[0].api_key, 'csrf_token': self.csrf_token})
107 assert_session_flash(response, 'Auth token successfully deleted')
108
109 user = User.get(user_id)
110 keys = user.extra_auth_tokens
111 assert 2 == len(keys)
@@ -0,0 +1,137 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22 import mock
23
24 from rhodecode.apps._base import ADMIN_PREFIX
25 from rhodecode.lib import helpers as h
26 from rhodecode.lib.auth import check_password
27 from rhodecode.model.meta import Session
28 from rhodecode.model.user import UserModel
29 from rhodecode.tests import assert_session_flash
30 from rhodecode.tests.fixture import Fixture, TestController, error_function
31
32 fixture = Fixture()
33
34
35 def route_path(name, **kwargs):
36 return {
37 'home': '/',
38 'my_account_password':
39 ADMIN_PREFIX + '/my_account/password',
40 }[name].format(**kwargs)
41
42
43 test_user_1 = 'testme'
44 test_user_1_password = '0jd83nHNS/d23n'
45
46
47 class TestMyAccountPassword(TestController):
48 def test_valid_change_password(self, user_util):
49 new_password = 'my_new_valid_password'
50 user = user_util.create_user(password=test_user_1_password)
51 self.log_user(user.username, test_user_1_password)
52
53 form_data = [
54 ('current_password', test_user_1_password),
55 ('__start__', 'new_password:mapping'),
56 ('new_password', new_password),
57 ('new_password-confirm', new_password),
58 ('__end__', 'new_password:mapping'),
59 ('csrf_token', self.csrf_token),
60 ]
61 response = self.app.post(route_path('my_account_password'), form_data).follow()
62 assert 'Successfully updated password' in response
63
64 # check_password depends on user being in session
65 Session().add(user)
66 try:
67 assert check_password(new_password, user.password)
68 finally:
69 Session().expunge(user)
70
71 @pytest.mark.parametrize('current_pw, new_pw, confirm_pw', [
72 ('', 'abcdef123', 'abcdef123'),
73 ('wrong_pw', 'abcdef123', 'abcdef123'),
74 (test_user_1_password, test_user_1_password, test_user_1_password),
75 (test_user_1_password, '', ''),
76 (test_user_1_password, 'abcdef123', ''),
77 (test_user_1_password, '', 'abcdef123'),
78 (test_user_1_password, 'not_the', 'same_pw'),
79 (test_user_1_password, 'short', 'short'),
80 ])
81 def test_invalid_change_password(self, current_pw, new_pw, confirm_pw,
82 user_util):
83 user = user_util.create_user(password=test_user_1_password)
84 self.log_user(user.username, test_user_1_password)
85
86 form_data = [
87 ('current_password', current_pw),
88 ('__start__', 'new_password:mapping'),
89 ('new_password', new_pw),
90 ('new_password-confirm', confirm_pw),
91 ('__end__', 'new_password:mapping'),
92 ('csrf_token', self.csrf_token),
93 ]
94 response = self.app.post(route_path('my_account_password'), form_data)
95
96 assert_response = response.assert_response()
97 assert assert_response.get_elements('.error-block')
98
99 @mock.patch.object(UserModel, 'update_user', error_function)
100 def test_invalid_change_password_exception(self, user_util):
101 user = user_util.create_user(password=test_user_1_password)
102 self.log_user(user.username, test_user_1_password)
103
104 form_data = [
105 ('current_password', test_user_1_password),
106 ('__start__', 'new_password:mapping'),
107 ('new_password', '123456'),
108 ('new_password-confirm', '123456'),
109 ('__end__', 'new_password:mapping'),
110 ('csrf_token', self.csrf_token),
111 ]
112 response = self.app.post(route_path('my_account_password'), form_data)
113 assert_session_flash(
114 response, 'Error occurred during update of user password')
115
116 def test_password_is_updated_in_session_on_password_change(self, user_util):
117 old_password = 'abcdef123'
118 new_password = 'abcdef124'
119
120 user = user_util.create_user(password=old_password)
121 session = self.log_user(user.username, old_password)
122 old_password_hash = session['password']
123
124 form_data = [
125 ('current_password', old_password),
126 ('__start__', 'new_password:mapping'),
127 ('new_password', new_password),
128 ('new_password-confirm', new_password),
129 ('__end__', 'new_password:mapping'),
130 ('csrf_token', self.csrf_token),
131 ]
132 self.app.post(route_path('my_account_password'), form_data)
133
134 response = self.app.get(route_path('home'))
135 new_password_hash = response.session['rhodecode_user']['password']
136
137 assert old_password_hash != new_password_hash No newline at end of file
@@ -0,0 +1,55 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.tests import (
25 TestController, TEST_USER_ADMIN_LOGIN,
26 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
27 from rhodecode.tests.fixture import Fixture
28
29 fixture = Fixture()
30
31
32 def route_path(name, **kwargs):
33 return {
34 'my_account':
35 ADMIN_PREFIX + '/my_account/profile',
36 }[name].format(**kwargs)
37
38
39 class TestMyAccountProfile(TestController):
40
41 def test_my_account(self):
42 self.log_user()
43 response = self.app.get(route_path('my_account'))
44
45 response.mustcontain(TEST_USER_ADMIN_LOGIN)
46 response.mustcontain('href="/_admin/my_account/edit"')
47 response.mustcontain('Photo')
48
49 def test_my_account_regular_user(self):
50 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
51 response = self.app.get(route_path('my_account'))
52
53 response.mustcontain(TEST_USER_REGULAR_LOGIN)
54 response.mustcontain('href="/_admin/my_account/edit"')
55 response.mustcontain('Photo')
@@ -0,0 +1,194 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
25
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode import forms
28 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib.utils2 import safe_int, md5
31 from rhodecode.model.auth_token import AuthTokenModel
32 from rhodecode.model.meta import Session
33 from rhodecode.model.user import UserModel
34 from rhodecode.model.validation_schema.schemas import user_schema
35
36 log = logging.getLogger(__name__)
37
38
39 class MyAccountView(BaseAppView):
40 ALLOW_SCOPED_TOKENS = False
41 """
42 This view has alternative version inside EE, if modified please take a look
43 in there as well.
44 """
45
46 def load_default_context(self):
47 c = self._get_local_tmpl_context()
48 c.user = c.auth_user.get_instance()
49 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
50 self._register_global_c(c)
51 return c
52
53 @LoginRequired()
54 @NotAnonymous()
55 @view_config(
56 route_name='my_account_profile', request_method='GET',
57 renderer='rhodecode:templates/admin/my_account/my_account.mako')
58 def my_account_profile(self):
59 c = self.load_default_context()
60 c.active = 'profile'
61 return self._get_template_context(c)
62
63 @LoginRequired()
64 @NotAnonymous()
65 @view_config(
66 route_name='my_account_password', request_method='GET',
67 renderer='rhodecode:templates/admin/my_account/my_account.mako')
68 def my_account_password(self):
69 c = self.load_default_context()
70 c.active = 'password'
71 c.extern_type = c.user.extern_type
72
73 schema = user_schema.ChangePasswordSchema().bind(
74 username=c.user.username)
75
76 form = forms.Form(
77 schema, buttons=(forms.buttons.save, forms.buttons.reset))
78
79 c.form = form
80 return self._get_template_context(c)
81
82 @LoginRequired()
83 @NotAnonymous()
84 @CSRFRequired()
85 @view_config(
86 route_name='my_account_password', request_method='POST',
87 renderer='rhodecode:templates/admin/my_account/my_account.mako')
88 def my_account_password_update(self):
89 _ = self.request.translate
90 c = self.load_default_context()
91 c.active = 'password'
92 c.extern_type = c.user.extern_type
93
94 schema = user_schema.ChangePasswordSchema().bind(
95 username=c.user.username)
96
97 form = forms.Form(
98 schema, buttons=(forms.buttons.save, forms.buttons.reset))
99
100 if c.extern_type != 'rhodecode':
101 raise HTTPFound(self.request.route_path('my_account_password'))
102
103 controls = self.request.POST.items()
104 try:
105 valid_data = form.validate(controls)
106 UserModel().update_user(c.user.user_id, **valid_data)
107 c.user.update_userdata(force_password_change=False)
108 Session().commit()
109 except forms.ValidationFailure as e:
110 c.form = e
111 return self._get_template_context(c)
112
113 except Exception:
114 log.exception("Exception updating password")
115 h.flash(_('Error occurred during update of user password'),
116 category='error')
117 else:
118 instance = c.auth_user.get_instance()
119 self.session.setdefault('rhodecode_user', {}).update(
120 {'password': md5(instance.password)})
121 self.session.save()
122 h.flash(_("Successfully updated password"), category='success')
123
124 raise HTTPFound(self.request.route_path('my_account_password'))
125
126 @LoginRequired()
127 @NotAnonymous()
128 @view_config(
129 route_name='my_account_auth_tokens', request_method='GET',
130 renderer='rhodecode:templates/admin/my_account/my_account.mako')
131 def my_account_auth_tokens(self):
132 _ = self.request.translate
133
134 c = self.load_default_context()
135 c.active = 'auth_tokens'
136
137 c.lifetime_values = [
138 (str(-1), _('forever')),
139 (str(5), _('5 minutes')),
140 (str(60), _('1 hour')),
141 (str(60 * 24), _('1 day')),
142 (str(60 * 24 * 30), _('1 month')),
143 ]
144 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
145 c.role_values = [
146 (x, AuthTokenModel.cls._get_role_name(x))
147 for x in AuthTokenModel.cls.ROLES]
148 c.role_options = [(c.role_values, _("Role"))]
149 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
150 c.user.user_id, show_expired=True)
151 return self._get_template_context(c)
152
153 def maybe_attach_token_scope(self, token):
154 # implemented in EE edition
155 pass
156
157 @LoginRequired()
158 @NotAnonymous()
159 @CSRFRequired()
160 @view_config(
161 route_name='my_account_auth_tokens_add', request_method='POST')
162 def my_account_auth_tokens_add(self):
163 _ = self.request.translate
164 c = self.load_default_context()
165
166 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
167 description = self.request.POST.get('description')
168 role = self.request.POST.get('role')
169
170 token = AuthTokenModel().create(
171 c.user.user_id, description, lifetime, role)
172 self.maybe_attach_token_scope(token)
173 Session().commit()
174
175 h.flash(_("Auth token successfully created"), category='success')
176 return HTTPFound(h.route_path('my_account_auth_tokens'))
177
178 @LoginRequired()
179 @NotAnonymous()
180 @CSRFRequired()
181 @view_config(
182 route_name='my_account_auth_tokens_delete', request_method='POST')
183 def my_account_auth_tokens_delete(self):
184 _ = self.request.translate
185 c = self.load_default_context()
186
187 del_auth_token = self.request.POST.get('del_auth_token')
188
189 if del_auth_token:
190 AuthTokenModel().delete(del_auth_token, c.user.user_id)
191 Session().commit()
192 h.flash(_("Auth token successfully deleted"), category='success')
193
194 return HTTPFound(h.route_path('my_account_auth_tokens'))
@@ -0,0 +1,46 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 def includeme(config):
23
24 config.add_route(
25 name='repo_maintenance',
26 pattern='/{repo_name:.*?[^/]}/maintenance', repo_route=True)
27
28 config.add_route(
29 name='repo_maintenance_execute',
30 pattern='/{repo_name:.*?[^/]}/maintenance/execute', repo_route=True)
31
32
33 # Strip
34 config.add_route(
35 name='strip',
36 pattern='/{repo_name:.*?[^/]}/strip', repo_route=True)
37
38 config.add_route(
39 name='strip_check',
40 pattern='/{repo_name:.*?[^/]}/strip_check', repo_route=True)
41
42 config.add_route(
43 name='strip_execute',
44 pattern='/{repo_name:.*?[^/]}/strip_execute', repo_route=True)
45 # Scan module for configuration decorators.
46 config.scan()
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -0,0 +1,70 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.view import view_config
24
25 from rhodecode.apps._base import RepoAppView
26 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
27 NotAnonymous)
28 from rhodecode.lib import repo_maintenance
29
30 log = logging.getLogger(__name__)
31
32
33 class RepoMaintenanceView(RepoAppView):
34 def load_default_context(self):
35 c = self._get_local_tmpl_context()
36
37 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
38 c.repo_info = self.db_repo
39
40 self._register_global_c(c)
41 return c
42
43 @LoginRequired()
44 @NotAnonymous()
45 @HasRepoPermissionAnyDecorator('repository.admin')
46 @view_config(
47 route_name='repo_maintenance', request_method='GET',
48 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
49 def repo_maintenance(self):
50 c = self.load_default_context()
51 c.active = 'maintenance'
52 maintenance = repo_maintenance.RepoMaintenance()
53 c.executable_tasks = maintenance.get_tasks_for_repo(self.db_repo)
54 return self._get_template_context(c)
55
56 @LoginRequired()
57 @NotAnonymous()
58 @HasRepoPermissionAnyDecorator('repository.admin')
59 @view_config(
60 route_name='repo_maintenance_execute', request_method='GET',
61 renderer='json', xhr=True)
62 def repo_maintenance_execute(self):
63 c = self.load_default_context()
64 c.active = 'maintenance'
65 _ = self.request.translate
66
67 maintenance = repo_maintenance.RepoMaintenance()
68 executed_types = maintenance.execute(self.db_repo)
69
70 return executed_types
@@ -0,0 +1,110 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 from pyramid.view import view_config
23
24 from rhodecode.apps._base import RepoAppView
25 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
26 NotAnonymous)
27
28
29 log = logging.getLogger(__name__)
30
31
32 class StripView(RepoAppView):
33 def load_default_context(self):
34 c = self._get_local_tmpl_context()
35
36 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
37 c.repo_info = self.db_repo
38
39 self._register_global_c(c)
40 return c
41
42 @LoginRequired()
43 @NotAnonymous()
44 @HasRepoPermissionAnyDecorator('repository.admin')
45 @view_config(
46 route_name='strip', request_method='GET',
47 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
48 def strip(self):
49 c = self.load_default_context()
50 c.active = 'strip'
51 c.strip_limit = 10
52
53 return self._get_template_context(c)
54
55 @LoginRequired()
56 @NotAnonymous()
57 @HasRepoPermissionAnyDecorator('repository.admin')
58 @view_config(
59 route_name='strip_check', request_method='POST',
60 renderer='json', xhr=True
61 )
62 def strip_check(self):
63 from rhodecode.lib.vcs.backends.base import EmptyCommit
64 data = {}
65 rp = self.request.POST
66 for i in range(1, 11):
67 chset = 'changeset_id-%d'%(i,)
68 check = rp.get(chset)
69 if check:
70 data[i] = self.db_repo.get_changeset(rp[chset])
71 if isinstance(data[i], EmptyCommit):
72 data[i] = {'rev': None, 'commit': rp[chset]}
73 else:
74 data[i] = {'rev': data[i].raw_id, 'branch': data[i].branch, 'author': data[i].author,
75 'comment': data[i].message}
76 else:
77 break
78 return data
79
80 @LoginRequired()
81 @NotAnonymous()
82 @HasRepoPermissionAnyDecorator('repository.admin')
83 @view_config(
84 route_name='strip_execute', request_method='POST',
85 renderer='json', xhr=True
86 )
87 def strip_execute(self):
88
89 from rhodecode.model.scm import ScmModel
90 from rhodecode.lib.ext_json import json
91
92 c = self.load_default_context()
93 user = self._rhodecode_user
94 rp = self.request.POST
95 data = {}
96 for idx in rp:
97 commit = json.loads(rp[idx])
98 #If someone put two times the same branch
99 if commit['branch'] in data.keys():
100 continue
101 try:
102 ScmModel().strip(repo=c.repo_info,
103 commit_id=commit['rev'], branch=commit['branch'])
104 log.info('Stripped commit %s from repo `%s` by %s' % (commit['rev'], c.repo_info.repo_name, user))
105 data[commit['rev']] = True
106 except Exception, e:
107 data[commit['rev']] = False
108 log.debug('Stripped commit %s from repo `%s` failed by %s, exeption %s' % (commit['rev'],
109 c.repo_info.repo_name, user, e.message))
110 return data
@@ -0,0 +1,28 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 def includeme(config):
23 config.add_route(
24 name='user_profile',
25 pattern='/_profiles/{username}')
26
27 # Scan module for configuration decorators.
28 config.scan()
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -0,0 +1,75 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import pytest
22
23 from rhodecode.model.db import User
24 from rhodecode.tests import (
25 TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
26 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
27 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.utils import AssertResponse
29
30 fixture = Fixture()
31
32
33 def route_path(name, **kwargs):
34 return '/_profiles/{username}'.format(**kwargs)
35
36
37 class TestUsersController(TestController):
38
39 def test_user_profile(self, user_util):
40 edit_link_css = '.user-profile .panel-edit'
41 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
42 user = user_util.create_user(
43 'test-my-user', password='qweqwe', email='testme@rhodecode.org')
44 username = user.username
45
46 response = self.app.get(route_path('user_profile', username=username))
47 response.mustcontain('testme')
48 response.mustcontain('testme@rhodecode.org')
49 assert_response = AssertResponse(response)
50 assert_response.no_element_exists(edit_link_css)
51
52 # edit should be available to superadmin users
53 self.logout_user()
54 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
55 response = self.app.get(route_path('user_profile', username=username))
56 assert_response = AssertResponse(response)
57 assert_response.element_contains(edit_link_css, 'Edit')
58
59 def test_user_profile_not_available(self, user_util):
60 user = user_util.create_user()
61 username = user.username
62
63 # not logged in, redirect
64 self.app.get(route_path('user_profile', username=username), status=302)
65
66 self.log_user()
67 # after log-in show
68 self.app.get(route_path('user_profile', username=username), status=200)
69
70 # default user, not allowed to show it
71 self.app.get(
72 route_path('user_profile', username=User.DEFAULT_USER), status=404)
73
74 # actual 404
75 self.app.get(route_path('user_profile', username='unknown'), status=404)
@@ -0,0 +1,53 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.httpexceptions import HTTPNotFound
24 from pyramid.view import view_config
25
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib.auth import LoginRequired, NotAnonymous
28
29 from rhodecode.model.db import User
30 from rhodecode.model.user import UserModel
31
32 log = logging.getLogger(__name__)
33
34
35 class UserProfileView(BaseAppView):
36
37 @LoginRequired()
38 @NotAnonymous()
39 @view_config(
40 route_name='user_profile', request_method='GET',
41 renderer='rhodecode:templates/users/user.mako')
42 def login(self):
43 # register local template context
44 c = self._get_local_tmpl_context()
45 c.active = 'user_profile'
46
47 username = self.request.matchdict.get('username')
48
49 c.user = UserModel().get_by_username(username)
50 if not c.user or c.user.username == User.DEFAULT_USER:
51 raise HTTPNotFound()
52
53 return self._get_template_context(c)
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,5 +1,5 b''
1 [bumpversion]
1 [bumpversion]
2 current_version = 4.6.1
2 current_version = 4.7.0
3 message = release: Bump version {current_version} to {new_version}
3 message = release: Bump version {current_version} to {new_version}
4
4
5 [bumpversion:file:rhodecode/VERSION]
5 [bumpversion:file:rhodecode/VERSION]
@@ -10,6 +10,8 b' include ='
10 omit =
10 omit =
11 rhodecode/lib/vcs/remote/*
11 rhodecode/lib/vcs/remote/*
12 rhodecode/lib/dbmigrate/*
12 rhodecode/lib/dbmigrate/*
13 rhodecode/lib/paster_commands/*
14 rhodecode/tests/*
13
15
14 [report]
16 [report]
15
17
@@ -4,26 +4,21 b' done = false'
4 [task:bump_version]
4 [task:bump_version]
5 done = true
5 done = true
6
6
7 [task:rc_tools_pinned]
8 done = true
9
10 [task:fixes_on_stable]
7 [task:fixes_on_stable]
11 done = true
12
8
13 [task:pip2nix_generated]
9 [task:pip2nix_generated]
14 done = true
15
10
16 [task:changelog_updated]
11 [task:changelog_updated]
17 done = true
18
12
19 [task:generate_api_docs]
13 [task:generate_api_docs]
20 done = true
14
15 [task:updated_translation]
21
16
22 [release]
17 [release]
23 state = prepared
18 state = in_progress
24 version = 4.6.1
19 version = 4.7.0
25
20
26 [task:updated_translation]
21 [task:rc_tools_pinned]
27
22
28 [task:generate_js_routes]
23 [task:generate_js_routes]
29
24
@@ -1,5 +1,5 b''
1 # top level files
1 # top level files
2 include test.ini
2
3 include MANIFEST.in
3 include MANIFEST.in
4 include README.rst
4 include README.rst
5 include CHANGES.rst
5 include CHANGES.rst
@@ -539,18 +539,15 b' vcs.server = localhost:9900'
539
539
540 ## Web server connectivity protocol, responsible for web based VCS operatations
540 ## Web server connectivity protocol, responsible for web based VCS operatations
541 ## Available protocols are:
541 ## Available protocols are:
542 ## `pyro4` - use pyro4 server
543 ## `http` - use http-rpc backend (default)
542 ## `http` - use http-rpc backend (default)
544 vcs.server.protocol = http
543 vcs.server.protocol = http
545
544
546 ## Push/Pull operations protocol, available options are:
545 ## Push/Pull operations protocol, available options are:
547 ## `pyro4` - use pyro4 server
548 ## `http` - use http-rpc backend (default)
546 ## `http` - use http-rpc backend (default)
549 ##
547 ##
550 vcs.scm_app_implementation = http
548 vcs.scm_app_implementation = http
551
549
552 ## Push/Pull operations hooks protocol, available options are:
550 ## Push/Pull operations hooks protocol, available options are:
553 ## `pyro4` - use pyro4 server
554 ## `http` - use http-rpc backend (default)
551 ## `http` - use http-rpc backend (default)
555 vcs.hooks.protocol = http
552 vcs.hooks.protocol = http
556
553
@@ -666,7 +663,7 b' formatter = color_formatter_sql'
666 ################
663 ################
667
664
668 [formatter_generic]
665 [formatter_generic]
669 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
666 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
670 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
667 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
671 datefmt = %Y-%m-%d %H:%M:%S
668 datefmt = %Y-%m-%d %H:%M:%S
672
669
@@ -36,7 +36,7 b' tmp_upload_dir = None'
36
36
37 # Custom log format
37 # Custom log format
38 access_log_format = (
38 access_log_format = (
39 '%(t)s GNCRN %(p)-8s %(h)-15s rqt:%(L)s %(s)s %(b)s "%(m)s:%(U)s %(q)s" usr:%(u)s "%(f)s" "%(a)s"')
39 '%(t)s GNCRN %(p)-8s %(h)-15s rqt:%(L)s %(s)s %(b)-6s "%(m)s:%(U)s %(q)s" usr:%(u)s "%(f)s" "%(a)s"')
40
40
41 # self adjust workers based on CPU count
41 # self adjust workers based on CPU count
42 # workers = multiprocessing.cpu_count() * 2 + 1
42 # workers = multiprocessing.cpu_count() * 2 + 1
@@ -508,18 +508,15 b' vcs.server = localhost:9900'
508
508
509 ## Web server connectivity protocol, responsible for web based VCS operatations
509 ## Web server connectivity protocol, responsible for web based VCS operatations
510 ## Available protocols are:
510 ## Available protocols are:
511 ## `pyro4` - use pyro4 server
512 ## `http` - use http-rpc backend (default)
511 ## `http` - use http-rpc backend (default)
513 vcs.server.protocol = http
512 vcs.server.protocol = http
514
513
515 ## Push/Pull operations protocol, available options are:
514 ## Push/Pull operations protocol, available options are:
516 ## `pyro4` - use pyro4 server
517 ## `http` - use http-rpc backend (default)
515 ## `http` - use http-rpc backend (default)
518 ##
516 ##
519 vcs.scm_app_implementation = http
517 vcs.scm_app_implementation = http
520
518
521 ## Push/Pull operations hooks protocol, available options are:
519 ## Push/Pull operations hooks protocol, available options are:
522 ## `pyro4` - use pyro4 server
523 ## `http` - use http-rpc backend (default)
520 ## `http` - use http-rpc backend (default)
524 vcs.hooks.protocol = http
521 vcs.hooks.protocol = http
525
522
@@ -635,7 +632,7 b' formatter = generic'
635 ################
632 ################
636
633
637 [formatter_generic]
634 [formatter_generic]
638 class = rhodecode.lib.logging_formatter.Pyro4AwareFormatter
635 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
639 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
636 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
640 datefmt = %Y-%m-%d %H:%M:%S
637 datefmt = %Y-%m-%d %H:%M:%S
641
638
@@ -124,6 +124,7 b' let'
124 name = "rhodecode-enterprise-ce-${version}";
124 name = "rhodecode-enterprise-ce-${version}";
125 releaseName = "RhodeCodeEnterpriseCE-${version}";
125 releaseName = "RhodeCodeEnterpriseCE-${version}";
126 src = rhodecode-enterprise-ce-src;
126 src = rhodecode-enterprise-ce-src;
127 dontStrip = true; # prevent strip, we don't need it.
127
128
128 buildInputs =
129 buildInputs =
129 attrs.buildInputs ++
130 attrs.buildInputs ++
@@ -225,8 +226,8 b' let'
225 rhodecode-testdata-src = sources.rhodecode-testdata or (
226 rhodecode-testdata-src = sources.rhodecode-testdata or (
226 pkgs.fetchhg {
227 pkgs.fetchhg {
227 url = "https://code.rhodecode.com/upstream/rc_testdata";
228 url = "https://code.rhodecode.com/upstream/rc_testdata";
228 rev = "v0.9.0";
229 rev = "v0.10.0";
229 sha256 = "0k0ccb7cncd6mmzwckfbr6l7fsymcympwcm948qc3i0f0m6bbg1y";
230 sha256 = "0zn9swwvx4vgw4qn8q3ri26vvzgrxn15x6xnjrysi1bwmz01qjl0";
230 });
231 });
231
232
232 # Apply all overrides and fix the final package set
233 # Apply all overrides and fix the final package set
@@ -17,7 +17,6 b' implemented in :mod:`rhodecode.lib.middl'
17 .. toctree::
17 .. toctree::
18 :maxdepth: 2
18 :maxdepth: 2
19
19
20 http-transition
21 middleware
20 middleware
22 vcsserver
21 vcsserver
23 subversion
22 subversion
@@ -59,7 +59,7 b' Below config if for an Apache Reverse Pr'
59
59
60 # Url to running RhodeCode instance. This is shown as `- URL:` when
60 # Url to running RhodeCode instance. This is shown as `- URL:` when
61 # running rccontrol status.
61 # running rccontrol status.
62 ProxyPass / http://127.0.0.1:10002/
62 ProxyPass / http://127.0.0.1:10002/ timeout=7200 Keepalive=On
63 ProxyPassReverse / http://127.0.0.1:10002/
63 ProxyPassReverse / http://127.0.0.1:10002/
64
64
65 # strict http prevents from https -> http downgrade
65 # strict http prevents from https -> http downgrade
@@ -47,7 +47,7 b' the ``debug`` level.'
47 ### LOGGING CONFIGURATION ####
47 ### LOGGING CONFIGURATION ####
48 ################################
48 ################################
49 [loggers]
49 [loggers]
50 keys = root, routes, rhodecode, sqlalchemy, beaker, pyro4, templates
50 keys = root, routes, rhodecode, sqlalchemy, beaker, templates
51
51
52 [handlers]
52 [handlers]
53 keys = console, console_sql, file, file_rotating
53 keys = console, console_sql, file, file_rotating
@@ -75,12 +75,6 b' the ``debug`` level.'
75 qualname = beaker.container
75 qualname = beaker.container
76 propagate = 1
76 propagate = 1
77
77
78 [logger_pyro4]
79 level = DEBUG
80 handlers =
81 qualname = Pyro4
82 propagate = 1
83
84 [logger_templates]
78 [logger_templates]
85 level = INFO
79 level = INFO
86 handlers =
80 handlers =
@@ -114,8 +114,20 b' 4. Add the following configuration optio'
114
114
115 This would create a special template file called ```mod_dav_svn.conf```. We
115 This would create a special template file called ```mod_dav_svn.conf```. We
116 used that file path in the apache config above inside the Include statement.
116 used that file path in the apache config above inside the Include statement.
117 It's also possible to generate the config from the
117 It's also possible to manually generate the config from the
118 :menuselection:`Admin --> Settings --> VCS` page.
118 :menuselection:`Admin --> Settings --> VCS` page by clicking a
119 `Generate Apache Config` button.
120
121 5. Now only things left is to enable svn support, and generate the initial
122 configuration.
123
124 - Select `Proxy subversion HTTP requests` checkbox
125 - Enter http://localhost:8090 into `Subversion HTTP Server URL`
126 - Click the `Generate Apache Config` button.
127
128 This config will be automatically re-generated once an user-groups is added
129 to properly map the additional paths generated.
130
119
131
120
132
121 Using |svn|
133 Using |svn|
@@ -7,26 +7,8 b' Change the |hg| Large Files Location'
7 :file:`/home/{user}/repos/.cache/largefiles`. If you wish to change this, use
7 :file:`/home/{user}/repos/.cache/largefiles`. If you wish to change this, use
8 the following steps:
8 the following steps:
9
9
10 1. Open ishell from the terminal and use it to log into the |RCE| database by
10 1. Open :menuselection:`Admin --> Settings --> VCS` as super-admin.
11 specifying the instance :file:`rhodecode.ini` file.
12
13 .. code-block:: bash
14
15 # Open iShell from the terminal and set ini file
16 $ rccontrol ishell enterprise-1
17
18 2. Run the following commands, and ensure that |RCE| has write access to the
19 new directory:
20
11
21 .. code-block:: bash
12 In section called `Mercurial Settings` you can change where the largefiles
13 objects should be stored.
22
14
23 # Once logged into the database, use SQL to redirect
24 # the large files location
25 In [1]: from rhodecode.model.settings import SettingsModel
26 In [2]: SettingsModel().get_ui_by_key('usercache')
27 Out[2]: <RhodeCodeUi[largefiles]usercache=>/mnt/hgfs/shared/workspace/xxxx/.cache/largefiles]>
28
29 In [3]: largefiles_cache = SettingsModel().get_ui_by_key('usercache')
30 In [4]: largefiles_cache.ui_value = '/new/path’
31 In [5]: Session().add(largefiles_cache);Session().commit()
32
@@ -17,5 +17,6 b' may find some of the following methods u'
17 tuning-mount-cache-memory
17 tuning-mount-cache-memory
18 tuning-change-encoding
18 tuning-change-encoding
19 tuning-change-large-file-dir
19 tuning-change-large-file-dir
20 tuning-change-lfs-dir
20 tuning-hg-auth-loop
21 tuning-hg-auth-loop
21
22
@@ -232,7 +232,7 b' For a more detailed explanation of the l'
232 ### LOGGING CONFIGURATION ####
232 ### LOGGING CONFIGURATION ####
233 ################################
233 ################################
234 [loggers]
234 [loggers]
235 keys = root, vcsserver, pyro4, beaker
235 keys = root, vcsserver, beaker
236
236
237 [handlers]
237 [handlers]
238 keys = console
238 keys = console
@@ -259,12 +259,6 b' For a more detailed explanation of the l'
259 qualname = beaker
259 qualname = beaker
260 propagate = 1
260 propagate = 1
261
261
262 [logger_pyro4]
263 level = DEBUG
264 handlers =
265 qualname = Pyro4
266 propagate = 1
267
268
262
269 ##############
263 ##############
270 ## HANDLERS ##
264 ## HANDLERS ##
@@ -75,7 +75,7 b' Example call for auto pulling from remot'
75 .. code-block:: bash
75 .. code-block:: bash
76
76
77 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,
77 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,
78 "auth_token":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull", "args":{"repo":"CPython"}}'
78 "auth_token":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull", "args":{"repoid":"CPython"}}'
79
79
80 Provide those parameters:
80 Provide those parameters:
81 - **id** A value of any type, which is used to match the response with the
81 - **id** A value of any type, which is used to match the response with the
@@ -24,13 +24,12 b' close_pull_request'
24
24
25 .. code-block:: bash
25 .. code-block:: bash
26
26
27 "id": <id_given_in_input>,
27 "id": <id_given_in_input>,
28 "result":
28 "result": {
29 {
30 "pull_request_id": "<int>",
29 "pull_request_id": "<int>",
31 "closed": "<bool>"
30 "closed": "<bool>"
32 },
31 },
33 "error": null
32 "error": null
34
33
35
34
36 comment_pull_request
35 comment_pull_request
@@ -67,15 +66,14 b' comment_pull_request'
67
66
68 .. code-block:: bash
67 .. code-block:: bash
69
68
70 id : <id_given_in_input>
69 id : <id_given_in_input>
71 result :
70 result : {
72 {
73 "pull_request_id": "<Integer>",
71 "pull_request_id": "<Integer>",
74 "comment_id": "<Integer>",
72 "comment_id": "<Integer>",
75 "status": {"given": <given_status>,
73 "status": {"given": <given_status>,
76 "was_changed": <bool status_was_actually_changed> },
74 "was_changed": <bool status_was_actually_changed> },
77 }
75 },
78 error : null
76 error : null
79
77
80
78
81 create_pull_request
79 create_pull_request
@@ -109,9 +107,8 b' create_pull_request'
109 :param reviewers: Set the new pull request reviewers list.
107 :param reviewers: Set the new pull request reviewers list.
110 :type reviewers: Optional(list)
108 :type reviewers: Optional(list)
111 Accepts username strings or objects of the format:
109 Accepts username strings or objects of the format:
112 {
110
113 'username': 'nick', 'reasons': ['original author']
111 {'username': 'nick', 'reasons': ['original author']}
114 }
115
112
116
113
117 get_pull_request
114 get_pull_request
@@ -305,9 +302,8 b' merge_pull_request'
305
302
306 .. code-block:: bash
303 .. code-block:: bash
307
304
308 "id": <id_given_in_input>,
305 "id": <id_given_in_input>,
309 "result":
306 "result": {
310 {
311 "executed": "<bool>",
307 "executed": "<bool>",
312 "failure_reason": "<int>",
308 "failure_reason": "<int>",
313 "merge_commit_id": "<merge_commit_id>",
309 "merge_commit_id": "<merge_commit_id>",
@@ -318,7 +314,7 b' merge_pull_request'
318 "name": "<name>"
314 "name": "<name>"
319 }
315 }
320 },
316 },
321 "error": null
317 "error": null
322
318
323
319
324 update_pull_request
320 update_pull_request
@@ -349,9 +345,8 b' update_pull_request'
349
345
350 .. code-block:: bash
346 .. code-block:: bash
351
347
352 id : <id_given_in_input>
348 id : <id_given_in_input>
353 result :
349 result : {
354 {
355 "msg": "Updated pull request `63`",
350 "msg": "Updated pull request `63`",
356 "pull_request": <pull_request_object>,
351 "pull_request": <pull_request_object>,
357 "updated_reviewers": {
352 "updated_reviewers": {
@@ -371,6 +366,6 b' update_pull_request'
371 "removed": []
366 "removed": []
372 }
367 }
373 }
368 }
374 error : null
369 error : null
375
370
376
371
@@ -85,6 +85,58 b' get_ip'
85 }
85 }
86
86
87
87
88 get_method
89 ----------
90
91 .. py:function:: get_method(apiuser, pattern=<Optional:'*'>)
92
93 Returns list of all available API methods. By default match pattern
94 os "*" but any other pattern can be specified. eg *comment* will return
95 all methods with comment inside them. If just single method is matched
96 returned data will also include method specification
97
98 This command can only be run using an |authtoken| with admin rights to
99 the specified repository.
100
101 This command takes the following options:
102
103 :param apiuser: This is filled automatically from the |authtoken|.
104 :type apiuser: AuthUser
105 :param pattern: pattern to match method names against
106 :type older_then: Optional("*")
107
108 Example output:
109
110 .. code-block:: bash
111
112 id : <id_given_in_input>
113 "result": [
114 "changeset_comment",
115 "comment_pull_request",
116 "comment_commit"
117 ]
118 error : null
119
120 .. code-block:: bash
121
122 id : <id_given_in_input>
123 "result": [
124 "comment_commit",
125 {
126 "apiuser": "<RequiredType>",
127 "comment_type": "<Optional:u'note'>",
128 "commit_id": "<RequiredType>",
129 "message": "<RequiredType>",
130 "repoid": "<RequiredType>",
131 "request": "<RequiredType>",
132 "resolves_comment_id": "<Optional:None>",
133 "status": "<Optional:None>",
134 "userid": "<Optional:<OptionalAttr:apiuser>>"
135 }
136 ]
137 error : null
138
139
88 get_server_info
140 get_server_info
89 ---------------
141 ---------------
90
142
@@ -41,15 +41,16 b' create_user'
41 :type force_password_change: Optional(``True`` | ``False``)
41 :type force_password_change: Optional(``True`` | ``False``)
42 :param create_personal_repo_group: Create personal repo group for this user
42 :param create_personal_repo_group: Create personal repo group for this user
43 :type create_personal_repo_group: Optional(``True`` | ``False``)
43 :type create_personal_repo_group: Optional(``True`` | ``False``)
44
44 Example output:
45 Example output:
45
46
46 .. code-block:: bash
47 .. code-block:: bash
47
48
48 id : <id_given_in_input>
49 id : <id_given_in_input>
49 result: {
50 result: {
50 "msg" : "created new user `<username>`",
51 "msg" : "created new user `<username>`",
51 "user": <user_obj>
52 "user": <user_obj>
52 }
53 }
53 error: null
54 error: null
54
55
55 Example error output:
56 Example error output:
@@ -98,9 +99,9 b' delete_user'
98
99
99 id : <id_given_in_input>
100 id : <id_given_in_input>
100 result: {
101 result: {
101 "msg" : "deleted user ID:<userid> <username>",
102 "msg" : "deleted user ID:<userid> <username>",
102 "user": null
103 "user": null
103 }
104 }
104 error: null
105 error: null
105
106
106 Example error output:
107 Example error output:
@@ -145,8 +146,8 b' get_user'
145 "result": {
146 "result": {
146 "active": true,
147 "active": true,
147 "admin": false,
148 "admin": false,
148 "api_key": "api-key",
149 "api_keys": [ list of keys ],
149 "api_keys": [ list of keys ],
150 "auth_tokens": [ list of tokens with details ],
150 "email": "user@example.com",
151 "email": "user@example.com",
151 "emails": [
152 "emails": [
152 "user@example.com"
153 "user@example.com"
@@ -157,6 +158,7 b' get_user'
157 "ip_addresses": [],
158 "ip_addresses": [],
158 "language": null,
159 "language": null,
159 "last_login": "Timestamp",
160 "last_login": "Timestamp",
161 "last_activity": "Timestamp",
160 "lastname": "surnae",
162 "lastname": "surnae",
161 "permissions": {
163 "permissions": {
162 "global": [
164 "global": [
@@ -183,6 +185,32 b' get_user'
183 }
185 }
184
186
185
187
188 get_user_audit_logs
189 -------------------
190
191 .. py:function:: get_user_audit_logs(apiuser, userid=<Optional:<OptionalAttr:apiuser>>)
192
193 Fetches all action logs made by the specified user.
194
195 This command takes the following options:
196
197 :param apiuser: This is filled automatically from the |authtoken|.
198 :type apiuser: AuthUser
199 :param userid: Sets the userid whose list of locked |repos| will be
200 displayed.
201 :type userid: Optional(str or int)
202
203 Example output:
204
205 .. code-block:: bash
206
207 id : <id_given_in_input>
208 result : {
209 [action, action,...]
210 }
211 error : null
212
213
186 get_user_locks
214 get_user_locks
187 --------------
215 --------------
188
216
@@ -232,7 +260,7 b' get_users'
232 .. code-block:: bash
260 .. code-block:: bash
233
261
234 id : <id_given_in_input>
262 id : <id_given_in_input>
235 result: [<user_object>, ...]
263 result: [<user_object>, ...]
236 error: null
264 error: null
237
265
238
266
@@ -279,9 +307,9 b' update_user'
279
307
280 id : <id_given_in_input>
308 id : <id_given_in_input>
281 result: {
309 result: {
282 "msg" : "updated user ID:<userid> <username>",
310 "msg" : "updated user ID:<userid> <username>",
283 "user": <user_object>,
311 "user": <user_object>,
284 }
312 }
285 error: null
313 error: null
286
314
287 Example error output:
315 Example error output:
@@ -19,4 +19,11 b' authentication.'
19 # Set the Active Directory user surname
19 # Set the Active Directory user surname
20 Last Name Attribute = user_surname
20 Last Name Attribute = user_surname
21 # Set the Active Directory user email
21 # Set the Active Directory user email
22 E-mail Attribute = userEmail No newline at end of file
22 E-mail Attribute = userEmail
23
24
25 Below is example setup that can be used with Active Directory and ldap groups.
26
27 .. image:: ../images/ldap-groups-example.png
28 :alt: LDAP/AD setup example
29 :scale: 50 % No newline at end of file
@@ -104,4 +104,10 b' The following are optional when enabling'
104 following directory: `/etc/openldap/cacerts`
104 following directory: `/etc/openldap/cacerts`
105
105
106
106
107 Below is example setup that can be used with Active Directory and ldap groups.
108
109 .. image:: ../images/ldap-groups-example.png
110 :alt: LDAP/AD setup example
111 :scale: 50 %
112
107 .. _RFC 2254: http://www.rfc-base.org/rfc-2254.html No newline at end of file
113 .. _RFC 2254: http://www.rfc-base.org/rfc-2254.html
@@ -114,6 +114,30 b' following command from inside the cloned'
114 fine on both MacOS and Linux platforms.
114 fine on both MacOS and Linux platforms.
115
115
116
116
117 Create config.nix for development
118 ---------------------------------
119
120 In order to run proper tests and setup linking across projects, a config.nix
121 file needs to be setup::
122
123 # create config
124 mkdir -p ~/.nixpkgs
125 touch ~/.nixpkgs/config.nix
126
127 # put the below content into the ~/.nixpkgs/config.nix file
128 # adjusts, the path to where you cloned your repositories.
129
130 {
131 rc = {
132 sources = {
133 rhodecode-vcsserver = "/home/dev/rhodecode-vcsserver";
134 rhodecode-enterprise-ce = "/home/dev/rhodecode-enterprise-ce";
135 rhodecode-enterprise-ee = "/home/dev/rhodecode-enterprise-ee";
136 };
137 };
138 }
139
140
117
141
118 Creating a Development Configuration
142 Creating a Development Configuration
119 ------------------------------------
143 ------------------------------------
@@ -21,7 +21,7 b' New Features'
21 - Pull request reviewers (EE only): added new default reviewers functionality.
21 - Pull request reviewers (EE only): added new default reviewers functionality.
22 Allows picking users or user groups defined as reviewers for new pull request.
22 Allows picking users or user groups defined as reviewers for new pull request.
23 Picking reviewers can be based on branch name, changed file name patterns or
23 Picking reviewers can be based on branch name, changed file name patterns or
24 original author of changed source code. eg *.css -> design team.
24 original author of changed source code. eg \*.css -> design team.
25 Master branch -> repo owner, fixes #1131.
25 Master branch -> repo owner, fixes #1131.
26 - Pull request reviewers: store and show reasons why given person is a reviewer.
26 - Pull request reviewers: store and show reasons why given person is a reviewer.
27 Manually adding reviewers after creating a PR will now be also indicated
27 Manually adding reviewers after creating a PR will now be also indicated
@@ -85,7 +85,7 b' General'
85 input for text box.
85 input for text box.
86 - Api: WARNING DEPRECATION, refactor repository group schemas. Fixes #4133.
86 - Api: WARNING DEPRECATION, refactor repository group schemas. Fixes #4133.
87 When using create_repo, create_repo_group, update_repo, update_repo_group
87 When using create_repo, create_repo_group, update_repo, update_repo_group
88 the *_name parameter now takes full path including sub repository groups.
88 the \*_name parameter now takes full path including sub repository groups.
89 This is the only way to add resource under another repository group.
89 This is the only way to add resource under another repository group.
90 Furthermore giving non-existing path will no longer create the missing
90 Furthermore giving non-existing path will no longer create the missing
91 structure. This change makes the api more consistent, it better validates
91 structure. This change makes the api more consistent, it better validates
@@ -9,6 +9,7 b' Release Notes'
9 .. toctree::
9 .. toctree::
10 :maxdepth: 1
10 :maxdepth: 1
11
11
12 release-notes-4.7.0.rst
12 release-notes-4.6.1.rst
13 release-notes-4.6.1.rst
13 release-notes-4.6.0.rst
14 release-notes-4.6.0.rst
14 release-notes-4.5.2.rst
15 release-notes-4.5.2.rst
@@ -9,6 +9,36 b' has a large files extension which tracks'
9 means that the large files are only downloaded when they are needed as part
9 means that the large files are only downloaded when they are needed as part
10 of the current revision. This saves both disk space and bandwidth.
10 of the current revision. This saves both disk space and bandwidth.
11
11
12
13 Enabling HG Largefiles
14 ++++++++++++++++++++++
15
16 Mercurial Largefiles extension is disabled by default within |RC| Server.
17
18 To enable Mercurial Largefiles Globally:
19
20 - Go to :menuselection:`Admin --> Settings --> VCS`
21
22 - Scroll down into `Mercurial settings`
23
24 - Tick `Enable largefiles extension`
25
26 - Save your settings.
27
28 Those settings apply globally to each repository that inherits from the defaults
29 You can leave `largefiles extension` disabled globally, and only enable it per
30 repository that would use the largefiles.
31
32
33 .. note::
34
35 You might want to adjust the global storage location at that point, however
36 we recommend leaving the default one created.
37
38
39 Installing and using the |hg| Largefiles
40 ++++++++++++++++++++++++++++++++++++++++
41
12 To find out more, see the |hg| `Large Files Extensions Documentation`_.
42 To find out more, see the |hg| `Large Files Extensions Documentation`_.
13
43
14 To configure the large files extension, you need to set up your
44 To configure the large files extension, you need to set up your
@@ -12,6 +12,7 b' then please send a request to support@rh'
12
12
13 deploy-from-host
13 deploy-from-host
14 hg-large-ext
14 hg-large-ext
15 git-lfs-ext
15 multi-instance-setup
16 multi-instance-setup
16 scaling-best-practices
17 scaling-best-practices
17 squash-commits
18 squash-commits
@@ -48,12 +48,27 b' self: super: {'
48 ];
48 ];
49 });
49 });
50
50
51 nbconvert = super.nbconvert.override (attrs: {
52 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
53 # marcink: plug in jupyter-client for notebook rendering
54 self.jupyter-client
55 ];
56 });
57
51 ipython = super.ipython.override (attrs: {
58 ipython = super.ipython.override (attrs: {
52 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
59 propagatedBuildInputs = attrs.propagatedBuildInputs ++ [
53 self.gnureadline
60 self.gnureadline
54 ];
61 ];
55 });
62 });
56
63
64 celery = super.celery.override (attrs: {
65 # The current version of kombu needs some patching to work with the
66 # other libs. Should be removed once we update celery and kombu.
67 patches = [
68 ./patch-celery-dateutil.diff
69 ];
70 });
71
57 kombu = super.kombu.override (attrs: {
72 kombu = super.kombu.override (attrs: {
58 # The current version of kombu needs some patching to work with the
73 # The current version of kombu needs some patching to work with the
59 # other libs. Should be removed once we update celery and kombu.
74 # other libs. Should be removed once we update celery and kombu.
@@ -197,19 +197,6 b''
197 license = [ pkgs.lib.licenses.bsdOriginal ];
197 license = [ pkgs.lib.licenses.bsdOriginal ];
198 };
198 };
199 };
199 };
200 Pyro4 = super.buildPythonPackage {
201 name = "Pyro4-4.41";
202 buildInputs = with self; [];
203 doCheck = false;
204 propagatedBuildInputs = with self; [serpent];
205 src = fetchurl {
206 url = "https://pypi.python.org/packages/56/2b/89b566b4bf3e7f8ba790db2d1223852f8cb454c52cab7693dd41f608ca2a/Pyro4-4.41.tar.gz";
207 md5 = "ed69e9bfafa9c06c049a87cb0c4c2b6c";
208 };
209 meta = {
210 license = [ pkgs.lib.licenses.mit ];
211 };
212 };
213 Routes = super.buildPythonPackage {
200 Routes = super.buildPythonPackage {
214 name = "Routes-1.13";
201 name = "Routes-1.13";
215 buildInputs = with self; [];
202 buildInputs = with self; [];
@@ -444,6 +431,19 b''
444 license = [ pkgs.lib.licenses.mit ];
431 license = [ pkgs.lib.licenses.mit ];
445 };
432 };
446 };
433 };
434 bleach = super.buildPythonPackage {
435 name = "bleach-1.5.0";
436 buildInputs = with self; [];
437 doCheck = false;
438 propagatedBuildInputs = with self; [six html5lib];
439 src = fetchurl {
440 url = "https://pypi.python.org/packages/99/00/25a8fce4de102bf6e3cc76bc4ea60685b2fee33bde1b34830c70cacc26a7/bleach-1.5.0.tar.gz";
441 md5 = "b663300efdf421b3b727b19d7be9c7e7";
442 };
443 meta = {
444 license = [ pkgs.lib.licenses.asl20 ];
445 };
446 };
447 bottle = super.buildPythonPackage {
447 bottle = super.buildPythonPackage {
448 name = "bottle-0.12.8";
448 name = "bottle-0.12.8";
449 buildInputs = with self; [];
449 buildInputs = with self; [];
@@ -535,6 +535,19 b''
535 license = [ pkgs.lib.licenses.bsdOriginal ];
535 license = [ pkgs.lib.licenses.bsdOriginal ];
536 };
536 };
537 };
537 };
538 configparser = super.buildPythonPackage {
539 name = "configparser-3.5.0";
540 buildInputs = with self; [];
541 doCheck = false;
542 propagatedBuildInputs = with self; [];
543 src = fetchurl {
544 url = "https://pypi.python.org/packages/7c/69/c2ce7e91c89dc073eb1aa74c0621c3eefbffe8216b3f9af9d3885265c01c/configparser-3.5.0.tar.gz";
545 md5 = "cfdd915a5b7a6c09917a64a573140538";
546 };
547 meta = {
548 license = [ pkgs.lib.licenses.mit ];
549 };
550 };
538 cov-core = super.buildPythonPackage {
551 cov-core = super.buildPythonPackage {
539 name = "cov-core-1.15.0";
552 name = "cov-core-1.15.0";
540 buildInputs = with self; [];
553 buildInputs = with self; [];
@@ -562,29 +575,29 b''
562 };
575 };
563 };
576 };
564 cssselect = super.buildPythonPackage {
577 cssselect = super.buildPythonPackage {
565 name = "cssselect-0.9.1";
578 name = "cssselect-1.0.1";
566 buildInputs = with self; [];
579 buildInputs = with self; [];
567 doCheck = false;
580 doCheck = false;
568 propagatedBuildInputs = with self; [];
581 propagatedBuildInputs = with self; [];
569 src = fetchurl {
582 src = fetchurl {
570 url = "https://pypi.python.org/packages/aa/e5/9ee1460d485b94a6d55732eb7ad5b6c084caf73dd6f9cb0bb7d2a78fafe8/cssselect-0.9.1.tar.gz";
583 url = "https://pypi.python.org/packages/77/ff/9c865275cd19290feba56344eba570e719efb7ca5b34d67ed12b22ebbb0d/cssselect-1.0.1.tar.gz";
571 md5 = "c74f45966277dc7a0f768b9b0f3522ac";
584 md5 = "3fa03bf82a9f0b1223c0f1eb1369e139";
572 };
585 };
573 meta = {
586 meta = {
574 license = [ pkgs.lib.licenses.bsdOriginal ];
587 license = [ pkgs.lib.licenses.bsdOriginal ];
575 };
588 };
576 };
589 };
577 decorator = super.buildPythonPackage {
590 decorator = super.buildPythonPackage {
578 name = "decorator-3.4.2";
591 name = "decorator-4.0.11";
579 buildInputs = with self; [];
592 buildInputs = with self; [];
580 doCheck = false;
593 doCheck = false;
581 propagatedBuildInputs = with self; [];
594 propagatedBuildInputs = with self; [];
582 src = fetchurl {
595 src = fetchurl {
583 url = "https://pypi.python.org/packages/35/3a/42566eb7a2cbac774399871af04e11d7ae3fc2579e7dae85213b8d1d1c57/decorator-3.4.2.tar.gz";
596 url = "https://pypi.python.org/packages/cc/ac/5a16f1fc0506ff72fcc8fd4e858e3a1c231f224ab79bb7c4c9b2094cc570/decorator-4.0.11.tar.gz";
584 md5 = "9e0536870d2b83ae27d58dbf22582f4d";
597 md5 = "73644c8f0bd4983d1b6a34b49adec0ae";
585 };
598 };
586 meta = {
599 meta = {
587 license = [ pkgs.lib.licenses.bsdOriginal ];
600 license = [ pkgs.lib.licenses.bsdOriginal { fullName = "new BSD License"; } ];
588 };
601 };
589 };
602 };
590 deform = super.buildPythonPackage {
603 deform = super.buildPythonPackage {
@@ -678,6 +691,19 b''
678 license = [ pkgs.lib.licenses.asl20 ];
691 license = [ pkgs.lib.licenses.asl20 ];
679 };
692 };
680 };
693 };
694 entrypoints = super.buildPythonPackage {
695 name = "entrypoints-0.2.2";
696 buildInputs = with self; [];
697 doCheck = false;
698 propagatedBuildInputs = with self; [configparser];
699 src = fetchurl {
700 url = "https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313";
701 md5 = "7db37771aea9ac9fefe093e5d6987313";
702 };
703 meta = {
704 license = [ pkgs.lib.licenses.mit ];
705 };
706 };
681 enum34 = super.buildPythonPackage {
707 enum34 = super.buildPythonPackage {
682 name = "enum34-1.1.6";
708 name = "enum34-1.1.6";
683 buildInputs = with self; [];
709 buildInputs = with self; [];
@@ -691,6 +717,19 b''
691 license = [ pkgs.lib.licenses.bsdOriginal ];
717 license = [ pkgs.lib.licenses.bsdOriginal ];
692 };
718 };
693 };
719 };
720 functools32 = super.buildPythonPackage {
721 name = "functools32-3.2.3.post2";
722 buildInputs = with self; [];
723 doCheck = false;
724 propagatedBuildInputs = with self; [];
725 src = fetchurl {
726 url = "https://pypi.python.org/packages/5e/1a/0aa2c8195a204a9f51284018562dea77e25511f02fe924fac202fc012172/functools32-3.2.3-2.zip";
727 md5 = "d55232eb132ec779e6893c902a0bc5ad";
728 };
729 meta = {
730 license = [ pkgs.lib.licenses.psfl ];
731 };
732 };
694 future = super.buildPythonPackage {
733 future = super.buildPythonPackage {
695 name = "future-0.14.3";
734 name = "future-0.14.3";
696 buildInputs = with self; [];
735 buildInputs = with self; [];
@@ -782,6 +821,19 b''
782 license = [ pkgs.lib.licenses.mit ];
821 license = [ pkgs.lib.licenses.mit ];
783 };
822 };
784 };
823 };
824 html5lib = super.buildPythonPackage {
825 name = "html5lib-0.9999999";
826 buildInputs = with self; [];
827 doCheck = false;
828 propagatedBuildInputs = with self; [six];
829 src = fetchurl {
830 url = "https://pypi.python.org/packages/ae/ae/bcb60402c60932b32dfaf19bb53870b29eda2cd17551ba5639219fb5ebf9/html5lib-0.9999999.tar.gz";
831 md5 = "ef43cb05e9e799f25d65d1135838a96f";
832 };
833 meta = {
834 license = [ pkgs.lib.licenses.mit ];
835 };
836 };
785 infrae.cache = super.buildPythonPackage {
837 infrae.cache = super.buildPythonPackage {
786 name = "infrae.cache-1.0.1";
838 name = "infrae.cache-1.0.1";
787 buildInputs = with self; [];
839 buildInputs = with self; [];
@@ -873,6 +925,45 b''
873 license = [ pkgs.lib.licenses.bsdOriginal ];
925 license = [ pkgs.lib.licenses.bsdOriginal ];
874 };
926 };
875 };
927 };
928 jsonschema = super.buildPythonPackage {
929 name = "jsonschema-2.6.0";
930 buildInputs = with self; [];
931 doCheck = false;
932 propagatedBuildInputs = with self; [functools32];
933 src = fetchurl {
934 url = "https://pypi.python.org/packages/58/b9/171dbb07e18c6346090a37f03c7e74410a1a56123f847efed59af260a298/jsonschema-2.6.0.tar.gz";
935 md5 = "50c6b69a373a8b55ff1e0ec6e78f13f4";
936 };
937 meta = {
938 license = [ pkgs.lib.licenses.mit ];
939 };
940 };
941 jupyter-client = super.buildPythonPackage {
942 name = "jupyter-client-5.0.0";
943 buildInputs = with self; [];
944 doCheck = false;
945 propagatedBuildInputs = with self; [traitlets jupyter-core pyzmq python-dateutil];
946 src = fetchurl {
947 url = "https://pypi.python.org/packages/e5/6f/65412ed462202b90134b7e761b0b7e7f949e07a549c1755475333727b3d0/jupyter_client-5.0.0.tar.gz";
948 md5 = "1acd331b5c9fb4d79dae9939e79f2426";
949 };
950 meta = {
951 license = [ pkgs.lib.licenses.bsdOriginal ];
952 };
953 };
954 jupyter-core = super.buildPythonPackage {
955 name = "jupyter-core-4.3.0";
956 buildInputs = with self; [];
957 doCheck = false;
958 propagatedBuildInputs = with self; [traitlets];
959 src = fetchurl {
960 url = "https://pypi.python.org/packages/2f/39/5138f975100ce14d150938df48a83cd852a3fd8e24b1244f4113848e69e2/jupyter_core-4.3.0.tar.gz";
961 md5 = "18819511a809afdeed9a995a9c27bcfb";
962 };
963 meta = {
964 license = [ pkgs.lib.licenses.bsdOriginal ];
965 };
966 };
876 kombu = super.buildPythonPackage {
967 kombu = super.buildPythonPackage {
877 name = "kombu-1.5.1";
968 name = "kombu-1.5.1";
878 buildInputs = with self; [];
969 buildInputs = with self; [];
@@ -887,13 +978,13 b''
887 };
978 };
888 };
979 };
889 lxml = super.buildPythonPackage {
980 lxml = super.buildPythonPackage {
890 name = "lxml-3.4.4";
981 name = "lxml-3.7.3";
891 buildInputs = with self; [];
982 buildInputs = with self; [];
892 doCheck = false;
983 doCheck = false;
893 propagatedBuildInputs = with self; [];
984 propagatedBuildInputs = with self; [];
894 src = fetchurl {
985 src = fetchurl {
895 url = "https://pypi.python.org/packages/63/c7/4f2a2a4ad6c6fa99b14be6b3c1cece9142e2d915aa7c43c908677afc8fa4/lxml-3.4.4.tar.gz";
986 url = "https://pypi.python.org/packages/39/e8/a8e0b1fa65dd021d48fe21464f71783655f39a41f218293c1c590d54eb82/lxml-3.7.3.tar.gz";
896 md5 = "a9a65972afc173ec7a39c585f4eea69c";
987 md5 = "075692ce442e69bbd604d44e21c02753";
897 };
988 };
898 meta = {
989 meta = {
899 license = [ pkgs.lib.licenses.bsdOriginal ];
990 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -912,6 +1003,19 b''
912 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1003 license = [ { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
913 };
1004 };
914 };
1005 };
1006 mistune = super.buildPythonPackage {
1007 name = "mistune-0.7.3";
1008 buildInputs = with self; [];
1009 doCheck = false;
1010 propagatedBuildInputs = with self; [];
1011 src = fetchurl {
1012 url = "https://pypi.python.org/packages/88/1e/be99791262b3a794332fda598a07c2749a433b9378586361ba9d8e824607/mistune-0.7.3.tar.gz";
1013 md5 = "4eba50bd121b83716fa4be6a4049004b";
1014 };
1015 meta = {
1016 license = [ pkgs.lib.licenses.bsdOriginal ];
1017 };
1018 };
915 mock = super.buildPythonPackage {
1019 mock = super.buildPythonPackage {
916 name = "mock-1.0.1";
1020 name = "mock-1.0.1";
917 buildInputs = with self; [];
1021 buildInputs = with self; [];
@@ -938,6 +1042,32 b''
938 license = [ pkgs.lib.licenses.asl20 ];
1042 license = [ pkgs.lib.licenses.asl20 ];
939 };
1043 };
940 };
1044 };
1045 nbconvert = super.buildPythonPackage {
1046 name = "nbconvert-5.1.1";
1047 buildInputs = with self; [];
1048 doCheck = false;
1049 propagatedBuildInputs = with self; [mistune Jinja2 Pygments traitlets jupyter-core nbformat entrypoints bleach pandocfilters testpath];
1050 src = fetchurl {
1051 url = "https://pypi.python.org/packages/95/58/df1c91f1658ee5df19097f915a1e71c91fc824a708d82d2b2e35f8b80e9a/nbconvert-5.1.1.tar.gz";
1052 md5 = "d0263fb03a44db2f94eea09a608ed813";
1053 };
1054 meta = {
1055 license = [ pkgs.lib.licenses.bsdOriginal ];
1056 };
1057 };
1058 nbformat = super.buildPythonPackage {
1059 name = "nbformat-4.3.0";
1060 buildInputs = with self; [];
1061 doCheck = false;
1062 propagatedBuildInputs = with self; [ipython-genutils traitlets jsonschema jupyter-core];
1063 src = fetchurl {
1064 url = "https://pypi.python.org/packages/f9/c5/89df4abf906f766727f976e170caa85b4f1c1d1feb1f45d716016e68e19f/nbformat-4.3.0.tar.gz";
1065 md5 = "9a00d20425914cd5ba5f97769d9963ca";
1066 };
1067 meta = {
1068 license = [ pkgs.lib.licenses.bsdOriginal ];
1069 };
1070 };
941 nose = super.buildPythonPackage {
1071 nose = super.buildPythonPackage {
942 name = "nose-1.3.6";
1072 name = "nose-1.3.6";
943 buildInputs = with self; [];
1073 buildInputs = with self; [];
@@ -977,6 +1107,19 b''
977 license = [ pkgs.lib.licenses.asl20 ];
1107 license = [ pkgs.lib.licenses.asl20 ];
978 };
1108 };
979 };
1109 };
1110 pandocfilters = super.buildPythonPackage {
1111 name = "pandocfilters-1.4.1";
1112 buildInputs = with self; [];
1113 doCheck = false;
1114 propagatedBuildInputs = with self; [];
1115 src = fetchurl {
1116 url = "https://pypi.python.org/packages/e3/1f/21d1b7e8ca571e80b796c758d361fdf5554335ff138158654684bc5401d8/pandocfilters-1.4.1.tar.gz";
1117 md5 = "7680d9f9ec07397dd17f380ee3818b9d";
1118 };
1119 meta = {
1120 license = [ pkgs.lib.licenses.bsdOriginal ];
1121 };
1122 };
980 paramiko = super.buildPythonPackage {
1123 paramiko = super.buildPythonPackage {
981 name = "paramiko-1.15.1";
1124 name = "paramiko-1.15.1";
982 buildInputs = with self; [];
1125 buildInputs = with self; [];
@@ -1043,13 +1186,13 b''
1043 };
1186 };
1044 };
1187 };
1045 prompt-toolkit = super.buildPythonPackage {
1188 prompt-toolkit = super.buildPythonPackage {
1046 name = "prompt-toolkit-1.0.9";
1189 name = "prompt-toolkit-1.0.13";
1047 buildInputs = with self; [];
1190 buildInputs = with self; [];
1048 doCheck = false;
1191 doCheck = false;
1049 propagatedBuildInputs = with self; [six wcwidth];
1192 propagatedBuildInputs = with self; [six wcwidth];
1050 src = fetchurl {
1193 src = fetchurl {
1051 url = "https://pypi.python.org/packages/83/14/5ac258da6c530eca02852ee25c7a9ff3ca78287bb4c198d0d0055845d856/prompt_toolkit-1.0.9.tar.gz";
1194 url = "https://pypi.python.org/packages/23/be/4876b52d5cc159cbd4b0ff6e7aa419a26470849a43a8f647857a4a24467b/prompt_toolkit-1.0.13.tar.gz";
1052 md5 = "a39f91a54308fb7446b1a421c11f227c";
1195 md5 = "427b496d2c147bd3819bc3a7f6e0d493";
1053 };
1196 };
1054 meta = {
1197 meta = {
1055 license = [ pkgs.lib.licenses.bsdOriginal ];
1198 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -1199,13 +1342,13 b''
1199 };
1342 };
1200 };
1343 };
1201 pyramid = super.buildPythonPackage {
1344 pyramid = super.buildPythonPackage {
1202 name = "pyramid-1.6.1";
1345 name = "pyramid-1.7.4";
1203 buildInputs = with self; [];
1346 buildInputs = with self; [];
1204 doCheck = false;
1347 doCheck = false;
1205 propagatedBuildInputs = with self; [setuptools WebOb repoze.lru zope.interface zope.deprecation venusian translationstring PasteDeploy];
1348 propagatedBuildInputs = with self; [setuptools WebOb repoze.lru zope.interface zope.deprecation venusian translationstring PasteDeploy];
1206 src = fetchurl {
1349 src = fetchurl {
1207 url = "https://pypi.python.org/packages/30/b3/fcc4a2a4800cbf21989e00454b5828cf1f7fe35c63e0810b350e56d4c475/pyramid-1.6.1.tar.gz";
1350 url = "https://pypi.python.org/packages/33/91/55f5c661f8923902cd1f68d75f2b937c45e7682857356cf18f0be5493899/pyramid-1.7.4.tar.gz";
1208 md5 = "b18688ff3cc33efdbb098a35b45dd122";
1351 md5 = "6ef1dfdcff9136d04490410757c4c446";
1209 };
1352 };
1210 meta = {
1353 meta = {
1211 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
1354 license = [ { fullName = "Repoze Public License"; } { fullName = "BSD-derived (http://www.repoze.org/LICENSE.txt)"; } ];
@@ -1225,13 +1368,13 b''
1225 };
1368 };
1226 };
1369 };
1227 pyramid-debugtoolbar = super.buildPythonPackage {
1370 pyramid-debugtoolbar = super.buildPythonPackage {
1228 name = "pyramid-debugtoolbar-2.4.2";
1371 name = "pyramid-debugtoolbar-3.0.5";
1229 buildInputs = with self; [];
1372 buildInputs = with self; [];
1230 doCheck = false;
1373 doCheck = false;
1231 propagatedBuildInputs = with self; [pyramid pyramid-mako repoze.lru Pygments];
1374 propagatedBuildInputs = with self; [pyramid pyramid-mako repoze.lru Pygments];
1232 src = fetchurl {
1375 src = fetchurl {
1233 url = "https://pypi.python.org/packages/89/00/ed5426ee41ed747ba3ffd30e8230841a6878286ea67d480b1444d24f06a2/pyramid_debugtoolbar-2.4.2.tar.gz";
1376 url = "https://pypi.python.org/packages/64/0e/df00bfb55605900e7a2f7e4a18dd83575a6651688e297d5a0aa4c208fd7d/pyramid_debugtoolbar-3.0.5.tar.gz";
1234 md5 = "073ea67086cc4bd5decc3a000853642d";
1377 md5 = "aebab8c3bfdc6f89e4d3adc1d126538e";
1235 };
1378 };
1236 meta = {
1379 meta = {
1237 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
1380 license = [ { fullName = "Repoze Public License"; } pkgs.lib.licenses.bsdOriginal ];
@@ -1368,16 +1511,16 b''
1368 };
1511 };
1369 };
1512 };
1370 python-dateutil = super.buildPythonPackage {
1513 python-dateutil = super.buildPythonPackage {
1371 name = "python-dateutil-1.5";
1514 name = "python-dateutil-2.1";
1372 buildInputs = with self; [];
1515 buildInputs = with self; [];
1373 doCheck = false;
1516 doCheck = false;
1374 propagatedBuildInputs = with self; [];
1517 propagatedBuildInputs = with self; [six];
1375 src = fetchurl {
1518 src = fetchurl {
1376 url = "https://pypi.python.org/packages/b4/7c/df59c89a753eb33c7c44e1dd42de0e9bc2ccdd5a4d576e0bfad97cc280cb/python-dateutil-1.5.tar.gz";
1519 url = "https://pypi.python.org/packages/65/52/9c18dac21f174ad31b65e22d24297864a954e6fe65876eba3f5773d2da43/python-dateutil-2.1.tar.gz";
1377 md5 = "0dcb1de5e5cad69490a3b6ab63f0cfa5";
1520 md5 = "1534bb15cf311f07afaa3aacba1c028b";
1378 };
1521 };
1379 meta = {
1522 meta = {
1380 license = [ pkgs.lib.licenses.psfl ];
1523 license = [ { fullName = "Simplified BSD"; } ];
1381 };
1524 };
1382 };
1525 };
1383 python-editor = super.buildPythonPackage {
1526 python-editor = super.buildPythonPackage {
@@ -1498,10 +1641,10 b''
1498 };
1641 };
1499 };
1642 };
1500 rhodecode-enterprise-ce = super.buildPythonPackage {
1643 rhodecode-enterprise-ce = super.buildPythonPackage {
1501 name = "rhodecode-enterprise-ce-4.6.1";
1644 name = "rhodecode-enterprise-ce-4.7.0";
1502 buildInputs = with self; [pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage cssselect lxml configobj];
1645 buildInputs = with self; [pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage configobj];
1503 doCheck = true;
1646 doCheck = true;
1504 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments pygments-markdown-lexer Pylons Pyro4 Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress celery channelstream colander decorator deform docutils gevent gunicorn infrae.cache ipython iso8601 kombu msgpack-python packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson subprocess32 waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt];
1647 propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments pygments-markdown-lexer Pylons Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic backport-ipaddress cssselect celery channelstream colander decorator deform docutils gevent gunicorn infrae.cache ipython iso8601 kombu lxml msgpack-python nbconvert packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson subprocess32 waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt];
1505 src = ./.;
1648 src = ./.;
1506 meta = {
1649 meta = {
1507 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
1650 license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ];
@@ -1520,19 +1663,6 b''
1520 license = [ { fullName = "AGPLv3 and Proprietary"; } ];
1663 license = [ { fullName = "AGPLv3 and Proprietary"; } ];
1521 };
1664 };
1522 };
1665 };
1523 serpent = super.buildPythonPackage {
1524 name = "serpent-1.15";
1525 buildInputs = with self; [];
1526 doCheck = false;
1527 propagatedBuildInputs = with self; [];
1528 src = fetchurl {
1529 url = "https://pypi.python.org/packages/7b/38/b2b27673a882ff2ea5871bb3e3e6b496ebbaafd1612e51990ffb158b9254/serpent-1.15.tar.gz";
1530 md5 = "e27b1aad5c218e16442f52abb7c7053a";
1531 };
1532 meta = {
1533 license = [ pkgs.lib.licenses.mit ];
1534 };
1535 };
1536 setproctitle = super.buildPythonPackage {
1666 setproctitle = super.buildPythonPackage {
1537 name = "setproctitle-1.1.8";
1667 name = "setproctitle-1.1.8";
1538 buildInputs = with self; [];
1668 buildInputs = with self; [];
@@ -1650,14 +1780,27 b''
1650 license = [ pkgs.lib.licenses.mit ];
1780 license = [ pkgs.lib.licenses.mit ];
1651 };
1781 };
1652 };
1782 };
1783 testpath = super.buildPythonPackage {
1784 name = "testpath-0.1";
1785 buildInputs = with self; [];
1786 doCheck = false;
1787 propagatedBuildInputs = with self; [];
1788 src = fetchurl {
1789 url = "https://pypi.python.org/packages/f9/c4/c0b22f35138bc26a6058c39cb61db1e8977e5e9550b12cd2cb02ef56fc51/testpath-0.1.tar.gz";
1790 md5 = "401918bcd0b0e5b71a9b909835117bc6";
1791 };
1792 meta = {
1793 license = [ pkgs.lib.licenses.mit ];
1794 };
1795 };
1653 traitlets = super.buildPythonPackage {
1796 traitlets = super.buildPythonPackage {
1654 name = "traitlets-4.3.1";
1797 name = "traitlets-4.3.2";
1655 buildInputs = with self; [];
1798 buildInputs = with self; [];
1656 doCheck = false;
1799 doCheck = false;
1657 propagatedBuildInputs = with self; [ipython-genutils six decorator enum34];
1800 propagatedBuildInputs = with self; [ipython-genutils six decorator enum34];
1658 src = fetchurl {
1801 src = fetchurl {
1659 url = "https://pypi.python.org/packages/b1/d6/5b5aa6d5c474691909b91493da1e8972e309c9f01ecfe4aeafd272eb3234/traitlets-4.3.1.tar.gz";
1802 url = "https://pypi.python.org/packages/a5/98/7f5ef2fe9e9e071813aaf9cb91d1a732e0a68b6c44a32b38cb8e14c3f069/traitlets-4.3.2.tar.gz";
1660 md5 = "dd0b1b6e5d31ce446d55a4b5e5083c98";
1803 md5 = "3068663f2f38fd939a9eb3a500ccc154";
1661 };
1804 };
1662 meta = {
1805 meta = {
1663 license = [ pkgs.lib.licenses.bsdOriginal ];
1806 license = [ pkgs.lib.licenses.bsdOriginal ];
@@ -2,7 +2,6 b''
2 testpaths = ./rhodecode
2 testpaths = ./rhodecode
3 pylons_config = rhodecode/tests/rhodecode.ini
3 pylons_config = rhodecode/tests/rhodecode.ini
4 vcsserver_protocol = http
4 vcsserver_protocol = http
5 vcsserver_config_pyro4 = rhodecode/tests/vcsserver_pyro4.ini
6 vcsserver_config_http = rhodecode/tests/vcsserver_http.ini
5 vcsserver_config_http = rhodecode/tests/vcsserver_http.ini
7 norecursedirs = tests/scripts
6 norecursedirs = tests/scripts
8 addopts = -k "not _BaseTest"
7 addopts = -k "not _BaseTest"
@@ -14,7 +14,8 b' channelstream==0.5.2'
14 click==5.1
14 click==5.1
15 colander==1.2
15 colander==1.2
16 configobj==5.0.6
16 configobj==5.0.6
17 decorator==3.4.2
17 cssselect==1.0.1
18 decorator==4.0.11
18 deform==2.0a2
19 deform==2.0a2
19 docutils==0.12
20 docutils==0.12
20 dogpile.cache==0.6.1
21 dogpile.cache==0.6.1
@@ -29,6 +30,7 b' iso8601==0.1.11'
29 itsdangerous==0.24
30 itsdangerous==0.24
30 Jinja2==2.7.3
31 Jinja2==2.7.3
31 kombu==1.5.1
32 kombu==1.5.1
33 lxml==3.7.3
32 Mako==1.0.6
34 Mako==1.0.6
33 Markdown==2.6.7
35 Markdown==2.6.7
34 MarkupSafe==0.23
36 MarkupSafe==0.23
@@ -42,6 +44,7 b' paramiko==1.15.1'
42 Paste==2.0.3
44 Paste==2.0.3
43 PasteDeploy==1.5.2
45 PasteDeploy==1.5.2
44 PasteScript==1.7.5
46 PasteScript==1.7.5
47 pathlib2==2.1.0
45 psutil==4.3.1
48 psutil==4.3.1
46 psycopg2==2.6.1
49 psycopg2==2.6.1
47 py-bcrypt==0.4
50 py-bcrypt==0.4
@@ -52,12 +55,12 b' pygments-markdown-lexer==0.1.0.dev39'
52 Pygments==2.2.0
55 Pygments==2.2.0
53 pyparsing==1.5.7
56 pyparsing==1.5.7
54 pyramid-beaker==0.8
57 pyramid-beaker==0.8
55 pyramid-debugtoolbar==2.4.2
58 pyramid-debugtoolbar==3.0.5
56 pyramid-jinja2==2.5
59 pyramid-jinja2==2.5
57 pyramid-mako==1.0.2
60 pyramid-mako==1.0.2
58 pyramid==1.6.1
61 pyramid==1.7.4
59 pysqlite==2.6.3
62 pysqlite==2.6.3
60 python-dateutil==1.5
63 python-dateutil==2.1
61 python-ldap==2.4.19
64 python-ldap==2.4.19
62 python-memcached==1.57
65 python-memcached==1.57
63 python-pam==1.8.2
66 python-pam==1.8.2
@@ -97,6 +100,12 b' https://code.rhodecode.com/upstream/pylo'
97 # not released py-gfm==0.1.3
100 # not released py-gfm==0.1.3
98 https://code.rhodecode.com/upstream/py-gfm/archive/0d66a19bc16e3d49de273c0f797d4e4781e8c0f2.tar.gz?md5=0d0d5385bfb629eea636a80b9c2bfd16#egg=py-gfm==0.1.3.rhodecode-upstream1
101 https://code.rhodecode.com/upstream/py-gfm/archive/0d66a19bc16e3d49de273c0f797d4e4781e8c0f2.tar.gz?md5=0d0d5385bfb629eea636a80b9c2bfd16#egg=py-gfm==0.1.3.rhodecode-upstream1
99
102
103 # IPYTHON RENDERING
104 # entrypoints backport, pypi version doesn't support egg installs
105 https://code.rhodecode.com/upstream/entrypoints/archive/96e6d645684e1af3d7df5b5272f3fe85a546b233.tar.gz?md5=7db37771aea9ac9fefe093e5d6987313#egg=entrypoints==0.2.2.rhodecode-upstream1
106 nbconvert==5.1.1
107 nbformat==4.3.0
108 jupyter_client==5.0.0
100
109
101 ## cli tools
110 ## cli tools
102 alembic==0.8.4
111 alembic==0.8.4
@@ -123,9 +132,5 b' https://code.rhodecode.com/rhodecode-too'
123 ## appenlight
132 ## appenlight
124 appenlight-client==0.6.14
133 appenlight-client==0.6.14
125
134
126 # Pyro/Deprecated TODO(Marcink): remove in 4.7 release.
127 Pyro4==4.41
128 serpent==1.15
129
130 ## test related requirements
135 ## test related requirements
131 -r requirements_test.txt
136 -r requirements_test.txt
@@ -13,5 +13,3 b' mock==1.0.1'
13 WebTest==1.4.3
13 WebTest==1.4.3
14 cov-core==1.15.0
14 cov-core==1.15.0
15 coverage==3.7.1
15 coverage==3.7.1
16 cssselect==0.9.1
17 lxml==3.4.4
@@ -1,1 +1,1 b''
1 4.6.1 No newline at end of file
1 4.7.0 No newline at end of file
@@ -51,7 +51,7 b' PYRAMID_SETTINGS = {}'
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 64 # defines current db version for migrations
54 __dbversion__ = 71 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
@@ -22,6 +22,7 b' import inspect'
22 import itertools
22 import itertools
23 import logging
23 import logging
24 import types
24 import types
25 import fnmatch
25
26
26 import decorator
27 import decorator
27 import venusian
28 import venusian
@@ -47,6 +48,18 b" DEFAULT_RENDERER = 'jsonrpc_renderer'"
47 DEFAULT_URL = '/_admin/apiv2'
48 DEFAULT_URL = '/_admin/apiv2'
48
49
49
50
51 def find_methods(jsonrpc_methods, pattern):
52 matches = OrderedDict()
53 if not isinstance(pattern, (list, tuple)):
54 pattern = [pattern]
55
56 for single_pattern in pattern:
57 for method_name, method in jsonrpc_methods.items():
58 if fnmatch.fnmatch(method_name, single_pattern):
59 matches[method_name] = method
60 return matches
61
62
50 class ExtJsonRenderer(object):
63 class ExtJsonRenderer(object):
51 """
64 """
52 Custom renderer that mkaes use of our ext_json lib
65 Custom renderer that mkaes use of our ext_json lib
@@ -143,7 +156,19 b' def exception_view(exc, request):'
143 log.debug('json-rpc method `%s` not found in list of '
156 log.debug('json-rpc method `%s` not found in list of '
144 'api calls: %s, rpc_id:%s',
157 'api calls: %s, rpc_id:%s',
145 method, request.registry.jsonrpc_methods.keys(), rpc_id)
158 method, request.registry.jsonrpc_methods.keys(), rpc_id)
146 fault_message = "No such method: {}".format(method)
159
160 similar = 'none'
161 try:
162 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
163 similar_found = find_methods(
164 request.registry.jsonrpc_methods, similar_paterns)
165 similar = ', '.join(similar_found.keys()) or similar
166 except Exception:
167 # make the whole above block safe
168 pass
169
170 fault_message = "No such method: {}. Similar methods: {}".format(
171 method, similar)
147
172
148 return jsonrpc_error(request, fault_message, rpc_id)
173 return jsonrpc_error(request, fault_message, rpc_id)
149
174
@@ -184,18 +209,18 b' def request_view(request):'
184 request.rpc_user = auth_u
209 request.rpc_user = auth_u
185
210
186 # now check if token is valid for API
211 # now check if token is valid for API
187 role = UserApiKeys.ROLE_API
212 auth_token = request.rpc_api_key
188 extra_auth_tokens = [
213 token_match = api_user.authenticate_by_token(
189 x.api_key for x in User.extra_valid_auth_tokens(api_user, role=role)]
214 auth_token, roles=[UserApiKeys.ROLE_API])
190 active_tokens = [api_user.api_key] + extra_auth_tokens
215 invalid_token = not token_match
191
216
192 log.debug('Checking if API key has proper role')
217 log.debug('Checking if API KEY is valid with proper role')
193 if request.rpc_api_key not in active_tokens:
218 if invalid_token:
194 return jsonrpc_error(
219 return jsonrpc_error(
195 request, retid=request.rpc_id,
220 request, retid=request.rpc_id,
196 message='API KEY has bad role for an API call')
221 message='API KEY invalid or, has bad role for an API call')
197
222
198 except Exception as e:
223 except Exception:
199 log.exception('Error on API AUTH')
224 log.exception('Error on API AUTH')
200 return jsonrpc_error(
225 return jsonrpc_error(
201 request, retid=request.rpc_id, message='Invalid API KEY')
226 request, retid=request.rpc_id, message='Invalid API KEY')
@@ -351,9 +376,10 b' class RoutePredicate(object):'
351 class NotFoundPredicate(object):
376 class NotFoundPredicate(object):
352 def __init__(self, val, config):
377 def __init__(self, val, config):
353 self.val = val
378 self.val = val
379 self.methods = config.registry.jsonrpc_methods
354
380
355 def text(self):
381 def text(self):
356 return 'jsonrpc method not found = %s' % self.val
382 return 'jsonrpc method not found = {}.'.format(self.val)
357
383
358 phash = text
384 phash = text
359
385
@@ -22,14 +22,19 b' import pytest'
22
22
23 from rhodecode.model.meta import Session
23 from rhodecode.model.meta import Session
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.auth_token import AuthTokenModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26
27
27
28
28 @pytest.fixture(scope="class")
29 @pytest.fixture(scope="class")
29 def testuser_api(request, pylonsapp):
30 def testuser_api(request, pylonsapp):
30 cls = request.cls
31 cls = request.cls
32
33 # ADMIN USER
31 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
34 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
32 cls.apikey = cls.usr.api_key
35 cls.apikey = cls.usr.api_key
36
37 # REGULAR USER
33 cls.test_user = UserModel().create_or_update(
38 cls.test_user = UserModel().create_or_update(
34 username='test-api',
39 username='test-api',
35 password='test',
40 password='test',
@@ -37,6 +42,11 b' def testuser_api(request, pylonsapp):'
37 firstname='first',
42 firstname='first',
38 lastname='last'
43 lastname='last'
39 )
44 )
45 # create TOKEN for user, if he doesn't have one
46 if not cls.test_user.api_key:
47 AuthTokenModel().create(
48 user=cls.test_user, description='TEST_USER_TOKEN')
49
40 Session().commit()
50 Session().commit()
41 cls.TEST_USER_LOGIN = cls.test_user.username
51 cls.TEST_USER_LOGIN = cls.test_user.username
42 cls.apikey_regular = cls.test_user.api_key
52 cls.apikey_regular = cls.test_user.api_key
@@ -80,7 +80,13 b' class TestApi(object):'
80 def test_api_non_existing_method(self, request):
80 def test_api_non_existing_method(self, request):
81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 expected = 'No such method: not_existing'
83 expected = 'No such method: not_existing. Similar methods: none'
84 assert_error(id_, expected, given=response.body)
85
86 def test_api_non_existing_method_have_similar(self, request):
87 id_, params = build_data(self.apikey, 'comment', args='xx')
88 response = api_call(self.app, params)
89 expected = 'No such method: comment. Similar methods: changeset_comment, comment_pull_request, comment_commit'
84 assert_error(id_, expected, given=response.body)
90 assert_error(id_, expected, given=response.body)
85
91
86 def test_api_disabled_user(self, request):
92 def test_api_disabled_user(self, request):
@@ -29,6 +29,7 b' from rhodecode.api.tests.utils import ('
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestClosePullRequest(object):
31 class TestClosePullRequest(object):
32
32 @pytest.mark.backends("git", "hg")
33 @pytest.mark.backends("git", "hg")
33 def test_api_close_pull_request(self, pr_util):
34 def test_api_close_pull_request(self, pr_util):
34 pull_request = pr_util.create_pull_request()
35 pull_request = pr_util.create_pull_request()
@@ -53,11 +53,10 b' class TestApiDeleteRepo(object):'
53 }
53 }
54 assert_ok(id_, expected, given=response.body)
54 assert_ok(id_, expected, given=response.body)
55
55
56 def test_api_delete_repo_by_non_admin_no_permission(
56 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
57 self, backend, user_regular):
58 repo = backend.create_repo()
57 repo = backend.create_repo()
59 id_, params = build_data(
58 id_, params = build_data(
60 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
59 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
61 response = api_call(self.app, params)
60 response = api_call(self.app, params)
62 expected = 'repository `%s` does not exist' % (repo.repo_name)
61 expected = 'repository `%s` does not exist' % (repo.repo_name)
63 assert_error(id_, expected, given=response.body)
62 assert_error(id_, expected, given=response.body)
@@ -23,7 +23,7 b' import pytest'
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.tests import TEST_USER_REGULAR_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_EMAIL
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
29
29
@@ -33,7 +33,8 b' class TestUpdateUserGroup(object):'
33 @pytest.mark.parametrize("changing_attr, updates", [
33 @pytest.mark.parametrize("changing_attr, updates", [
34 ('group_name', {'group_name': 'new_group_name'}),
34 ('group_name', {'group_name': 'new_group_name'}),
35 ('group_name', {'group_name': 'test_group_for_update'}),
35 ('group_name', {'group_name': 'test_group_for_update'}),
36 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
36 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
37 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
37 ('active', {'active': False}),
38 ('active', {'active': False}),
38 ('active', {'active': True})
39 ('active', {'active': True})
39 ])
40 ])
@@ -59,7 +60,8 b' class TestUpdateUserGroup(object):'
59 # TODO: mikhail: decide if we need to test against the commented params
60 # TODO: mikhail: decide if we need to test against the commented params
60 # ('group_name', {'group_name': 'new_group_name'}),
61 # ('group_name', {'group_name': 'new_group_name'}),
61 # ('group_name', {'group_name': 'test_group_for_update'}),
62 # ('group_name', {'group_name': 'test_group_for_update'}),
62 ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
63 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
64 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
63 ('active', {'active': False}),
65 ('active', {'active': False}),
64 ('active', {'active': True})
66 ('active', {'active': True})
65 ])
67 ])
@@ -181,29 +181,56 b' class TestGetRefHash(object):'
181 class TestUserByNameOrError(object):
181 class TestUserByNameOrError(object):
182 def test_user_found_by_id(self):
182 def test_user_found_by_id(self):
183 fake_user = Mock(id=123)
183 fake_user = Mock(id=123)
184
185 patcher = patch('rhodecode.model.user.UserModel.get_user')
186 with patcher as get_user:
187 get_user.return_value = fake_user
188
189 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
190 with patcher as get_by_username:
191 result = utils.get_user_or_error(123)
192 assert result == fake_user
193
194 def test_user_not_found_by_id_as_str(self):
195 fake_user = Mock(id=123)
196
184 patcher = patch('rhodecode.model.user.UserModel.get_user')
197 patcher = patch('rhodecode.model.user.UserModel.get_user')
185 with patcher as get_user:
198 with patcher as get_user:
186 get_user.return_value = fake_user
199 get_user.return_value = fake_user
187 result = utils.get_user_or_error('123')
200 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
188 assert result == fake_user
201 with patcher as get_by_username:
202 get_by_username.return_value = None
203
204 with pytest.raises(JSONRPCError):
205 utils.get_user_or_error('123')
189
206
190 def test_user_found_by_name(self):
207 def test_user_found_by_name(self):
191 fake_user = Mock(id=123)
208 fake_user = Mock(id=123)
192 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
209
193 with patcher as get_by_username:
210 patcher = patch('rhodecode.model.user.UserModel.get_user')
194 get_by_username.return_value = fake_user
211 with patcher as get_user:
195 result = utils.get_user_or_error('test')
212 get_user.return_value = None
196 assert result == fake_user
213
214 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
215 with patcher as get_by_username:
216 get_by_username.return_value = fake_user
217
218 result = utils.get_user_or_error('test')
219 assert result == fake_user
197
220
198 def test_user_not_found_by_id(self):
221 def test_user_not_found_by_id(self):
199 patcher = patch('rhodecode.model.user.UserModel.get_user')
222 patcher = patch('rhodecode.model.user.UserModel.get_user')
200 with patcher as get_user:
223 with patcher as get_user:
201 get_user.return_value = None
224 get_user.return_value = None
202 with pytest.raises(JSONRPCError) as excinfo:
225 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
203 utils.get_user_or_error('123')
226 with patcher as get_by_username:
227 get_by_username.return_value = None
204
228
205 expected_message = 'user `123` does not exist'
229 with pytest.raises(JSONRPCError) as excinfo:
206 assert excinfo.value.message == expected_message
230 utils.get_user_or_error(123)
231
232 expected_message = 'user `123` does not exist'
233 assert excinfo.value.message == expected_message
207
234
208 def test_user_not_found_by_name(self):
235 def test_user_not_found_by_name(self):
209 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
236 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
@@ -216,8 +243,7 b' class TestUserByNameOrError(object):'
216 assert excinfo.value.message == expected_message
243 assert excinfo.value.message == expected_message
217
244
218
245
219 class TestGetCommitDict:
246 class TestGetCommitDict(object):
220
221 @pytest.mark.parametrize('filename, expected', [
247 @pytest.mark.parametrize('filename, expected', [
222 (b'sp\xc3\xa4cial', u'sp\xe4cial'),
248 (b'sp\xc3\xa4cial', u'sp\xe4cial'),
223 (b'sp\xa4cial', u'sp\ufffdcial'),
249 (b'sp\xa4cial', u'sp\ufffdcial'),
@@ -213,15 +213,19 b' def get_user_or_error(userid):'
213 :param userid:
213 :param userid:
214 """
214 """
215 from rhodecode.model.user import UserModel
215 from rhodecode.model.user import UserModel
216 user_model = UserModel()
216
217
217 user_model = UserModel()
218 if isinstance(userid, (int, long)):
218 try:
219 try:
219 user = user_model.get_user(int(userid))
220 user = user_model.get_user(userid)
220 except ValueError:
221 except ValueError:
222 user = None
223 else:
221 user = user_model.get_by_username(userid)
224 user = user_model.get_by_username(userid)
222
225
223 if user is None:
226 if user is None:
224 raise JSONRPCError("user `%s` does not exist" % (userid,))
227 raise JSONRPCError(
228 'user `%s` does not exist' % (userid,))
225 return user
229 return user
226
230
227
231
@@ -232,10 +236,19 b' def get_repo_or_error(repoid):'
232 :param repoid:
236 :param repoid:
233 """
237 """
234 from rhodecode.model.repo import RepoModel
238 from rhodecode.model.repo import RepoModel
239 repo_model = RepoModel()
235
240
236 repo = RepoModel().get_repo(repoid)
241 if isinstance(repoid, (int, long)):
242 try:
243 repo = repo_model.get_repo(repoid)
244 except ValueError:
245 repo = None
246 else:
247 repo = repo_model.get_by_repo_name(repoid)
248
237 if repo is None:
249 if repo is None:
238 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
250 raise JSONRPCError(
251 'repository `%s` does not exist' % (repoid,))
239 return repo
252 return repo
240
253
241
254
@@ -246,8 +259,16 b' def get_repo_group_or_error(repogroupid)'
246 :param repogroupid:
259 :param repogroupid:
247 """
260 """
248 from rhodecode.model.repo_group import RepoGroupModel
261 from rhodecode.model.repo_group import RepoGroupModel
262 repo_group_model = RepoGroupModel()
249
263
250 repo_group = RepoGroupModel()._get_repo_group(repogroupid)
264 if isinstance(repogroupid, (int, long)):
265 try:
266 repo_group = repo_group_model._get_repo_group(repogroupid)
267 except ValueError:
268 repo_group = None
269 else:
270 repo_group = repo_group_model.get_by_group_name(repogroupid)
271
251 if repo_group is None:
272 if repo_group is None:
252 raise JSONRPCError(
273 raise JSONRPCError(
253 'repository group `%s` does not exist' % (repogroupid,))
274 'repository group `%s` does not exist' % (repogroupid,))
@@ -261,10 +282,19 b' def get_user_group_or_error(usergroupid)'
261 :param usergroupid:
282 :param usergroupid:
262 """
283 """
263 from rhodecode.model.user_group import UserGroupModel
284 from rhodecode.model.user_group import UserGroupModel
285 user_group_model = UserGroupModel()
264
286
265 user_group = UserGroupModel().get_group(usergroupid)
287 if isinstance(usergroupid, (int, long)):
288 try:
289 user_group = user_group_model.get_group(usergroupid)
290 except ValueError:
291 user_group = None
292 else:
293 user_group = user_group_model.get_by_name(usergroupid)
294
266 if user_group is None:
295 if user_group is None:
267 raise JSONRPCError('user group `%s` does not exist' % (usergroupid,))
296 raise JSONRPCError(
297 'user group `%s` does not exist' % (usergroupid,))
268 return user_group
298 return user_group
269
299
270
300
@@ -244,9 +244,8 b' def merge_pull_request(request, apiuser,'
244
244
245 .. code-block:: bash
245 .. code-block:: bash
246
246
247 "id": <id_given_in_input>,
247 "id": <id_given_in_input>,
248 "result":
248 "result": {
249 {
250 "executed": "<bool>",
249 "executed": "<bool>",
251 "failure_reason": "<int>",
250 "failure_reason": "<int>",
252 "merge_commit_id": "<merge_commit_id>",
251 "merge_commit_id": "<merge_commit_id>",
@@ -257,8 +256,7 b' def merge_pull_request(request, apiuser,'
257 "name": "<name>"
256 "name": "<name>"
258 }
257 }
259 },
258 },
260 "error": null
259 "error": null
261
262 """
260 """
263 repo = get_repo_or_error(repoid)
261 repo = get_repo_or_error(repoid)
264 if not isinstance(userid, Optional):
262 if not isinstance(userid, Optional):
@@ -321,13 +319,12 b' def close_pull_request(request, apiuser,'
321
319
322 .. code-block:: bash
320 .. code-block:: bash
323
321
324 "id": <id_given_in_input>,
322 "id": <id_given_in_input>,
325 "result":
323 "result": {
326 {
327 "pull_request_id": "<int>",
324 "pull_request_id": "<int>",
328 "closed": "<bool>"
325 "closed": "<bool>"
329 },
326 },
330 "error": null
327 "error": null
331
328
332 """
329 """
333 repo = get_repo_or_error(repoid)
330 repo = get_repo_or_error(repoid)
@@ -396,15 +393,14 b' def comment_pull_request('
396
393
397 .. code-block:: bash
394 .. code-block:: bash
398
395
399 id : <id_given_in_input>
396 id : <id_given_in_input>
400 result :
397 result : {
401 {
402 "pull_request_id": "<Integer>",
398 "pull_request_id": "<Integer>",
403 "comment_id": "<Integer>",
399 "comment_id": "<Integer>",
404 "status": {"given": <given_status>,
400 "status": {"given": <given_status>,
405 "was_changed": <bool status_was_actually_changed> },
401 "was_changed": <bool status_was_actually_changed> },
406 }
402 },
407 error : null
403 error : null
408 """
404 """
409 repo = get_repo_or_error(repoid)
405 repo = get_repo_or_error(repoid)
410 if not isinstance(userid, Optional):
406 if not isinstance(userid, Optional):
@@ -535,9 +531,8 b' def create_pull_request('
535 :param reviewers: Set the new pull request reviewers list.
531 :param reviewers: Set the new pull request reviewers list.
536 :type reviewers: Optional(list)
532 :type reviewers: Optional(list)
537 Accepts username strings or objects of the format:
533 Accepts username strings or objects of the format:
538 {
534
539 'username': 'nick', 'reasons': ['original author']
535 {'username': 'nick', 'reasons': ['original author']}
540 }
541 """
536 """
542
537
543 source = get_repo_or_error(source_repo)
538 source = get_repo_or_error(source_repo)
@@ -633,9 +628,8 b' def update_pull_request('
633
628
634 .. code-block:: bash
629 .. code-block:: bash
635
630
636 id : <id_given_in_input>
631 id : <id_given_in_input>
637 result :
632 result : {
638 {
639 "msg": "Updated pull request `63`",
633 "msg": "Updated pull request `63`",
640 "pull_request": <pull_request_object>,
634 "pull_request": <pull_request_object>,
641 "updated_reviewers": {
635 "updated_reviewers": {
@@ -655,7 +649,7 b' def update_pull_request('
655 "removed": []
649 "removed": []
656 }
650 }
657 }
651 }
658 error : null
652 error : null
659 """
653 """
660
654
661 repo = get_repo_or_error(repoid)
655 repo = get_repo_or_error(repoid)
@@ -18,10 +18,12 b''
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21 import inspect
22 import logging
22 import logging
23 import itertools
23
24
24 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCForbidden
25 from rhodecode.api import (
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, find_methods)
25
27
26 from rhodecode.api.utils import (
28 from rhodecode.api.utils import (
27 Optional, OAttr, has_superadmin_permission, get_user_or_error)
29 Optional, OAttr, has_superadmin_permission, get_user_or_error)
@@ -243,3 +245,77 b' def cleanup_sessions(request, apiuser, o'
243 raise JSONRPCError(
245 raise JSONRPCError(
244 'Error occurred during session cleanup'
246 'Error occurred during session cleanup'
245 )
247 )
248
249
250 @jsonrpc_method()
251 def get_method(request, apiuser, pattern=Optional('*')):
252 """
253 Returns list of all available API methods. By default match pattern
254 os "*" but any other pattern can be specified. eg *comment* will return
255 all methods with comment inside them. If just single method is matched
256 returned data will also include method specification
257
258 This command can only be run using an |authtoken| with admin rights to
259 the specified repository.
260
261 This command takes the following options:
262
263 :param apiuser: This is filled automatically from the |authtoken|.
264 :type apiuser: AuthUser
265 :param pattern: pattern to match method names against
266 :type older_then: Optional("*")
267
268 Example output:
269
270 .. code-block:: bash
271
272 id : <id_given_in_input>
273 "result": [
274 "changeset_comment",
275 "comment_pull_request",
276 "comment_commit"
277 ]
278 error : null
279
280 .. code-block:: bash
281
282 id : <id_given_in_input>
283 "result": [
284 "comment_commit",
285 {
286 "apiuser": "<RequiredType>",
287 "comment_type": "<Optional:u'note'>",
288 "commit_id": "<RequiredType>",
289 "message": "<RequiredType>",
290 "repoid": "<RequiredType>",
291 "request": "<RequiredType>",
292 "resolves_comment_id": "<Optional:None>",
293 "status": "<Optional:None>",
294 "userid": "<Optional:<OptionalAttr:apiuser>>"
295 }
296 ]
297 error : null
298 """
299 if not has_superadmin_permission(apiuser):
300 raise JSONRPCForbidden()
301
302 pattern = Optional.extract(pattern)
303
304 matches = find_methods(request.registry.jsonrpc_methods, pattern)
305
306 args_desc = []
307 if len(matches) == 1:
308 func = matches[matches.keys()[0]]
309
310 argspec = inspect.getargspec(func)
311 arglist = argspec[0]
312 defaults = map(repr, argspec[3] or [])
313
314 default_empty = '<RequiredType>'
315
316 # kw arguments required by this method
317 func_kwargs = dict(itertools.izip_longest(
318 reversed(arglist), reversed(defaults), fillvalue=default_empty))
319 args_desc.append(func_kwargs)
320
321 return matches.keys() + args_desc
@@ -29,7 +29,6 b' from rhodecode.lib.utils2 import safe_in'
29 from rhodecode.model.db import Session, User, Repository
29 from rhodecode.model.db import Session, User, Repository
30 from rhodecode.model.user import UserModel
30 from rhodecode.model.user import UserModel
31
31
32
33 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
34
33
35
34
@@ -62,8 +61,8 b' def get_user(request, apiuser, userid=Op'
62 "result": {
61 "result": {
63 "active": true,
62 "active": true,
64 "admin": false,
63 "admin": false,
65 "api_key": "api-key",
66 "api_keys": [ list of keys ],
64 "api_keys": [ list of keys ],
65 "auth_tokens": [ list of tokens with details ],
67 "email": "user@example.com",
66 "email": "user@example.com",
68 "emails": [
67 "emails": [
69 "user@example.com"
68 "user@example.com"
@@ -74,6 +73,7 b' def get_user(request, apiuser, userid=Op'
74 "ip_addresses": [],
73 "ip_addresses": [],
75 "language": null,
74 "language": null,
76 "last_login": "Timestamp",
75 "last_login": "Timestamp",
76 "last_activity": "Timestamp",
77 "lastname": "surnae",
77 "lastname": "surnae",
78 "permissions": {
78 "permissions": {
79 "global": [
79 "global": [
@@ -133,7 +133,7 b' def get_users(request, apiuser):'
133 .. code-block:: bash
133 .. code-block:: bash
134
134
135 id : <id_given_in_input>
135 id : <id_given_in_input>
136 result: [<user_object>, ...]
136 result: [<user_object>, ...]
137 error: null
137 error: null
138 """
138 """
139
139
@@ -191,15 +191,16 b' def create_user(request, apiuser, userna'
191 :type force_password_change: Optional(``True`` | ``False``)
191 :type force_password_change: Optional(``True`` | ``False``)
192 :param create_personal_repo_group: Create personal repo group for this user
192 :param create_personal_repo_group: Create personal repo group for this user
193 :type create_personal_repo_group: Optional(``True`` | ``False``)
193 :type create_personal_repo_group: Optional(``True`` | ``False``)
194
194 Example output:
195 Example output:
195
196
196 .. code-block:: bash
197 .. code-block:: bash
197
198
198 id : <id_given_in_input>
199 id : <id_given_in_input>
199 result: {
200 result: {
200 "msg" : "created new user `<username>`",
201 "msg" : "created new user `<username>`",
201 "user": <user_obj>
202 "user": <user_obj>
202 }
203 }
203 error: null
204 error: null
204
205
205 Example error output:
206 Example error output:
@@ -305,9 +306,9 b' def update_user(request, apiuser, userid'
305
306
306 id : <id_given_in_input>
307 id : <id_given_in_input>
307 result: {
308 result: {
308 "msg" : "updated user ID:<userid> <username>",
309 "msg" : "updated user ID:<userid> <username>",
309 "user": <user_object>,
310 "user": <user_object>,
310 }
311 }
311 error: null
312 error: null
312
313
313 Example error output:
314 Example error output:
@@ -384,9 +385,9 b' def delete_user(request, apiuser, userid'
384
385
385 id : <id_given_in_input>
386 id : <id_given_in_input>
386 result: {
387 result: {
387 "msg" : "deleted user ID:<userid> <username>",
388 "msg" : "deleted user ID:<userid> <username>",
388 "user": null
389 "user": null
389 }
390 }
390 error: null
391 error: null
391
392
392 Example error output:
393 Example error output:
@@ -470,3 +471,45 b' def get_user_locks(request, apiuser, use'
470 ret.append(_api_data)
471 ret.append(_api_data)
471
472
472 return ret
473 return ret
474
475
476 @jsonrpc_method()
477 def get_user_audit_logs(request, apiuser, userid=Optional(OAttr('apiuser'))):
478 """
479 Fetches all action logs made by the specified user.
480
481 This command takes the following options:
482
483 :param apiuser: This is filled automatically from the |authtoken|.
484 :type apiuser: AuthUser
485 :param userid: Sets the userid whose list of locked |repos| will be
486 displayed.
487 :type userid: Optional(str or int)
488
489 Example output:
490
491 .. code-block:: bash
492
493 id : <id_given_in_input>
494 result : {
495 [action, action,...]
496 }
497 error : null
498 """
499
500 if not has_superadmin_permission(apiuser):
501 # make sure normal user does not pass someone else userid,
502 # he is not allowed to do that
503 if not isinstance(userid, Optional) and userid != apiuser.user_id:
504 raise JSONRPCError('userid is not the same as your user')
505
506 userid = Optional.extract(userid, evaluate_locals=locals())
507 userid = getattr(userid, 'user_id', userid)
508 user = get_user_or_error(userid)
509
510 ret = []
511
512 # show all user actions
513 for entry in UserModel().get_user_log(user, filter_term=None):
514 ret.append(entry)
515 return ret
@@ -19,11 +19,72 b''
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 from rhodecode.admin.navigation import NavigationRegistry
22 from rhodecode.apps.admin.navigation import NavigationRegistry
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25
25
26
26
27 def admin_routes(config):
28 """
29 Admin prefixed routes
30 """
31
32 config.add_route(
33 name='admin_settings_open_source',
34 pattern='/settings/open_source')
35 config.add_route(
36 name='admin_settings_vcs_svn_generate_cfg',
37 pattern='/settings/vcs/svn_generate_cfg')
38
39 config.add_route(
40 name='admin_settings_system',
41 pattern='/settings/system')
42 config.add_route(
43 name='admin_settings_system_update',
44 pattern='/settings/system/updates')
45
46 config.add_route(
47 name='admin_settings_sessions',
48 pattern='/settings/sessions')
49 config.add_route(
50 name='admin_settings_sessions_cleanup',
51 pattern='/settings/sessions/cleanup')
52
53 # users admin
54 config.add_route(
55 name='users',
56 pattern='/users')
57
58 config.add_route(
59 name='users_data',
60 pattern='/users_data')
61
62 # user auth tokens
63 config.add_route(
64 name='edit_user_auth_tokens',
65 pattern='/users/{user_id:\d+}/edit/auth_tokens')
66 config.add_route(
67 name='edit_user_auth_tokens_add',
68 pattern='/users/{user_id:\d+}/edit/auth_tokens/new')
69 config.add_route(
70 name='edit_user_auth_tokens_delete',
71 pattern='/users/{user_id:\d+}/edit/auth_tokens/delete')
72
73 # user groups management
74 config.add_route(
75 name='edit_user_groups_management',
76 pattern='/users/{user_id:\d+}/edit/groups_management')
77
78 config.add_route(
79 name='edit_user_groups_management_updates',
80 pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates')
81
82 # user audit logs
83 config.add_route(
84 name='edit_user_audit_logs',
85 pattern='/users/{user_id:\d+}/edit/audit')
86
87
27 def includeme(config):
88 def includeme(config):
28 settings = config.get_settings()
89 settings = config.get_settings()
29
90
@@ -32,26 +93,7 b' def includeme(config):'
32 navigation_registry = NavigationRegistry(labs_active=labs_active)
93 navigation_registry = NavigationRegistry(labs_active=labs_active)
33 config.registry.registerUtility(navigation_registry)
94 config.registry.registerUtility(navigation_registry)
34
95
35 config.add_route(
96 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
36 name='admin_settings_open_source',
37 pattern=ADMIN_PREFIX + '/settings/open_source')
38 config.add_route(
39 name='admin_settings_vcs_svn_generate_cfg',
40 pattern=ADMIN_PREFIX + '/settings/vcs/svn_generate_cfg')
41
42 config.add_route(
43 name='admin_settings_system',
44 pattern=ADMIN_PREFIX + '/settings/system')
45 config.add_route(
46 name='admin_settings_system_update',
47 pattern=ADMIN_PREFIX + '/settings/system/updates')
48
49 config.add_route(
50 name='admin_settings_sessions',
51 pattern=ADMIN_PREFIX + '/settings/sessions')
52 config.add_route(
53 name='admin_settings_sessions_cleanup',
54 pattern=ADMIN_PREFIX + '/settings/sessions/cleanup')
55
97
56 # Scan module for configuration decorators.
98 # Scan module for configuration decorators.
57 config.scan()
99 config.scan()
1 NO CONTENT: file renamed from rhodecode/admin/interfaces.py to rhodecode/apps/admin/interfaces.py
NO CONTENT: file renamed from rhodecode/admin/interfaces.py to rhodecode/apps/admin/interfaces.py
@@ -25,7 +25,7 b' import collections'
25 from pylons import url
25 from pylons import url
26 from zope.interface import implementer
26 from zope.interface import implementer
27
27
28 from rhodecode.admin.interfaces import IAdminNavigationRegistry
28 from rhodecode.apps.admin.interfaces import IAdminNavigationRegistry
29 from rhodecode.lib.utils import get_registry
29 from rhodecode.lib.utils import get_registry
30 from rhodecode.translation import _
30 from rhodecode.translation import _
31
31
1 NO CONTENT: file renamed from rhodecode/admin/views/__init__.py to rhodecode/apps/admin/views/__init__.py
NO CONTENT: file renamed from rhodecode/admin/views/__init__.py to rhodecode/apps/admin/views/__init__.py
@@ -24,15 +24,15 b' import logging'
24 from pylons import tmpl_context as c
24 from pylons import tmpl_context as c
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.admin.views.base import AdminSettingsView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.admin.navigation import navigation_list
28 from rhodecode.apps.admin.navigation import navigation_list
29 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
29 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
30 from rhodecode.lib.utils import read_opensource_licenses
30 from rhodecode.lib.utils import read_opensource_licenses
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 class OpenSourceLicensesAdminSettingsView(AdminSettingsView):
35 class OpenSourceLicensesAdminSettingsView(BaseAppView):
36
36
37 @LoginRequired()
37 @LoginRequired()
38 @HasPermissionAllDecorator('hg.admin')
38 @HasPermissionAllDecorator('hg.admin')
@@ -24,9 +24,8 b' from pylons import tmpl_context as c'
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26
26
27 from rhodecode.translation import _
27 from rhodecode.apps._base import BaseAppView
28
28 from rhodecode.apps.admin.navigation import navigation_list
29 from rhodecode.admin.views.base import AdminSettingsView
30 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
31 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
30 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
32 from rhodecode.lib.utils2 import safe_int
31 from rhodecode.lib.utils2 import safe_int
@@ -34,13 +33,10 b' from rhodecode.lib import system_info'
34 from rhodecode.lib import user_sessions
33 from rhodecode.lib import user_sessions
35
34
36
35
37 from rhodecode.admin.navigation import navigation_list
38
39
40 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
41
37
42
38
43 class AdminSessionSettingsView(AdminSettingsView):
39 class AdminSessionSettingsView(BaseAppView):
44
40
45 @LoginRequired()
41 @LoginRequired()
46 @HasPermissionAllDecorator('hg.admin')
42 @HasPermissionAllDecorator('hg.admin')
@@ -22,16 +22,15 b' import logging'
22
22
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24
24
25 from rhodecode.svn_support.utils import generate_mod_dav_svn_config
25 from rhodecode.apps._base import BaseAppView
26
26 from rhodecode.apps.svn_support.utils import generate_mod_dav_svn_config
27 from rhodecode.admin.views.base import AdminSettingsView
28 from rhodecode.lib.auth import (
27 from rhodecode.lib.auth import (
29 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
28 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
30
29
31 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
32
31
33
32
34 class SvnConfigAdminSettingsView(AdminSettingsView):
33 class SvnConfigAdminSettingsView(BaseAppView):
35
34
36 @LoginRequired()
35 @LoginRequired()
37 @CSRFRequired()
36 @CSRFRequired()
@@ -26,20 +26,19 b' from pylons import tmpl_context as c'
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27
27
28 import rhodecode
28 import rhodecode
29 from rhodecode.apps._base import BaseAppView
30 from rhodecode.apps.admin.navigation import navigation_list
29 from rhodecode.lib import helpers as h
31 from rhodecode.lib import helpers as h
30 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
32 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
31 from rhodecode.lib.utils2 import str2bool
33 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.lib import system_info
34 from rhodecode.lib import system_info
33 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
34
35 from rhodecode.admin.views.base import AdminSettingsView
36 from rhodecode.admin.navigation import navigation_list
37 from rhodecode.model.settings import SettingsModel
36 from rhodecode.model.settings import SettingsModel
38
37
39 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
40
39
41
40
42 class AdminSystemInfoSettingsView(AdminSettingsView):
41 class AdminSystemInfoSettingsView(BaseAppView):
43
42
44 @staticmethod
43 @staticmethod
45 def get_update_data(update_url):
44 def get_update_data(update_url):
@@ -107,6 +106,8 b' class AdminSystemInfoSettingsView(AdminS'
107 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
106 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
108 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
107 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
109 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
108 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
109 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
110 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
110 ('', '', ''), # spacer
111 ('', '', ''), # spacer
111
112
112 # Database
113 # Database
@@ -122,7 +123,7 b' class AdminSystemInfoSettingsView(AdminS'
122 ('', '', ''), # spacer
123 ('', '', ''), # spacer
123
124
124 # Systems stats
125 # Systems stats
125 (_('CPU'), val('cpu'), state('cpu')),
126 (_('CPU'), val('cpu')['text'], state('cpu')),
126 (_('Load'), val('load')['text'], state('load')),
127 (_('Load'), val('load')['text'], state('load')),
127 (_('Memory'), val('memory')['text'], state('memory')),
128 (_('Memory'), val('memory')['text'], state('memory')),
128 (_('Uptime'), val('uptime')['text'], state('uptime')),
129 (_('Uptime'), val('uptime')['text'], state('uptime')),
@@ -85,4 +85,6 b' def includeme(config):'
85 config.add_route(
85 config.add_route(
86 name='channelstream_proxy',
86 name='channelstream_proxy',
87 pattern=settings.get('channelstream.proxy_path') or '/_channelstream')
87 pattern=settings.get('channelstream.proxy_path') or '/_channelstream')
88 config.scan('rhodecode.channelstream')
88
89 # Scan module for configuration decorators.
90 config.scan()
1 NO CONTENT: file renamed from rhodecode/channelstream/views.py to rhodecode/apps/channelstream/views.py
NO CONTENT: file renamed from rhodecode/channelstream/views.py to rhodecode/apps/channelstream/views.py
1 NO CONTENT: file renamed from rhodecode/login/__init__.py to rhodecode/apps/login/__init__.py
NO CONTENT: file renamed from rhodecode/login/__init__.py to rhodecode/apps/login/__init__.py
1 NO CONTENT: file renamed from rhodecode/login/tests/__init__.py to rhodecode/apps/login/tests/__init__.py
NO CONTENT: file renamed from rhodecode/login/tests/__init__.py to rhodecode/apps/login/tests/__init__.py
@@ -22,8 +22,9 b''
22 import mock
22 import mock
23 import pytest
23 import pytest
24
24
25 from rhodecode.apps.login.views import LoginView, CaptchaData
25 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.login.views import LoginView, CaptchaData
27 from rhodecode.lib.utils2 import AttributeDict
27 from rhodecode.model.settings import SettingsModel
28 from rhodecode.model.settings import SettingsModel
28 from rhodecode.tests.utils import AssertResponse
29 from rhodecode.tests.utils import AssertResponse
29
30
@@ -40,7 +41,7 b' class RhodeCodeSetting(object):'
40 model.create_or_update_setting(name=self.name, val=self.value)
41 model.create_or_update_setting(name=self.name, val=self.value)
41 return self
42 return self
42
43
43 def __exit__(self, type, value, traceback):
44 def __exit__(self, exc_type, exc_val, exc_tb):
44 model = SettingsModel()
45 model = SettingsModel()
45 if self.old_setting:
46 if self.old_setting:
46 model.create_or_update_setting(
47 model.create_or_update_setting(
@@ -57,8 +58,12 b' class TestRegisterCaptcha(object):'
57 ('privkey', '', CaptchaData(True, 'privkey', '')),
58 ('privkey', '', CaptchaData(True, 'privkey', '')),
58 ('privkey', 'pubkey', CaptchaData(True, 'privkey', 'pubkey')),
59 ('privkey', 'pubkey', CaptchaData(True, 'privkey', 'pubkey')),
59 ])
60 ])
60 def test_get_captcha_data(self, private_key, public_key, expected, db):
61 def test_get_captcha_data(self, private_key, public_key, expected, db,
61 login_view = LoginView(mock.Mock(), mock.Mock())
62 request_stub, user_util):
63 request_stub.user = user_util.create_user().AuthUser
64 request_stub.matched_route = AttributeDict({'name': 'login'})
65 login_view = LoginView(mock.Mock(), request_stub)
66
62 with RhodeCodeSetting('captcha_private_key', private_key):
67 with RhodeCodeSetting('captcha_private_key', private_key):
63 with RhodeCodeSetting('captcha_public_key', public_key):
68 with RhodeCodeSetting('captcha_public_key', public_key):
64 captcha = login_view._get_captcha_data()
69 captcha = login_view._get_captcha_data()
@@ -92,7 +97,7 b' class TestRegisterCaptcha(object):'
92 assertr.no_element_exists('#recaptcha_field')
97 assertr.no_element_exists('#recaptcha_field')
93
98
94 @pytest.mark.parametrize('valid', [False, True])
99 @pytest.mark.parametrize('valid', [False, True])
95 @mock.patch('rhodecode.login.views.submit')
100 @mock.patch('rhodecode.apps.login.views.submit')
96 @mock.patch.object(LoginView, '_get_captcha_data')
101 @mock.patch.object(LoginView, '_get_captcha_data')
97 def test_register_with_active_captcha(
102 def test_register_with_active_captcha(
98 self, m_get_captcha_data, m_submit, valid, app, csrf_token):
103 self, m_get_captcha_data, m_submit, valid, app, csrf_token):
@@ -18,6 +18,7 b''
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import collections
22 import collections
22 import datetime
23 import datetime
23 import formencode
24 import formencode
@@ -29,6 +30,7 b' from pyramid.httpexceptions import HTTPF'
29 from pyramid.view import view_config
30 from pyramid.view import view_config
30 from recaptcha.client.captcha import submit
31 from recaptcha.client.captcha import submit
31
32
33 from rhodecode.apps._base import BaseAppView
32 from rhodecode.authentication.base import authenticate, HTTP_TYPE
34 from rhodecode.authentication.base import authenticate, HTTP_TYPE
33 from rhodecode.events import UserRegistered
35 from rhodecode.events import UserRegistered
34 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
@@ -37,9 +39,10 b' from rhodecode.lib.auth import ('
37 from rhodecode.lib.base import get_ip_addr
39 from rhodecode.lib.base import get_ip_addr
38 from rhodecode.lib.exceptions import UserCreationError
40 from rhodecode.lib.exceptions import UserCreationError
39 from rhodecode.lib.utils2 import safe_str
41 from rhodecode.lib.utils2 import safe_str
40 from rhodecode.model.db import User
42 from rhodecode.model.db import User, UserApiKeys
41 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
43 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
42 from rhodecode.model.meta import Session
44 from rhodecode.model.meta import Session
45 from rhodecode.model.auth_token import AuthTokenModel
43 from rhodecode.model.settings import SettingsModel
46 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.user import UserModel
47 from rhodecode.model.user import UserModel
45 from rhodecode.translation import _
48 from rhodecode.translation import _
@@ -103,20 +106,13 b' def get_came_from(request):'
103 return came_from or url('home')
106 return came_from or url('home')
104
107
105
108
106 class LoginView(object):
109 class LoginView(BaseAppView):
107
110
108 def __init__(self, context, request):
111 def load_default_context(self):
109 self.request = request
112 c = self._get_local_tmpl_context()
110 self.context = context
113 c.came_from = get_came_from(self.request)
111 self.session = request.session
114 self._register_global_c(c)
112 self._rhodecode_user = request.user
115 return c
113
114 def _get_template_context(self):
115 return {
116 'came_from': get_came_from(self.request),
117 'defaults': {},
118 'errors': {},
119 }
120
116
121 def _get_captcha_data(self):
117 def _get_captcha_data(self):
122 settings = SettingsModel().get_all_settings()
118 settings = SettingsModel().get_all_settings()
@@ -130,12 +126,13 b' class LoginView(object):'
130 route_name='login', request_method='GET',
126 route_name='login', request_method='GET',
131 renderer='rhodecode:templates/login.mako')
127 renderer='rhodecode:templates/login.mako')
132 def login(self):
128 def login(self):
133 came_from = get_came_from(self.request)
129 c = self.load_default_context()
134 user = self.request.user
130 auth_user = self._rhodecode_user
135
131
136 # redirect if already logged in
132 # redirect if already logged in
137 if user.is_authenticated and not user.is_default and user.ip_allowed:
133 if (auth_user.is_authenticated and
138 raise HTTPFound(came_from)
134 not auth_user.is_default and auth_user.ip_allowed):
135 raise HTTPFound(c.came_from)
139
136
140 # check if we use headers plugin, and try to login using it.
137 # check if we use headers plugin, and try to login using it.
141 try:
138 try:
@@ -145,18 +142,18 b' class LoginView(object):'
145 if auth_info:
142 if auth_info:
146 headers = _store_user_in_session(
143 headers = _store_user_in_session(
147 self.session, auth_info.get('username'))
144 self.session, auth_info.get('username'))
148 raise HTTPFound(came_from, headers=headers)
145 raise HTTPFound(c.came_from, headers=headers)
149 except UserCreationError as e:
146 except UserCreationError as e:
150 log.error(e)
147 log.error(e)
151 self.session.flash(e, queue='error')
148 self.session.flash(e, queue='error')
152
149
153 return self._get_template_context()
150 return self._get_template_context(c)
154
151
155 @view_config(
152 @view_config(
156 route_name='login', request_method='POST',
153 route_name='login', request_method='POST',
157 renderer='rhodecode:templates/login.mako')
154 renderer='rhodecode:templates/login.mako')
158 def login_post(self):
155 def login_post(self):
159 came_from = get_came_from(self.request)
156 c = self.load_default_context()
160
157
161 login_form = LoginForm()()
158 login_form = LoginForm()()
162
159
@@ -168,13 +165,13 b' class LoginView(object):'
168 self.session,
165 self.session,
169 username=form_result['username'],
166 username=form_result['username'],
170 remember=form_result['remember'])
167 remember=form_result['remember'])
171 log.debug('Redirecting to "%s" after login.', came_from)
168 log.debug('Redirecting to "%s" after login.', c.came_from)
172 raise HTTPFound(came_from, headers=headers)
169 raise HTTPFound(c.came_from, headers=headers)
173 except formencode.Invalid as errors:
170 except formencode.Invalid as errors:
174 defaults = errors.value
171 defaults = errors.value
175 # remove password from filling in form again
172 # remove password from filling in form again
176 defaults.pop('password', None)
173 defaults.pop('password', None)
177 render_ctx = self._get_template_context()
174 render_ctx = self._get_template_context(c)
178 render_ctx.update({
175 render_ctx.update({
179 'errors': errors.error_dict,
176 'errors': errors.error_dict,
180 'defaults': defaults,
177 'defaults': defaults,
@@ -187,13 +184,13 b' class LoginView(object):'
187 # with user creation, explanation should be provided in
184 # with user creation, explanation should be provided in
188 # Exception itself
185 # Exception itself
189 self.session.flash(e, queue='error')
186 self.session.flash(e, queue='error')
190 return self._get_template_context()
187 return self._get_template_context(c)
191
188
192 @CSRFRequired()
189 @CSRFRequired()
193 @view_config(route_name='logout', request_method='POST')
190 @view_config(route_name='logout', request_method='POST')
194 def logout(self):
191 def logout(self):
195 user = self.request.user
192 auth_user = self._rhodecode_user
196 log.info('Deleting session for user: `%s`', user)
193 log.info('Deleting session for user: `%s`', auth_user)
197 self.session.delete()
194 self.session.delete()
198 return HTTPFound(url('home'))
195 return HTTPFound(url('home'))
199
196
@@ -203,6 +200,7 b' class LoginView(object):'
203 route_name='register', request_method='GET',
200 route_name='register', request_method='GET',
204 renderer='rhodecode:templates/register.mako',)
201 renderer='rhodecode:templates/register.mako',)
205 def register(self, defaults=None, errors=None):
202 def register(self, defaults=None, errors=None):
203 c = self.load_default_context()
206 defaults = defaults or {}
204 defaults = defaults or {}
207 errors = errors or {}
205 errors = errors or {}
208
206
@@ -212,7 +210,7 b' class LoginView(object):'
212 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
210 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
213 .AuthUser.permissions['global']
211 .AuthUser.permissions['global']
214
212
215 render_ctx = self._get_template_context()
213 render_ctx = self._get_template_context(c)
216 render_ctx.update({
214 render_ctx.update({
217 'defaults': defaults,
215 'defaults': defaults,
218 'errors': errors,
216 'errors': errors,
@@ -289,17 +287,24 b' class LoginView(object):'
289 'errors': {},
287 'errors': {},
290 }
288 }
291
289
290 # always send implicit message to prevent from discovery of
291 # matching emails
292 msg = _('If such email exists, a password reset link was sent to it.')
293
292 if self.request.POST:
294 if self.request.POST:
295 if h.HasPermissionAny('hg.password_reset.disabled')():
296 _email = self.request.POST.get('email', '')
297 log.error('Failed attempt to reset password for `%s`.', _email)
298 self.session.flash(_('Password reset has been disabled.'),
299 queue='error')
300 return HTTPFound(self.request.route_path('reset_password'))
301
293 password_reset_form = PasswordResetForm()()
302 password_reset_form = PasswordResetForm()()
294 try:
303 try:
295 form_result = password_reset_form.to_python(
304 form_result = password_reset_form.to_python(
296 self.request.params)
305 self.request.params)
297 if h.HasPermissionAny('hg.password_reset.disabled')():
306 user_email = form_result['email']
298 log.error('Failed attempt to reset password for %s.', form_result['email'] )
307
299 self.session.flash(
300 _('Password reset has been disabled.'),
301 queue='error')
302 return HTTPFound(self.request.route_path('reset_password'))
303 if captcha.active:
308 if captcha.active:
304 response = submit(
309 response = submit(
305 self.request.params.get('recaptcha_challenge_field'),
310 self.request.params.get('recaptcha_challenge_field'),
@@ -310,43 +315,76 b' class LoginView(object):'
310 _value = form_result
315 _value = form_result
311 _msg = _('Bad captcha')
316 _msg = _('Bad captcha')
312 error_dict = {'recaptcha_field': _msg}
317 error_dict = {'recaptcha_field': _msg}
313 raise formencode.Invalid(_msg, _value, None,
318 raise formencode.Invalid(
314 error_dict=error_dict)
319 _msg, _value, None, error_dict=error_dict)
315
320
316 # Generate reset URL and send mail.
321 # Generate reset URL and send mail.
317 user_email = form_result['email']
318 user = User.get_by_email(user_email)
322 user = User.get_by_email(user_email)
323
324 # generate password reset token that expires in 10minutes
325 desc = 'Generated token for password reset from {}'.format(
326 datetime.datetime.now().isoformat())
327 reset_token = AuthTokenModel().create(
328 user, lifetime=10,
329 description=desc,
330 role=UserApiKeys.ROLE_PASSWORD_RESET)
331 Session().commit()
332
333 log.debug('Successfully created password recovery token')
319 password_reset_url = self.request.route_url(
334 password_reset_url = self.request.route_url(
320 'reset_password_confirmation',
335 'reset_password_confirmation',
321 _query={'key': user.api_key})
336 _query={'key': reset_token.api_key})
322 UserModel().reset_password_link(
337 UserModel().reset_password_link(
323 form_result, password_reset_url)
338 form_result, password_reset_url)
324
325 # Display success message and redirect.
339 # Display success message and redirect.
326 self.session.flash(
340 self.session.flash(msg, queue='success')
327 _('Your password reset link was sent'),
341 return HTTPFound(self.request.route_path('reset_password'))
328 queue='success')
329 return HTTPFound(self.request.route_path('login'))
330
342
331 except formencode.Invalid as errors:
343 except formencode.Invalid as errors:
332 render_ctx.update({
344 render_ctx.update({
333 'defaults': errors.value,
345 'defaults': errors.value,
334 'errors': errors.error_dict,
346 'errors': errors.error_dict,
335 })
347 })
348 if not self.request.params.get('email'):
349 # case of empty email, we want to report that
350 return render_ctx
351
352 if 'recaptcha_field' in errors.error_dict:
353 # case of failed captcha
354 return render_ctx
355
356 log.debug('faking response on invalid password reset')
357 # make this take 2s, to prevent brute forcing.
358 time.sleep(2)
359 self.session.flash(msg, queue='success')
360 return HTTPFound(self.request.route_path('reset_password'))
336
361
337 return render_ctx
362 return render_ctx
338
363
339 @view_config(route_name='reset_password_confirmation',
364 @view_config(route_name='reset_password_confirmation',
340 request_method='GET')
365 request_method='GET')
341 def password_reset_confirmation(self):
366 def password_reset_confirmation(self):
367
342 if self.request.GET and self.request.GET.get('key'):
368 if self.request.GET and self.request.GET.get('key'):
369 # make this take 2s, to prevent brute forcing.
370 time.sleep(2)
371
372 token = AuthTokenModel().get_auth_token(
373 self.request.GET.get('key'))
374
375 # verify token is the correct role
376 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
377 log.debug('Got token with role:%s expected is %s',
378 getattr(token, 'role', 'EMPTY_TOKEN'),
379 UserApiKeys.ROLE_PASSWORD_RESET)
380 self.session.flash(
381 _('Given reset token is invalid'), queue='error')
382 return HTTPFound(self.request.route_path('reset_password'))
383
343 try:
384 try:
344 user = User.get_by_auth_token(self.request.GET.get('key'))
385 owner = token.user
345 password_reset_url = self.request.route_url(
386 data = {'email': owner.email, 'token': token.api_key}
346 'reset_password_confirmation',
387 UserModel().reset_password(data)
347 _query={'key': user.api_key})
348 data = {'email': user.email}
349 UserModel().reset_password(data, password_reset_url)
350 self.session.flash(
388 self.session.flash(
351 _('Your password reset was successful, '
389 _('Your password reset was successful, '
352 'a new password has been sent to your email'),
390 'a new password has been sent to your email'),
1 NO CONTENT: file renamed from rhodecode/svn_support/__init__.py to rhodecode/apps/svn_support/__init__.py
NO CONTENT: file renamed from rhodecode/svn_support/__init__.py to rhodecode/apps/svn_support/__init__.py
1 NO CONTENT: file renamed from rhodecode/svn_support/config_keys.py to rhodecode/apps/svn_support/config_keys.py
NO CONTENT: file renamed from rhodecode/svn_support/config_keys.py to rhodecode/apps/svn_support/config_keys.py
1 NO CONTENT: file renamed from rhodecode/svn_support/events.py to rhodecode/apps/svn_support/events.py
NO CONTENT: file renamed from rhodecode/svn_support/events.py to rhodecode/apps/svn_support/events.py
1 NO CONTENT: file renamed from rhodecode/svn_support/subscribers.py to rhodecode/apps/svn_support/subscribers.py
NO CONTENT: file renamed from rhodecode/svn_support/subscribers.py to rhodecode/apps/svn_support/subscribers.py
1 NO CONTENT: file renamed from rhodecode/svn_support/templates/mod-dav-svn.conf.mako to rhodecode/apps/svn_support/templates/mod-dav-svn.conf.mako
NO CONTENT: file renamed from rhodecode/svn_support/templates/mod-dav-svn.conf.mako to rhodecode/apps/svn_support/templates/mod-dav-svn.conf.mako
@@ -25,7 +25,7 b' import re'
25
25
26 from pyramid import testing
26 from pyramid import testing
27
27
28 from rhodecode.svn_support import utils
28 from rhodecode.apps.svn_support import utils
29
29
30
30
31 class TestModDavSvnConfig(object):
31 class TestModDavSvnConfig(object):
@@ -81,7 +81,7 b' def _render_mod_dav_svn_config('
81 }
81 }
82
82
83 # Render the configuration template to string.
83 # Render the configuration template to string.
84 template = 'rhodecode:svn_support/templates/mod-dav-svn.conf.mako'
84 template = 'rhodecode:apps/svn_support/templates/mod-dav-svn.conf.mako'
85 return render(template, context)
85 return render(template, context)
86
86
87
87
@@ -91,6 +91,11 b' class RhodeCodeAuthPluginBase(object):'
91 # set on authenticate() method and via set_auth_type func.
91 # set on authenticate() method and via set_auth_type func.
92 auth_type = None
92 auth_type = None
93
93
94 # set on authenticate() method and via set_calling_scope_repo, this is a
95 # calling scope repository when doing authentication most likely on VCS
96 # operations
97 acl_repo_name = None
98
94 # List of setting names to store encrypted. Plugins may override this list
99 # List of setting names to store encrypted. Plugins may override this list
95 # to store settings encrypted.
100 # to store settings encrypted.
96 _settings_encrypted = []
101 _settings_encrypted = []
@@ -268,6 +273,9 b' class RhodeCodeAuthPluginBase(object):'
268 def set_auth_type(self, auth_type):
273 def set_auth_type(self, auth_type):
269 self.auth_type = auth_type
274 self.auth_type = auth_type
270
275
276 def set_calling_scope_repo(self, acl_repo_name):
277 self.acl_repo_name = acl_repo_name
278
271 def allows_authentication_from(
279 def allows_authentication_from(
272 self, user, allows_non_existing_user=True,
280 self, user, allows_non_existing_user=True,
273 allowed_auth_plugins=None, allowed_auth_sources=None):
281 allowed_auth_plugins=None, allowed_auth_sources=None):
@@ -332,6 +340,8 b' class RhodeCodeAuthPluginBase(object):'
332 log.debug('provided username:`%s` is empty skipping...', username)
340 log.debug('provided username:`%s` is empty skipping...', username)
333 if not user:
341 if not user:
334 log.debug('User `%s` not found in database', username)
342 log.debug('User `%s` not found in database', username)
343 else:
344 log.debug('Got DB user:%s', user)
335 return user
345 return user
336
346
337 def user_activation_state(self):
347 def user_activation_state(self):
@@ -518,7 +528,7 b' def get_auth_cache_manager(custom_ttl=No'
518
528
519
529
520 def authenticate(username, password, environ=None, auth_type=None,
530 def authenticate(username, password, environ=None, auth_type=None,
521 skip_missing=False, registry=None):
531 skip_missing=False, registry=None, acl_repo_name=None):
522 """
532 """
523 Authentication function used for access control,
533 Authentication function used for access control,
524 It tries to authenticate based on enabled authentication modules.
534 It tries to authenticate based on enabled authentication modules.
@@ -538,6 +548,7 b' def authenticate(username, password, env'
538 authn_registry = get_authn_registry(registry)
548 authn_registry = get_authn_registry(registry)
539 for plugin in authn_registry.get_plugins_for_authentication():
549 for plugin in authn_registry.get_plugins_for_authentication():
540 plugin.set_auth_type(auth_type)
550 plugin.set_auth_type(auth_type)
551 plugin.set_calling_scope_repo(acl_repo_name)
541 user = plugin.get_user(username)
552 user = plugin.get_user(username)
542 display_user = user.username if user else username
553 display_user = user.username if user else username
543
554
@@ -627,3 +638,21 b' def authenticate(username, password, env'
627 log.debug("User `%s` failed to authenticate against %s",
638 log.debug("User `%s` failed to authenticate against %s",
628 display_user, plugin.get_id())
639 display_user, plugin.get_id())
629 return None
640 return None
641
642
643 def chop_at(s, sub, inclusive=False):
644 """Truncate string ``s`` at the first occurrence of ``sub``.
645
646 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
647
648 >>> chop_at("plutocratic brats", "rat")
649 'plutoc'
650 >>> chop_at("plutocratic brats", "rat", True)
651 'plutocrat'
652 """
653 pos = s.find(sub)
654 if pos == -1:
655 return s
656 if inclusive:
657 return s[:pos+len(sub)]
658 return s[:pos]
@@ -28,10 +28,9 b' import base64'
28 import logging
28 import logging
29 import urllib2
29 import urllib2
30
30
31 from pylons.i18n.translation import lazy_ugettext as _
31 from rhodecode.translation import _
32 from sqlalchemy.ext.hybrid import hybrid_property
32 from rhodecode.authentication.base import (
33
33 RhodeCodeExternalAuthPlugin, hybrid_property)
34 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
35 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
34 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
36 from rhodecode.authentication.routes import AuthnPluginResourceBase
35 from rhodecode.authentication.routes import AuthnPluginResourceBase
37 from rhodecode.lib.colander_utils import strip_whitespace
36 from rhodecode.lib.colander_utils import strip_whitespace
@@ -21,15 +21,14 b''
21 import colander
21 import colander
22 import logging
22 import logging
23
23
24 from sqlalchemy.ext.hybrid import hybrid_property
24 from rhodecode.translation import _
25
25 from rhodecode.authentication.base import (
26 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
26 RhodeCodeExternalAuthPlugin, hybrid_property)
27 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
27 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
28 from rhodecode.authentication.routes import AuthnPluginResourceBase
28 from rhodecode.authentication.routes import AuthnPluginResourceBase
29 from rhodecode.lib.colander_utils import strip_whitespace
29 from rhodecode.lib.colander_utils import strip_whitespace
30 from rhodecode.lib.utils2 import str2bool, safe_unicode
30 from rhodecode.lib.utils2 import str2bool, safe_unicode
31 from rhodecode.model.db import User
31 from rhodecode.model.db import User
32 from rhodecode.translation import _
33
32
34
33
35 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
@@ -30,10 +30,9 b' import rhodecode'
30 import urllib
30 import urllib
31 import urllib2
31 import urllib2
32
32
33 from pylons.i18n.translation import lazy_ugettext as _
33 from rhodecode.translation import _
34 from sqlalchemy.ext.hybrid import hybrid_property
34 from rhodecode.authentication.base import (
35
35 RhodeCodeExternalAuthPlugin, hybrid_property)
36 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
36 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
38 from rhodecode.authentication.routes import AuthnPluginResourceBase
37 from rhodecode.authentication.routes import AuthnPluginResourceBase
39 from rhodecode.lib.colander_utils import strip_whitespace
38 from rhodecode.lib.colander_utils import strip_whitespace
@@ -27,10 +27,9 b' import colander'
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from pylons.i18n.translation import lazy_ugettext as _
30 from rhodecode.translation import _
31 from sqlalchemy.ext.hybrid import hybrid_property
31 from rhodecode.authentication.base import (
32
32 RhodeCodeExternalAuthPlugin, chop_at, hybrid_property)
33 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
34 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
33 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
35 from rhodecode.authentication.routes import AuthnPluginResourceBase
34 from rhodecode.authentication.routes import AuthnPluginResourceBase
36 from rhodecode.lib.colander_utils import strip_whitespace
35 from rhodecode.lib.colander_utils import strip_whitespace
@@ -72,14 +71,15 b' class LdapSettingsSchema(AuthnPluginSett'
72 host = colander.SchemaNode(
71 host = colander.SchemaNode(
73 colander.String(),
72 colander.String(),
74 default='',
73 default='',
75 description=_('Host of the LDAP Server'),
74 description=_('Host of the LDAP Server \n'
75 '(e.g., 192.168.2.154, or ldap-server.domain.com'),
76 preparer=strip_whitespace,
76 preparer=strip_whitespace,
77 title=_('LDAP Host'),
77 title=_('LDAP Host'),
78 widget='string')
78 widget='string')
79 port = colander.SchemaNode(
79 port = colander.SchemaNode(
80 colander.Int(),
80 colander.Int(),
81 default=389,
81 default=389,
82 description=_('Port that the LDAP server is listening on'),
82 description=_('Custom port that the LDAP server is listening on. Default: 389'),
83 preparer=strip_whitespace,
83 preparer=strip_whitespace,
84 title=_('Port'),
84 title=_('Port'),
85 validator=colander.Range(min=0, max=65536),
85 validator=colander.Range(min=0, max=65536),
@@ -87,7 +87,9 b' class LdapSettingsSchema(AuthnPluginSett'
87 dn_user = colander.SchemaNode(
87 dn_user = colander.SchemaNode(
88 colander.String(),
88 colander.String(),
89 default='',
89 default='',
90 description=_('User to connect to LDAP'),
90 description=_('Optional user DN/account to connect to LDAP if authentication is required. \n'
91 'e.g., cn=admin,dc=mydomain,dc=com, or '
92 'uid=root,cn=users,dc=mydomain,dc=com, or admin@mydomain.com'),
91 missing='',
93 missing='',
92 preparer=strip_whitespace,
94 preparer=strip_whitespace,
93 title=_('Account'),
95 title=_('Account'),
@@ -95,7 +97,7 b' class LdapSettingsSchema(AuthnPluginSett'
95 dn_pass = colander.SchemaNode(
97 dn_pass = colander.SchemaNode(
96 colander.String(),
98 colander.String(),
97 default='',
99 default='',
98 description=_('Password to connect to LDAP'),
100 description=_('Password to authenticate for given user DN.'),
99 missing='',
101 missing='',
100 preparer=strip_whitespace,
102 preparer=strip_whitespace,
101 title=_('Password'),
103 title=_('Password'),
@@ -117,7 +119,9 b' class LdapSettingsSchema(AuthnPluginSett'
117 base_dn = colander.SchemaNode(
119 base_dn = colander.SchemaNode(
118 colander.String(),
120 colander.String(),
119 default='',
121 default='',
120 description=_('Base DN to search (e.g., dc=mydomain,dc=com)'),
122 description=_('Base DN to search. Dynamic bind is supported. Add `$login` marker '
123 'in it to be replaced with current user credentials \n'
124 '(e.g., dc=mydomain,dc=com, or ou=Users,dc=mydomain,dc=com)'),
121 missing='',
125 missing='',
122 preparer=strip_whitespace,
126 preparer=strip_whitespace,
123 title=_('Base DN'),
127 title=_('Base DN'),
@@ -125,22 +129,25 b' class LdapSettingsSchema(AuthnPluginSett'
125 filter = colander.SchemaNode(
129 filter = colander.SchemaNode(
126 colander.String(),
130 colander.String(),
127 default='',
131 default='',
128 description=_('Filter to narrow results (e.g., ou=Users, etc)'),
132 description=_('Filter to narrow results \n'
133 '(e.g., (&(objectCategory=Person)(objectClass=user)), or \n'
134 '(memberof=cn=rc-login,ou=groups,ou=company,dc=mydomain,dc=com)))'),
129 missing='',
135 missing='',
130 preparer=strip_whitespace,
136 preparer=strip_whitespace,
131 title=_('LDAP Search Filter'),
137 title=_('LDAP Search Filter'),
132 widget='string')
138 widget='string')
139
133 search_scope = colander.SchemaNode(
140 search_scope = colander.SchemaNode(
134 colander.String(),
141 colander.String(),
135 default=search_scope_choices[0],
142 default=search_scope_choices[2],
136 description=_('How deep to search LDAP'),
143 description=_('How deep to search LDAP. If unsure set to SUBTREE'),
137 title=_('LDAP Search Scope'),
144 title=_('LDAP Search Scope'),
138 validator=colander.OneOf(search_scope_choices),
145 validator=colander.OneOf(search_scope_choices),
139 widget='select')
146 widget='select')
140 attr_login = colander.SchemaNode(
147 attr_login = colander.SchemaNode(
141 colander.String(),
148 colander.String(),
142 default='',
149 default='uid',
143 description=_('LDAP Attribute to map to user name'),
150 description=_('LDAP Attribute to map to user name (e.g., uid, or sAMAccountName)'),
144 preparer=strip_whitespace,
151 preparer=strip_whitespace,
145 title=_('Login Attribute'),
152 title=_('Login Attribute'),
146 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
153 missing_msg=_('The LDAP Login attribute of the CN must be specified'),
@@ -148,7 +155,7 b' class LdapSettingsSchema(AuthnPluginSett'
148 attr_firstname = colander.SchemaNode(
155 attr_firstname = colander.SchemaNode(
149 colander.String(),
156 colander.String(),
150 default='',
157 default='',
151 description=_('LDAP Attribute to map to first name'),
158 description=_('LDAP Attribute to map to first name (e.g., givenName)'),
152 missing='',
159 missing='',
153 preparer=strip_whitespace,
160 preparer=strip_whitespace,
154 title=_('First Name Attribute'),
161 title=_('First Name Attribute'),
@@ -156,7 +163,7 b' class LdapSettingsSchema(AuthnPluginSett'
156 attr_lastname = colander.SchemaNode(
163 attr_lastname = colander.SchemaNode(
157 colander.String(),
164 colander.String(),
158 default='',
165 default='',
159 description=_('LDAP Attribute to map to last name'),
166 description=_('LDAP Attribute to map to last name (e.g., sn)'),
160 missing='',
167 missing='',
161 preparer=strip_whitespace,
168 preparer=strip_whitespace,
162 title=_('Last Name Attribute'),
169 title=_('Last Name Attribute'),
@@ -164,7 +171,9 b' class LdapSettingsSchema(AuthnPluginSett'
164 attr_email = colander.SchemaNode(
171 attr_email = colander.SchemaNode(
165 colander.String(),
172 colander.String(),
166 default='',
173 default='',
167 description=_('LDAP Attribute to map to email address'),
174 description=_('LDAP Attribute to map to email address (e.g., mail).\n'
175 'Emails are a crucial part of RhodeCode. \n'
176 'If possible add a valid email attribute to ldap users.'),
168 missing='',
177 missing='',
169 preparer=strip_whitespace,
178 preparer=strip_whitespace,
170 title=_('Email Attribute'),
179 title=_('Email Attribute'),
@@ -182,7 +191,7 b' class AuthLdap(object):'
182 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
191 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
183 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
192 tls_kind='PLAIN', tls_reqcert='DEMAND', ldap_version=3,
184 search_scope='SUBTREE', attr_login='uid',
193 search_scope='SUBTREE', attr_login='uid',
185 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))'):
194 ldap_filter=None):
186 if ldap == Missing:
195 if ldap == Missing:
187 raise LdapImportError("Missing or incompatible ldap library")
196 raise LdapImportError("Missing or incompatible ldap library")
188
197
@@ -236,14 +245,13 b' class AuthLdap(object):'
236 server.start_tls_s()
245 server.start_tls_s()
237
246
238 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
247 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
239 log.debug('Trying simple_bind with password and given DN: %s',
248 log.debug('Trying simple_bind with password and given login DN: %s',
240 self.LDAP_BIND_DN)
249 self.LDAP_BIND_DN)
241 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
250 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
242
251
243 return server
252 return server
244
253
245 def get_uid(self, username):
254 def get_uid(self, username):
246 from rhodecode.lib.helpers import chop_at
247 uid = username
255 uid = username
248 for server_addr in self.SERVER_ADDRESSES:
256 for server_addr in self.SERVER_ADDRESSES:
249 uid = chop_at(username, "@%s" % server_addr)
257 uid = chop_at(username, "@%s" % server_addr)
@@ -292,8 +300,11 b' class AuthLdap(object):'
292 self.BASE_DN, self.SEARCH_SCOPE, filter_)
300 self.BASE_DN, self.SEARCH_SCOPE, filter_)
293
301
294 if not lobjects:
302 if not lobjects:
303 log.debug("No matching LDAP objects for authentication "
304 "of UID:'%s' username:(%s)", uid, username)
295 raise ldap.NO_SUCH_OBJECT()
305 raise ldap.NO_SUCH_OBJECT()
296
306
307 log.debug('Found matching ldap object, trying to authenticate')
297 for (dn, _attrs) in lobjects:
308 for (dn, _attrs) in lobjects:
298 if dn is None:
309 if dn is None:
299 continue
310 continue
@@ -304,15 +315,13 b' class AuthLdap(object):'
304 break
315 break
305
316
306 else:
317 else:
307 log.debug("No matching LDAP objects for authentication "
308 "of '%s' (%s)", uid, username)
309 raise LdapPasswordError('Failed to authenticate user '
318 raise LdapPasswordError('Failed to authenticate user '
310 'with given password')
319 'with given password')
311
320
312 except ldap.NO_SUCH_OBJECT:
321 except ldap.NO_SUCH_OBJECT:
313 log.debug("LDAP says no such user '%s' (%s), org_exc:",
322 log.debug("LDAP says no such user '%s' (%s), org_exc:",
314 uid, username, exc_info=True)
323 uid, username, exc_info=True)
315 raise LdapUsernameError()
324 raise LdapUsernameError('Unable to find user')
316 except ldap.SERVER_DOWN:
325 except ldap.SERVER_DOWN:
317 org_exc = traceback.format_exc()
326 org_exc = traceback.format_exc()
318 raise LdapConnectionError(
327 raise LdapConnectionError(
@@ -447,7 +456,7 b' class RhodeCodeAuthPlugin(RhodeCodeExter'
447 'email': get_ldap_attr('attr_email') or email,
456 'email': get_ldap_attr('attr_email') or email,
448 'admin': admin,
457 'admin': admin,
449 'active': active,
458 'active': active,
450 "active_from_extern": None,
459 'active_from_extern': None,
451 'extern_name': user_dn,
460 'extern_name': user_dn,
452 'extern_type': extern_type,
461 'extern_type': extern_type,
453 }
462 }
@@ -17,6 +17,7 b''
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20 """
21 """
21 RhodeCode authentication library for PAM
22 RhodeCode authentication library for PAM
22 """
23 """
@@ -29,10 +30,9 b' import pwd'
29 import re
30 import re
30 import socket
31 import socket
31
32
32 from pylons.i18n.translation import lazy_ugettext as _
33 from rhodecode.translation import _
33 from sqlalchemy.ext.hybrid import hybrid_property
34 from rhodecode.authentication.base import (
34
35 RhodeCodeExternalAuthPlugin, hybrid_property)
35 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
36 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
36 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 from rhodecode.authentication.routes import AuthnPluginResourceBase
37 from rhodecode.authentication.routes import AuthnPluginResourceBase
38 from rhodecode.lib.colander_utils import strip_whitespace
38 from rhodecode.lib.colander_utils import strip_whitespace
@@ -25,9 +25,8 b' RhodeCode authentication plugin for buil'
25 import logging
25 import logging
26
26
27 from pylons.i18n.translation import lazy_ugettext as _
27 from pylons.i18n.translation import lazy_ugettext as _
28 from sqlalchemy.ext.hybrid import hybrid_property
29
28
30 from rhodecode.authentication.base import RhodeCodeAuthPluginBase
29 from rhodecode.authentication.base import RhodeCodeAuthPluginBase, hybrid_property
31 from rhodecode.authentication.routes import AuthnPluginResourceBase
30 from rhodecode.authentication.routes import AuthnPluginResourceBase
32 from rhodecode.lib.utils2 import safe_str
31 from rhodecode.lib.utils2 import safe_str
33 from rhodecode.model.db import User
32 from rhodecode.model.db import User
@@ -24,12 +24,11 b' RhodeCode authentication token plugin fo'
24
24
25 import logging
25 import logging
26
26
27 from sqlalchemy.ext.hybrid import hybrid_property
28
29 from rhodecode.translation import _
27 from rhodecode.translation import _
30 from rhodecode.authentication.base import RhodeCodeAuthPluginBase, VCS_TYPE
28 from rhodecode.authentication.base import (
29 RhodeCodeAuthPluginBase, VCS_TYPE, hybrid_property)
31 from rhodecode.authentication.routes import AuthnPluginResourceBase
30 from rhodecode.authentication.routes import AuthnPluginResourceBase
32 from rhodecode.model.db import User, UserApiKeys
31 from rhodecode.model.db import User, UserApiKeys, Repository
33
32
34
33
35 log = logging.getLogger(__name__)
34 log = logging.getLogger(__name__)
@@ -122,10 +121,17 b' class RhodeCodeAuthPlugin(RhodeCodeAuthP'
122
121
123 log.debug('Authenticating user with args %s', user_attrs)
122 log.debug('Authenticating user with args %s', user_attrs)
124 if userobj.active:
123 if userobj.active:
125 role = UserApiKeys.ROLE_VCS
124 # calling context repo for token scopes
126 active_tokens = [x.api_key for x in
125 scope_repo_id = None
127 User.extra_valid_auth_tokens(userobj, role=role)]
126 if self.acl_repo_name:
128 if userobj.username == username and password in active_tokens:
127 repo = Repository.get_by_repo_name(self.acl_repo_name)
128 scope_repo_id = repo.repo_id if repo else None
129
130 token_match = userobj.authenticate_by_token(
131 password, roles=[UserApiKeys.ROLE_VCS],
132 scope_repo_id=scope_repo_id)
133
134 if userobj.username == username and token_match:
129 log.info(
135 log.info(
130 'user `%s` successfully authenticated via %s',
136 'user `%s` successfully authenticated via %s',
131 user_attrs['username'], self.name)
137 user_attrs['username'], self.name)
@@ -40,10 +40,11 b' class AuthnPluginSettingsSchemaBase(cola'
40 cache_ttl = colander.SchemaNode(
40 cache_ttl = colander.SchemaNode(
41 colander.Int(),
41 colander.Int(),
42 default=0,
42 default=0,
43 description=_('Amount of seconds to cache the authentication '
43 description=_('Amount of seconds to cache the authentication response'
44 'call for this plugin. Useful for long calls like '
44 'call for this plugin. \n'
45 'LDAP to improve the responsiveness of the '
45 'Useful for long calls like LDAP to improve the '
46 'authentication system (0 means disabled).'),
46 'performance of the authentication system '
47 '(0 means disabled).'),
47 missing=0,
48 missing=0,
48 title=_('Auth Cache TTL'),
49 title=_('Auth Cache TTL'),
49 validator=colander.Range(min=0, max=None),
50 validator=colander.Range(min=0, max=None),
@@ -87,15 +87,6 b' def load_environment(global_conf, app_co'
87
87
88 config['routes.map'] = make_map(config)
88 config['routes.map'] = make_map(config)
89
89
90 if asbool(config.get('generate_js_files', 'false')):
91 jsroutes = config['routes.map'].jsroutes()
92 jsroutes_file_content = generate_jsroutes_content(jsroutes)
93 jsroutes_file_path = os.path.join(
94 paths['static_files'], 'js', 'rhodecode', 'routes.js')
95
96 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
97 f.write(jsroutes_file_content)
98
99 config['pylons.app_globals'] = app_globals.Globals(config)
90 config['pylons.app_globals'] = app_globals.Globals(config)
100 config['pylons.h'] = helpers
91 config['pylons.h'] = helpers
101 rhodecode.CONFIG = config
92 rhodecode.CONFIG = config
@@ -184,7 +175,6 b' def load_pyramid_environment(global_conf'
184 protocol=utils.get_vcs_server_protocol(settings),
175 protocol=utils.get_vcs_server_protocol(settings),
185 log_level=settings['vcs.server.log_level'])
176 log_level=settings['vcs.server.log_level'])
186
177
187 utils.configure_pyro4(settings)
188 utils.configure_vcs(settings)
178 utils.configure_vcs(settings)
189 if vcs_server_enabled:
179 if vcs_server_enabled:
190 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(settings))
180 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(settings))
@@ -40,10 +40,7 b''
40 },
40 },
41 "python2.7-Pylons-1.0.1-patch1": {
41 "python2.7-Pylons-1.0.1-patch1": {
42 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
42 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
43 },
43 },
44 "python2.7-Pyro4-4.35": {
45 "MIT License": "http://spdx.org/licenses/MIT"
46 },
47 "python2.7-Routes-1.13": {
44 "python2.7-Routes-1.13": {
48 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
45 "BSD 4-clause \"Original\" or \"Old\" License": "http://spdx.org/licenses/BSD-4-Clause"
49 },
46 },
@@ -214,10 +211,7 b''
214 },
211 },
215 "python2.7-requests-2.9.1": {
212 "python2.7-requests-2.9.1": {
216 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
213 "Apache License 2.0": "http://spdx.org/licenses/Apache-2.0"
217 },
214 },
218 "python2.7-serpent-1.12": {
219 "MIT License": "http://spdx.org/licenses/MIT"
220 },
221 "python2.7-setuptools-19.4": {
215 "python2.7-setuptools-19.4": {
222 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0",
216 "Python Software Foundation License version 2": "http://spdx.org/licenses/Python-2.0",
223 "Zope Public License 2.0": "http://spdx.org/licenses/ZPL-2.0"
217 "Zope Public License 2.0": "http://spdx.org/licenses/ZPL-2.0"
@@ -32,7 +32,7 b' from pyramid.config import Configurator'
32 from pyramid.settings import asbool, aslist
32 from pyramid.settings import asbool, aslist
33 from pyramid.wsgi import wsgiapp
33 from pyramid.wsgi import wsgiapp
34 from pyramid.httpexceptions import (
34 from pyramid.httpexceptions import (
35 HTTPError, HTTPInternalServerError, HTTPFound)
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
36 from pyramid.events import ApplicationCreated
36 from pyramid.events import ApplicationCreated
37 from pyramid.renderers import render_to_response
37 from pyramid.renderers import render_to_response
38 from routes.middleware import RoutesMiddleware
38 from routes.middleware import RoutesMiddleware
@@ -53,7 +53,8 b' from rhodecode.lib.middleware.vcs import'
53 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
53 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
54 from rhodecode.lib.utils2 import aslist as rhodecode_aslist
54 from rhodecode.lib.utils2 import aslist as rhodecode_aslist
55 from rhodecode.subscribers import (
55 from rhodecode.subscribers import (
56 scan_repositories_if_enabled, write_metadata_if_needed)
56 scan_repositories_if_enabled, write_metadata_if_needed,
57 write_js_routes_if_enabled)
57
58
58
59
59 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
@@ -219,18 +220,14 b' def add_pylons_compat_data(registry, glo'
219
220
220
221
221 def error_handler(exception, request):
222 def error_handler(exception, request):
222 from rhodecode.model.settings import SettingsModel
223 import rhodecode
223 from rhodecode.lib.utils2 import AttributeDict
224 from rhodecode.lib.utils2 import AttributeDict
224
225
225 try:
226 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
226 rc_config = SettingsModel().get_all_settings()
227 except Exception:
228 log.exception('failed to fetch settings')
229 rc_config = {}
230
227
231 base_response = HTTPInternalServerError()
228 base_response = HTTPInternalServerError()
232 # prefer original exception for the response since it may have headers set
229 # prefer original exception for the response since it may have headers set
233 if isinstance(exception, HTTPError):
230 if isinstance(exception, HTTPException):
234 base_response = exception
231 base_response = exception
235
232
236 def is_http_error(response):
233 def is_http_error(response):
@@ -251,7 +248,7 b' def error_handler(exception, request):'
251 request.route_url('rhodecode_support')
248 request.route_url('rhodecode_support')
252 )
249 )
253 c.redirect_time = 0
250 c.redirect_time = 0
254 c.rhodecode_name = rc_config.get('rhodecode_title', '')
251 c.rhodecode_name = rhodecode_title
255 if not c.rhodecode_name:
252 if not c.rhodecode_name:
256 c.rhodecode_name = 'Rhodecode'
253 c.rhodecode_name = 'Rhodecode'
257
254
@@ -281,14 +278,24 b' def includeme(config):'
281 # Includes which are required. The application would fail without them.
278 # Includes which are required. The application would fail without them.
282 config.include('pyramid_mako')
279 config.include('pyramid_mako')
283 config.include('pyramid_beaker')
280 config.include('pyramid_beaker')
284 config.include('rhodecode.channelstream')
281
285 config.include('rhodecode.admin')
286 config.include('rhodecode.authentication')
282 config.include('rhodecode.authentication')
287 config.include('rhodecode.integrations')
283 config.include('rhodecode.integrations')
288 config.include('rhodecode.login')
284
285 # apps
286 config.include('rhodecode.apps._base')
287
288 config.include('rhodecode.apps.admin')
289 config.include('rhodecode.apps.channelstream')
290 config.include('rhodecode.apps.login')
291 config.include('rhodecode.apps.repository')
292 config.include('rhodecode.apps.user_profile')
293 config.include('rhodecode.apps.my_account')
294 config.include('rhodecode.apps.svn_support')
295
289 config.include('rhodecode.tweens')
296 config.include('rhodecode.tweens')
290 config.include('rhodecode.api')
297 config.include('rhodecode.api')
291 config.include('rhodecode.svn_support')
298
292 config.add_route(
299 config.add_route(
293 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
300 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
294
301
@@ -298,6 +305,7 b' def includeme(config):'
298 # Add subscribers.
305 # Add subscribers.
299 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
306 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
300 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
307 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
308 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
301
309
302 # Set the authorization policy.
310 # Set the authorization policy.
303 authz_policy = ACLAuthorizationPolicy()
311 authz_policy = ACLAuthorizationPolicy()
@@ -92,7 +92,7 b' class JSRoutesMapper(Mapper):'
92 def _extract_route_information(self, route):
92 def _extract_route_information(self, route):
93 """
93 """
94 Convert a route into tuple(name, path, args), eg:
94 Convert a route into tuple(name, path, args), eg:
95 ('user_profile', '/profile/%(username)s', ['username'])
95 ('show_user', '/profile/%(username)s', ['username'])
96 """
96 """
97 routepath = route.routepath
97 routepath = route.routepath
98 def replace(matchobj):
98 def replace(matchobj):
@@ -198,10 +198,6 b' def make_map(config):'
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
199 action='user_group_autocomplete_data', jsroute=True)
199 action='user_group_autocomplete_data', jsroute=True)
200
200
201 rmap.connect(
202 'user_profile', '/_profiles/{username}', controller='users',
203 action='user_profile')
204
205 # TODO: johbo: Static links, to be replaced by our redirection mechanism
201 # TODO: johbo: Static links, to be replaced by our redirection mechanism
206 rmap.connect('rst_help',
202 rmap.connect('rst_help',
207 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
203 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
@@ -296,8 +292,6 b' def make_map(config):'
296 controller='admin/users') as m:
292 controller='admin/users') as m:
297 m.connect('users', '/users',
293 m.connect('users', '/users',
298 action='create', conditions={'method': ['POST']})
294 action='create', conditions={'method': ['POST']})
299 m.connect('users', '/users',
300 action='index', conditions={'method': ['GET']})
301 m.connect('new_user', '/users/new',
295 m.connect('new_user', '/users/new',
302 action='new', conditions={'method': ['GET']})
296 action='new', conditions={'method': ['GET']})
303 m.connect('update_user', '/users/{user_id}',
297 m.connect('update_user', '/users/{user_id}',
@@ -319,13 +313,6 b' def make_map(config):'
319 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
313 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
320 action='update_advanced', conditions={'method': ['PUT']})
314 action='update_advanced', conditions={'method': ['PUT']})
321
315
322 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
323 action='edit_auth_tokens', conditions={'method': ['GET']})
324 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
325 action='add_auth_token', conditions={'method': ['PUT']})
326 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
327 action='delete_auth_token', conditions={'method': ['DELETE']})
328
329 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
316 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
330 action='edit_global_perms', conditions={'method': ['GET']})
317 action='edit_global_perms', conditions={'method': ['GET']})
331 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
318 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
@@ -387,6 +374,10 b' def make_map(config):'
387 '/user_groups/{user_group_id}/edit/advanced',
374 '/user_groups/{user_group_id}/edit/advanced',
388 action='edit_advanced', conditions={'method': ['GET']})
375 action='edit_advanced', conditions={'method': ['GET']})
389
376
377 m.connect('edit_user_group_advanced_sync',
378 '/user_groups/{user_group_id}/edit/advanced/sync',
379 action='edit_advanced_set_synchronization', conditions={'method': ['POST']})
380
390 m.connect('edit_user_group_members',
381 m.connect('edit_user_group_members',
391 '/user_groups/{user_group_id}/edit/members', jsroute=True,
382 '/user_groups/{user_group_id}/edit/members', jsroute=True,
392 action='user_group_members', conditions={'method': ['GET']})
383 action='user_group_members', conditions={'method': ['GET']})
@@ -518,15 +509,15 b' def make_map(config):'
518 with rmap.submapper(path_prefix=ADMIN_PREFIX,
509 with rmap.submapper(path_prefix=ADMIN_PREFIX,
519 controller='admin/my_account') as m:
510 controller='admin/my_account') as m:
520
511
521 m.connect('my_account', '/my_account',
522 action='my_account', conditions={'method': ['GET']})
523 m.connect('my_account_edit', '/my_account/edit',
512 m.connect('my_account_edit', '/my_account/edit',
524 action='my_account_edit', conditions={'method': ['GET']})
513 action='my_account_edit', conditions={'method': ['GET']})
525 m.connect('my_account', '/my_account',
514 m.connect('my_account', '/my_account/update',
526 action='my_account_update', conditions={'method': ['POST']})
515 action='my_account_update', conditions={'method': ['POST']})
527
516
517 # NOTE(marcink): this needs to be kept for password force flag to be
518 # handler, remove after migration to pyramid
528 m.connect('my_account_password', '/my_account/password',
519 m.connect('my_account_password', '/my_account/password',
529 action='my_account_password', conditions={'method': ['GET', 'POST']})
520 action='my_account_password', conditions={'method': ['GET']})
530
521
531 m.connect('my_account_repos', '/my_account/repos',
522 m.connect('my_account_repos', '/my_account/repos',
532 action='my_account_repos', conditions={'method': ['GET']})
523 action='my_account_repos', conditions={'method': ['GET']})
@@ -547,12 +538,6 b' def make_map(config):'
547 m.connect('my_account_emails', '/my_account/emails',
538 m.connect('my_account_emails', '/my_account/emails',
548 action='my_account_emails_delete', conditions={'method': ['DELETE']})
539 action='my_account_emails_delete', conditions={'method': ['DELETE']})
549
540
550 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
551 action='my_account_auth_tokens', conditions={'method': ['GET']})
552 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
553 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
554 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
555 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
556 m.connect('my_account_notifications', '/my_account/notifications',
541 m.connect('my_account_notifications', '/my_account/notifications',
557 action='my_notifications',
542 action='my_notifications',
558 conditions={'method': ['GET']})
543 conditions={'method': ['GET']})
@@ -1066,6 +1051,12 b' def make_map(config):'
1066 f_path='', annotate=True, conditions={'function': check_repo},
1051 f_path='', annotate=True, conditions={'function': check_repo},
1067 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1052 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1068
1053
1054 rmap.connect('files_annotate_previous',
1055 '/{repo_name}/annotate-previous/{revision}/{f_path}',
1056 controller='files', action='annotate_previous', revision='tip',
1057 f_path='', annotate=True, conditions={'function': check_repo},
1058 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1059
1069 rmap.connect('files_edit',
1060 rmap.connect('files_edit',
1070 '/{repo_name}/edit/{revision}/{f_path}',
1061 '/{repo_name}/edit/{revision}/{f_path}',
1071 controller='files', action='edit', revision='tip',
1062 controller='files', action='edit', revision='tip',
@@ -1137,11 +1128,6 b' def make_map(config):'
1137 conditions={'function': check_repo},
1128 conditions={'function': check_repo},
1138 requirements=URL_NAME_REQUIREMENTS)
1129 requirements=URL_NAME_REQUIREMENTS)
1139
1130
1140 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1141 controller='followers', action='followers',
1142 conditions={'function': check_repo},
1143 requirements=URL_NAME_REQUIREMENTS)
1144
1145 # must be here for proper group/repo catching pattern
1131 # must be here for proper group/repo catching pattern
1146 _connect_with_slash(
1132 _connect_with_slash(
1147 rmap, 'repo_group_home', '/{group_name}',
1133 rmap, 'repo_group_home', '/{group_name}',
@@ -20,29 +20,11 b''
20
20
21 import os
21 import os
22 import shlex
22 import shlex
23 import Pyro4
24 import platform
23 import platform
25
24
26 from rhodecode.model import init_model
25 from rhodecode.model import init_model
27
26
28
27
29 def configure_pyro4(config):
30 """
31 Configure Pyro4 based on `config`.
32
33 This will mainly set the different configuration parameters of the Pyro4
34 library based on the settings in our INI files. The Pyro4 documentation
35 lists more details about the specific settings and their meaning.
36 """
37 Pyro4.config.COMMTIMEOUT = float(config['vcs.connection_timeout'])
38 Pyro4.config.SERIALIZER = 'pickle'
39 Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle')
40
41 # Note: We need server configuration in the WSGI processes
42 # because we provide a callback server in certain vcs operations.
43 Pyro4.config.SERVERTYPE = "multiplex"
44 Pyro4.config.POLLTIMEOUT = 0.01
45
46
28
47 def configure_vcs(config):
29 def configure_vcs(config):
48 """
30 """
@@ -77,9 +59,16 b' def initialize_test_environment(settings'
77 create_test_directory, create_test_database, create_test_repositories,
59 create_test_directory, create_test_database, create_test_repositories,
78 create_test_index)
60 create_test_index)
79 from rhodecode.tests import TESTS_TMP_PATH
61 from rhodecode.tests import TESTS_TMP_PATH
62 from rhodecode.lib.vcs.backends.hg import largefiles_store
63 from rhodecode.lib.vcs.backends.git import lfs_store
64
80 # test repos
65 # test repos
81 if test_env:
66 if test_env:
82 create_test_directory(TESTS_TMP_PATH)
67 create_test_directory(TESTS_TMP_PATH)
68 # large object stores
69 create_test_directory(largefiles_store(TESTS_TMP_PATH))
70 create_test_directory(lfs_store(TESTS_TMP_PATH))
71
83 create_test_database(TESTS_TMP_PATH, settings)
72 create_test_database(TESTS_TMP_PATH, settings)
84 create_test_repositories(TESTS_TMP_PATH, settings)
73 create_test_repositories(TESTS_TMP_PATH, settings)
85 create_test_index(TESTS_TMP_PATH, settings)
74 create_test_index(TESTS_TMP_PATH, settings)
@@ -28,101 +28,17 b' import logging'
28 from pylons import request, tmpl_context as c, url
28 from pylons import request, tmpl_context as c, url
29 from pylons.controllers.util import redirect
29 from pylons.controllers.util import redirect
30 from sqlalchemy.orm import joinedload
30 from sqlalchemy.orm import joinedload
31 from whoosh.qparser.default import QueryParser, query
32 from whoosh.qparser.dateparse import DateParserPlugin
33 from whoosh.fields import (TEXT, Schema, DATETIME)
34 from sqlalchemy.sql.expression import or_, and_, func
35
31
36 from rhodecode.model.db import UserLog, PullRequest
32 from rhodecode.model.db import UserLog, PullRequest
33 from rhodecode.lib.user_log_filter import user_log_filter
37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
34 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
38 from rhodecode.lib.base import BaseController, render
35 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.utils2 import safe_int, remove_prefix, remove_suffix
36 from rhodecode.lib.utils2 import safe_int
40 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.helpers import Page
41
38
42
39
43 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
44
41
45 # JOURNAL SCHEMA used only to generate queries in journal. We use whoosh
46 # querylang to build sql queries and filter journals
47 JOURNAL_SCHEMA = Schema(
48 username=TEXT(),
49 date=DATETIME(),
50 action=TEXT(),
51 repository=TEXT(),
52 ip=TEXT(),
53 )
54
55
56 def _journal_filter(user_log, search_term):
57 """
58 Filters sqlalchemy user_log based on search_term with whoosh Query language
59 http://packages.python.org/Whoosh/querylang.html
60
61 :param user_log:
62 :param search_term:
63 """
64 log.debug('Initial search term: %r' % search_term)
65 qry = None
66 if search_term:
67 qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
68 qp.add_plugin(DateParserPlugin())
69 qry = qp.parse(unicode(search_term))
70 log.debug('Filtering using parsed query %r' % qry)
71
72 def wildcard_handler(col, wc_term):
73 if wc_term.startswith('*') and not wc_term.endswith('*'):
74 # postfix == endswith
75 wc_term = remove_prefix(wc_term, prefix='*')
76 return func.lower(col).endswith(wc_term)
77 elif wc_term.startswith('*') and wc_term.endswith('*'):
78 # wildcard == ilike
79 wc_term = remove_prefix(wc_term, prefix='*')
80 wc_term = remove_suffix(wc_term, suffix='*')
81 return func.lower(col).contains(wc_term)
82
83 def get_filterion(field, val, term):
84
85 if field == 'repository':
86 field = getattr(UserLog, 'repository_name')
87 elif field == 'ip':
88 field = getattr(UserLog, 'user_ip')
89 elif field == 'date':
90 field = getattr(UserLog, 'action_date')
91 elif field == 'username':
92 field = getattr(UserLog, 'username')
93 else:
94 field = getattr(UserLog, field)
95 log.debug('filter field: %s val=>%s' % (field, val))
96
97 # sql filtering
98 if isinstance(term, query.Wildcard):
99 return wildcard_handler(field, val)
100 elif isinstance(term, query.Prefix):
101 return func.lower(field).startswith(func.lower(val))
102 elif isinstance(term, query.DateRange):
103 return and_(field >= val[0], field <= val[1])
104 return func.lower(field) == func.lower(val)
105
106 if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard,
107 query.DateRange)):
108 if not isinstance(qry, query.And):
109 qry = [qry]
110 for term in qry:
111 field = term.fieldname
112 val = (term.text if not isinstance(term, query.DateRange)
113 else [term.startdate, term.enddate])
114 user_log = user_log.filter(get_filterion(field, val, term))
115 elif isinstance(qry, query.Or):
116 filters = []
117 for term in qry:
118 field = term.fieldname
119 val = (term.text if not isinstance(term, query.DateRange)
120 else [term.startdate, term.enddate])
121 filters.append(get_filterion(field, val, term))
122 user_log = user_log.filter(or_(*filters))
123
124 return user_log
125
126
42
127 class AdminController(BaseController):
43 class AdminController(BaseController):
128
44
@@ -139,7 +55,7 b' class AdminController(BaseController):'
139 # FILTERING
55 # FILTERING
140 c.search_term = request.GET.get('filter')
56 c.search_term = request.GET.get('filter')
141 try:
57 try:
142 users_log = _journal_filter(users_log, c.search_term)
58 users_log = user_log_filter(users_log, c.search_term)
143 except Exception:
59 except Exception:
144 # we want this to crash for now
60 # we want this to crash for now
145 raise
61 raise
@@ -29,32 +29,30 b' import datetime'
29 import formencode
29 import formencode
30 from formencode import htmlfill
30 from formencode import htmlfill
31 from pyramid.threadlocal import get_current_registry
31 from pyramid.threadlocal import get_current_registry
32 from pylons import request, tmpl_context as c, url, session
32 from pyramid.httpexceptions import HTTPFound
33
34 from pylons import request, tmpl_context as c, url
33 from pylons.controllers.util import redirect
35 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
35 from sqlalchemy.orm import joinedload
37 from sqlalchemy.orm import joinedload
36 from webob.exc import HTTPBadGateway
37
38
38 from rhodecode import forms
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib import auth
40 from rhodecode.lib import auth
41 from rhodecode.lib.auth import (
41 from rhodecode.lib.auth import (
42 LoginRequired, NotAnonymous, AuthUser, generate_auth_token)
42 LoginRequired, NotAnonymous, AuthUser)
43 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.utils import jsonify
44 from rhodecode.lib.utils import jsonify
45 from rhodecode.lib.utils2 import safe_int, md5, str2bool
45 from rhodecode.lib.utils2 import safe_int, str2bool
46 from rhodecode.lib.ext_json import json
46 from rhodecode.lib.ext_json import json
47 from rhodecode.lib.channelstream import channelstream_request, \
47 from rhodecode.lib.channelstream import channelstream_request, \
48 ChannelstreamException
48 ChannelstreamException
49
49
50 from rhodecode.model.validation_schema.schemas import user_schema
51 from rhodecode.model.db import (
50 from rhodecode.model.db import (
52 Repository, PullRequest, UserEmailMap, User, UserFollowing)
51 Repository, PullRequest, UserEmailMap, User, UserFollowing)
53 from rhodecode.model.forms import UserForm
52 from rhodecode.model.forms import UserForm
54 from rhodecode.model.scm import RepoList
53 from rhodecode.model.scm import RepoList
55 from rhodecode.model.user import UserModel
54 from rhodecode.model.user import UserModel
56 from rhodecode.model.repo import RepoModel
55 from rhodecode.model.repo import RepoModel
57 from rhodecode.model.auth_token import AuthTokenModel
58 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
59 from rhodecode.model.pull_request import PullRequestModel
57 from rhodecode.model.pull_request import PullRequestModel
60 from rhodecode.model.comment import CommentsModel
58 from rhodecode.model.comment import CommentsModel
@@ -79,7 +77,7 b' class MyAccountController(BaseController'
79 if c.user.username == User.DEFAULT_USER:
77 if c.user.username == User.DEFAULT_USER:
80 h.flash(_("You can't edit this user since it's"
78 h.flash(_("You can't edit this user since it's"
81 " crucial for entire application"), category='warning')
79 " crucial for entire application"), category='warning')
82 return redirect(url('users'))
80 return redirect(h.route_path('users'))
83
81
84 c.auth_user = AuthUser(
82 c.auth_user = AuthUser(
85 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
83 user_id=c.rhodecode_user.user_id, ip_addr=self.ip_addr)
@@ -156,7 +154,7 b' class MyAccountController(BaseController'
156 % form_result.get('username'), category='error')
154 % form_result.get('username'), category='error')
157
155
158 if update:
156 if update:
159 return redirect('my_account')
157 raise HTTPFound(h.route_path('my_account_profile'))
160
158
161 return htmlfill.render(
159 return htmlfill.render(
162 render('admin/my_account/my_account.mako'),
160 render('admin/my_account/my_account.mako'),
@@ -165,19 +163,6 b' class MyAccountController(BaseController'
165 force_defaults=False
163 force_defaults=False
166 )
164 )
167
165
168 def my_account(self):
169 """
170 GET /_admin/my_account Displays info about my account
171 """
172 # url('my_account')
173 c.active = 'profile'
174 self.__load_data()
175
176 defaults = c.user.get_dict()
177 return htmlfill.render(
178 render('admin/my_account/my_account.mako'),
179 defaults=defaults, encoding="UTF-8", force_defaults=False)
180
181 def my_account_edit(self):
166 def my_account_edit(self):
182 """
167 """
183 GET /_admin/my_account/edit Displays edit form of my account
168 GET /_admin/my_account/edit Displays edit form of my account
@@ -196,47 +181,6 b' class MyAccountController(BaseController'
196 force_defaults=False
181 force_defaults=False
197 )
182 )
198
183
199 @auth.CSRFRequired(except_methods=['GET'])
200 def my_account_password(self):
201 c.active = 'password'
202 self.__load_data()
203 c.extern_type = c.user.extern_type
204
205 schema = user_schema.ChangePasswordSchema().bind(
206 username=c.rhodecode_user.username)
207
208 form = forms.Form(schema,
209 buttons=(forms.buttons.save, forms.buttons.reset))
210
211 if request.method == 'POST' and c.extern_type == 'rhodecode':
212 controls = request.POST.items()
213 try:
214 valid_data = form.validate(controls)
215 UserModel().update_user(c.rhodecode_user.user_id, **valid_data)
216 instance = c.rhodecode_user.get_instance()
217 instance.update_userdata(force_password_change=False)
218 Session().commit()
219 except forms.ValidationFailure as e:
220 request.session.flash(
221 _('Error occurred during update of user password'),
222 queue='error')
223 form = e
224 except Exception:
225 log.exception("Exception updating password")
226 request.session.flash(
227 _('Error occurred during update of user password'),
228 queue='error')
229 else:
230 session.setdefault('rhodecode_user', {}).update(
231 {'password': md5(instance.password)})
232 session.save()
233 request.session.flash(
234 _("Successfully updated password"), queue='success')
235 return redirect(url('my_account_password'))
236
237 c.form = form
238 return render('admin/my_account/my_account.mako')
239
240 def my_account_repos(self):
184 def my_account_repos(self):
241 c.active = 'repos'
185 c.active = 'repos'
242 self.__load_data()
186 self.__load_data()
@@ -376,54 +320,6 b' class MyAccountController(BaseController'
376 else:
320 else:
377 return json.dumps(data)
321 return json.dumps(data)
378
322
379 def my_account_auth_tokens(self):
380 c.active = 'auth_tokens'
381 self.__load_data()
382 show_expired = True
383 c.lifetime_values = [
384 (str(-1), _('forever')),
385 (str(5), _('5 minutes')),
386 (str(60), _('1 hour')),
387 (str(60 * 24), _('1 day')),
388 (str(60 * 24 * 30), _('1 month')),
389 ]
390 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
391 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
392 for x in AuthTokenModel.cls.ROLES]
393 c.role_options = [(c.role_values, _("Role"))]
394 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
395 c.rhodecode_user.user_id, show_expired=show_expired)
396 return render('admin/my_account/my_account.mako')
397
398 @auth.CSRFRequired()
399 def my_account_auth_tokens_add(self):
400 lifetime = safe_int(request.POST.get('lifetime'), -1)
401 description = request.POST.get('description')
402 role = request.POST.get('role')
403 AuthTokenModel().create(c.rhodecode_user.user_id, description, lifetime,
404 role)
405 Session().commit()
406 h.flash(_("Auth token successfully created"), category='success')
407 return redirect(url('my_account_auth_tokens'))
408
409 @auth.CSRFRequired()
410 def my_account_auth_tokens_delete(self):
411 auth_token = request.POST.get('del_auth_token')
412 user_id = c.rhodecode_user.user_id
413 if request.POST.get('del_auth_token_builtin'):
414 user = User.get(user_id)
415 if user:
416 user.api_key = generate_auth_token(user.username)
417 Session().add(user)
418 Session().commit()
419 h.flash(_("Auth token successfully reset"), category='success')
420 elif auth_token:
421 AuthTokenModel().delete(auth_token, c.rhodecode_user.user_id)
422 Session().commit()
423 h.flash(_("Auth token successfully deleted"), category='success')
424
425 return redirect(url('my_account_auth_tokens'))
426
427 def my_notifications(self):
323 def my_notifications(self):
428 c.active = 'notifications'
324 c.active = 'notifications'
429 return render('admin/my_account/my_account.mako')
325 return render('admin/my_account/my_account.mako')
@@ -36,7 +36,8 b' from pyramid.threadlocal import get_curr'
36 from webob.exc import HTTPBadRequest
36 from webob.exc import HTTPBadRequest
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.admin.navigation import navigation_list
39 from rhodecode.apps.admin.navigation import navigation_list
40 from rhodecode.apps.svn_support.config_keys import generate_config
40 from rhodecode.lib import auth
41 from rhodecode.lib import auth
41 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
43 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
@@ -62,7 +63,6 b' from rhodecode.model.settings import ('
62 SettingsModel)
63 SettingsModel)
63
64
64 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
65 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
65 from rhodecode.svn_support.config_keys import generate_config
66
66
67
67
68 log = logging.getLogger(__name__)
68 log = logging.getLogger(__name__)
@@ -82,7 +82,7 b' class SettingsController(BaseController)'
82 rhodecode.CONFIG.get('labs_settings_active', 'true'))
82 rhodecode.CONFIG.get('labs_settings_active', 'true'))
83 c.navlist = navigation_list(request)
83 c.navlist = navigation_list(request)
84
84
85 def _get_hg_ui_settings(self):
85 def _get_ui_settings(self):
86 ret = RhodeCodeUi.query().all()
86 ret = RhodeCodeUi.query().all()
87
87
88 if not ret:
88 if not ret:
@@ -94,7 +94,7 b' class SettingsController(BaseController)'
94 if k == '/':
94 if k == '/':
95 k = 'root_path'
95 k = 'root_path'
96
96
97 if k in ['push_ssl', 'publish']:
97 if k in ['push_ssl', 'publish', 'enabled']:
98 v = str2bool(v)
98 v = str2bool(v)
99
99
100 if k.find('.') != -1:
100 if k.find('.') != -1:
@@ -165,6 +165,7 b' class SettingsController(BaseController)'
165
165
166 model.create_or_update_global_svn_settings(form_result)
166 model.create_or_update_global_svn_settings(form_result)
167 model.create_or_update_global_hg_settings(form_result)
167 model.create_or_update_global_hg_settings(form_result)
168 model.create_or_update_global_git_settings(form_result)
168 model.create_or_update_global_pr_settings(form_result)
169 model.create_or_update_global_pr_settings(form_result)
169 except Exception:
170 except Exception:
170 log.exception("Exception while updating settings")
171 log.exception("Exception while updating settings")
@@ -668,7 +669,8 b' class SettingsController(BaseController)'
668
669
669 def _form_defaults(self):
670 def _form_defaults(self):
670 defaults = SettingsModel().get_all_settings()
671 defaults = SettingsModel().get_all_settings()
671 defaults.update(self._get_hg_ui_settings())
672 defaults.update(self._get_ui_settings())
673
672 defaults.update({
674 defaults.update({
673 'new_svn_branch': '',
675 'new_svn_branch': '',
674 'new_svn_tag': '',
676 'new_svn_tag': '',
@@ -117,7 +117,7 b' class UserGroupsController(BaseControlle'
117 def user_group_actions(user_group_id, user_group_name):
117 def user_group_actions(user_group_id, user_group_name):
118 return _render("user_group_actions", user_group_id, user_group_name)
118 return _render("user_group_actions", user_group_id, user_group_name)
119
119
120 ## json generate
120 # json generate
121 group_iter = UserGroupList(UserGroup.query().all(),
121 group_iter = UserGroupList(UserGroup.query().all(),
122 perm_set=['usergroup.admin'])
122 perm_set=['usergroup.admin'])
123
123
@@ -129,6 +129,7 b' class UserGroupsController(BaseControlle'
129 "group_name_raw": user_gr.users_group_name,
129 "group_name_raw": user_gr.users_group_name,
130 "desc": h.escape(user_gr.user_group_description),
130 "desc": h.escape(user_gr.user_group_description),
131 "members": len(user_gr.members),
131 "members": len(user_gr.members),
132 "sync": user_gr.group_data.get('extern_type'),
132 "active": h.bool2icon(user_gr.users_group_active),
133 "active": h.bool2icon(user_gr.users_group_active),
133 "owner": h.escape(h.link_to_user(user_gr.user.username)),
134 "owner": h.escape(h.link_to_user(user_gr.user.username)),
134 "action": user_group_actions(
135 "action": user_group_actions(
@@ -431,7 +432,6 b' class UserGroupsController(BaseControlle'
431 prefix_error=False,
432 prefix_error=False,
432 encoding="UTF-8",
433 encoding="UTF-8",
433 force_defaults=False)
434 force_defaults=False)
434
435 except Exception:
435 except Exception:
436 log.exception("Exception during permissions saving")
436 log.exception("Exception during permissions saving")
437 h.flash(_('An error occurred during permissions saving'),
437 h.flash(_('An error occurred during permissions saving'),
@@ -459,6 +459,36 b' class UserGroupsController(BaseControlle'
459 return render('admin/user_groups/user_group_edit.mako')
459 return render('admin/user_groups/user_group_edit.mako')
460
460
461 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
461 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
462 def edit_advanced_set_synchronization(self, user_group_id):
463 user_group_id = safe_int(user_group_id)
464 user_group = UserGroup.get_or_404(user_group_id)
465
466 existing = user_group.group_data.get('extern_type')
467
468 if existing:
469 new_state = user_group.group_data
470 new_state['extern_type'] = None
471 else:
472 new_state = user_group.group_data
473 new_state['extern_type'] = 'manual'
474 new_state['extern_type_set_by'] = c.rhodecode_user.username
475
476 try:
477 user_group.group_data = new_state
478 Session().add(user_group)
479 Session().commit()
480
481 h.flash(_('User Group synchronization updated successfully'),
482 category='success')
483 except Exception:
484 log.exception("Exception during sync settings saving")
485 h.flash(_('An error occurred during synchronization update'),
486 category='error')
487
488 return redirect(
489 url('edit_user_group_advanced', user_group_id=user_group_id))
490
491 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
462 @XHRRequired()
492 @XHRRequired()
463 @jsonify
493 @jsonify
464 def user_group_members(self, user_group_id):
494 def user_group_members(self, user_group_id):
@@ -50,7 +50,6 b' from rhodecode.model.user import UserMod'
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.permission import PermissionModel
51 from rhodecode.model.permission import PermissionModel
52 from rhodecode.lib.utils import action_logger
52 from rhodecode.lib.utils import action_logger
53 from rhodecode.lib.ext_json import json
54 from rhodecode.lib.utils2 import datetime_to_time, safe_int, AttributeDict
53 from rhodecode.lib.utils2 import datetime_to_time, safe_int, AttributeDict
55
54
56 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
@@ -76,51 +75,6 b' class UsersController(BaseController):'
76 ]
75 ]
77 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
76 PermissionModel().set_global_permission_choices(c, gettext_translator=_)
78
77
79 @HasPermissionAllDecorator('hg.admin')
80 def index(self):
81 """GET /users: All items in the collection"""
82 # url('users')
83
84 from rhodecode.lib.utils import PartialRenderer
85 _render = PartialRenderer('data_table/_dt_elements.mako')
86
87 def username(user_id, username):
88 return _render("user_name", user_id, username)
89
90 def user_actions(user_id, username):
91 return _render("user_actions", user_id, username)
92
93 # json generate
94 c.users_list = User.query()\
95 .filter(User.username != User.DEFAULT_USER) \
96 .all()
97
98 users_data = []
99 for user in c.users_list:
100 users_data.append({
101 "username": h.gravatar_with_user(user.username),
102 "username_raw": user.username,
103 "email": user.email,
104 "first_name": h.escape(user.name),
105 "last_name": h.escape(user.lastname),
106 "last_login": h.format_date(user.last_login),
107 "last_login_raw": datetime_to_time(user.last_login),
108 "last_activity": h.format_date(
109 h.time_to_datetime(user.user_data.get('last_activity', 0))),
110 "last_activity_raw": user.user_data.get('last_activity', 0),
111 "active": h.bool2icon(user.active),
112 "active_raw": user.active,
113 "admin": h.bool2icon(user.admin),
114 "admin_raw": user.admin,
115 "extern_type": user.extern_type,
116 "extern_name": user.extern_name,
117 "action": user_actions(user.user_id, user.username),
118 })
119
120
121 c.data = json.dumps(users_data)
122 return render('admin/users/users.mako')
123
124 def _get_personal_repo_group_template_vars(self):
78 def _get_personal_repo_group_template_vars(self):
125 DummyUser = AttributeDict({
79 DummyUser = AttributeDict({
126 'username': '${username}',
80 'username': '${username}',
@@ -135,7 +89,6 b' class UsersController(BaseController):'
135 @auth.CSRFRequired()
89 @auth.CSRFRequired()
136 def create(self):
90 def create(self):
137 """POST /users: Create a new item"""
91 """POST /users: Create a new item"""
138 # url('users')
139 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
92 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
140 user_model = UserModel()
93 user_model = UserModel()
141 user_form = UserForm()()
94 user_form = UserForm()()
@@ -168,7 +121,7 b' class UsersController(BaseController):'
168 log.exception("Exception creation of user")
121 log.exception("Exception creation of user")
169 h.flash(_('Error occurred during creation of user %s')
122 h.flash(_('Error occurred during creation of user %s')
170 % request.POST.get('username'), category='error')
123 % request.POST.get('username'), category='error')
171 return redirect(url('users'))
124 return redirect(h.route_path('users'))
172
125
173 @HasPermissionAllDecorator('hg.admin')
126 @HasPermissionAllDecorator('hg.admin')
174 def new(self):
127 def new(self):
@@ -312,7 +265,7 b' class UsersController(BaseController):'
312 log.exception("Exception during deletion of user")
265 log.exception("Exception during deletion of user")
313 h.flash(_('An error occurred during deletion of user'),
266 h.flash(_('An error occurred during deletion of user'),
314 category='error')
267 category='error')
315 return redirect(url('users'))
268 return redirect(h.route_path('users'))
316
269
317 @HasPermissionAllDecorator('hg.admin')
270 @HasPermissionAllDecorator('hg.admin')
318 @auth.CSRFRequired()
271 @auth.CSRFRequired()
@@ -404,7 +357,7 b' class UsersController(BaseController):'
404 c.user = User.get_or_404(user_id)
357 c.user = User.get_or_404(user_id)
405 if c.user.username == User.DEFAULT_USER:
358 if c.user.username == User.DEFAULT_USER:
406 h.flash(_("You can't edit this user"), category='warning')
359 h.flash(_("You can't edit this user"), category='warning')
407 return redirect(url('users'))
360 return redirect(h.route_path('users'))
408
361
409 c.active = 'profile'
362 c.active = 'profile'
410 c.extern_type = c.user.extern_type
363 c.extern_type = c.user.extern_type
@@ -425,7 +378,7 b' class UsersController(BaseController):'
425 user = c.user = User.get_or_404(user_id)
378 user = c.user = User.get_or_404(user_id)
426 if user.username == User.DEFAULT_USER:
379 if user.username == User.DEFAULT_USER:
427 h.flash(_("You can't edit this user"), category='warning')
380 h.flash(_("You can't edit this user"), category='warning')
428 return redirect(url('users'))
381 return redirect(h.route_path('users'))
429
382
430 c.active = 'advanced'
383 c.active = 'advanced'
431 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
384 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
@@ -452,83 +405,12 b' class UsersController(BaseController):'
452 force_defaults=False)
405 force_defaults=False)
453
406
454 @HasPermissionAllDecorator('hg.admin')
407 @HasPermissionAllDecorator('hg.admin')
455 def edit_auth_tokens(self, user_id):
456 user_id = safe_int(user_id)
457 c.user = User.get_or_404(user_id)
458 if c.user.username == User.DEFAULT_USER:
459 h.flash(_("You can't edit this user"), category='warning')
460 return redirect(url('users'))
461
462 c.active = 'auth_tokens'
463 show_expired = True
464 c.lifetime_values = [
465 (str(-1), _('forever')),
466 (str(5), _('5 minutes')),
467 (str(60), _('1 hour')),
468 (str(60 * 24), _('1 day')),
469 (str(60 * 24 * 30), _('1 month')),
470 ]
471 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
472 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
473 for x in AuthTokenModel.cls.ROLES]
474 c.role_options = [(c.role_values, _("Role"))]
475 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
476 c.user.user_id, show_expired=show_expired)
477 defaults = c.user.get_dict()
478 return htmlfill.render(
479 render('admin/users/user_edit.mako'),
480 defaults=defaults,
481 encoding="UTF-8",
482 force_defaults=False)
483
484 @HasPermissionAllDecorator('hg.admin')
485 @auth.CSRFRequired()
486 def add_auth_token(self, user_id):
487 user_id = safe_int(user_id)
488 c.user = User.get_or_404(user_id)
489 if c.user.username == User.DEFAULT_USER:
490 h.flash(_("You can't edit this user"), category='warning')
491 return redirect(url('users'))
492
493 lifetime = safe_int(request.POST.get('lifetime'), -1)
494 description = request.POST.get('description')
495 role = request.POST.get('role')
496 AuthTokenModel().create(c.user.user_id, description, lifetime, role)
497 Session().commit()
498 h.flash(_("Auth token successfully created"), category='success')
499 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
500
501 @HasPermissionAllDecorator('hg.admin')
502 @auth.CSRFRequired()
503 def delete_auth_token(self, user_id):
504 user_id = safe_int(user_id)
505 c.user = User.get_or_404(user_id)
506 if c.user.username == User.DEFAULT_USER:
507 h.flash(_("You can't edit this user"), category='warning')
508 return redirect(url('users'))
509
510 auth_token = request.POST.get('del_auth_token')
511 if request.POST.get('del_auth_token_builtin'):
512 user = User.get(c.user.user_id)
513 if user:
514 user.api_key = generate_auth_token(user.username)
515 Session().add(user)
516 Session().commit()
517 h.flash(_("Auth token successfully reset"), category='success')
518 elif auth_token:
519 AuthTokenModel().delete(auth_token, c.user.user_id)
520 Session().commit()
521 h.flash(_("Auth token successfully deleted"), category='success')
522
523 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
524
525 @HasPermissionAllDecorator('hg.admin')
526 def edit_global_perms(self, user_id):
408 def edit_global_perms(self, user_id):
527 user_id = safe_int(user_id)
409 user_id = safe_int(user_id)
528 c.user = User.get_or_404(user_id)
410 c.user = User.get_or_404(user_id)
529 if c.user.username == User.DEFAULT_USER:
411 if c.user.username == User.DEFAULT_USER:
530 h.flash(_("You can't edit this user"), category='warning')
412 h.flash(_("You can't edit this user"), category='warning')
531 return redirect(url('users'))
413 return redirect(h.route_path('users'))
532
414
533 c.active = 'global_perms'
415 c.active = 'global_perms'
534
416
@@ -602,7 +484,7 b' class UsersController(BaseController):'
602 c.user = User.get_or_404(user_id)
484 c.user = User.get_or_404(user_id)
603 if c.user.username == User.DEFAULT_USER:
485 if c.user.username == User.DEFAULT_USER:
604 h.flash(_("You can't edit this user"), category='warning')
486 h.flash(_("You can't edit this user"), category='warning')
605 return redirect(url('users'))
487 return redirect(h.route_path('users'))
606
488
607 c.active = 'perms_summary'
489 c.active = 'perms_summary'
608 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
490 c.perm_user = AuthUser(user_id=user_id, ip_addr=self.ip_addr)
@@ -615,7 +497,7 b' class UsersController(BaseController):'
615 c.user = User.get_or_404(user_id)
497 c.user = User.get_or_404(user_id)
616 if c.user.username == User.DEFAULT_USER:
498 if c.user.username == User.DEFAULT_USER:
617 h.flash(_("You can't edit this user"), category='warning')
499 h.flash(_("You can't edit this user"), category='warning')
618 return redirect(url('users'))
500 return redirect(h.route_path('users'))
619
501
620 c.active = 'emails'
502 c.active = 'emails'
621 c.user_email_map = UserEmailMap.query() \
503 c.user_email_map = UserEmailMap.query() \
@@ -673,7 +555,7 b' class UsersController(BaseController):'
673 c.user = User.get_or_404(user_id)
555 c.user = User.get_or_404(user_id)
674 if c.user.username == User.DEFAULT_USER:
556 if c.user.username == User.DEFAULT_USER:
675 h.flash(_("You can't edit this user"), category='warning')
557 h.flash(_("You can't edit this user"), category='warning')
676 return redirect(url('users'))
558 return redirect(h.route_path('users'))
677
559
678 c.active = 'ips'
560 c.active = 'ips'
679 c.user_ip_map = UserIpMap.query() \
561 c.user_ip_map = UserIpMap.query() \
@@ -28,12 +28,11 b' import pytz'
28 from pylons import url, response, tmpl_context as c
28 from pylons import url, response, tmpl_context as c
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30
30
31 from beaker.cache import cache_region, region_invalidate
31 from beaker.cache import cache_region
32 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
32 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
33
33
34 from rhodecode.model.db import CacheKey
34 from rhodecode.model.db import CacheKey, UserApiKeys
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib import caches
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 from rhodecode.lib.base import BaseRepoController
37 from rhodecode.lib.base import BaseRepoController
39 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
38 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
@@ -62,7 +61,7 b' class FeedController(BaseRepoController)'
62 safe_int(config.get('rss_cut_off_limit', 32 * 1024)),
61 safe_int(config.get('rss_cut_off_limit', 32 * 1024)),
63 }
62 }
64
63
65 @LoginRequired(auth_token_access=True)
64 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
66 def __before__(self):
65 def __before__(self):
67 super(FeedController, self).__before__()
66 super(FeedController, self).__before__()
68 config = self._get_config()
67 config = self._get_config()
@@ -223,6 +223,8 b' class FilesController(BaseRepoController'
223 c.file_author = True
223 c.file_author = True
224 c.file_tree = ''
224 c.file_tree = ''
225 if c.file.is_file():
225 if c.file.is_file():
226 c.lf_node = c.file.get_largefile_node()
227
226 c.file_source_page = 'true'
228 c.file_source_page = 'true'
227 c.file_last_commit = c.file.last_commit
229 c.file_last_commit = c.file.last_commit
228 if c.file.size < self.cut_off_limit_file:
230 if c.file.size < self.cut_off_limit_file:
@@ -239,7 +241,11 b' class FilesController(BaseRepoController'
239
241
240 c.on_branch_head = self._is_valid_head(
242 c.on_branch_head = self._is_valid_head(
241 commit_id, c.rhodecode_repo)
243 commit_id, c.rhodecode_repo)
242 c.branch_or_raw_id = c.commit.branch or c.commit.raw_id
244
245 branch = c.commit.branch if (
246 c.commit.branch and '/' not in c.commit.branch) else None
247 c.branch_or_raw_id = branch or c.commit.raw_id
248 c.branch_name = c.commit.branch or h.short_id(c.commit.raw_id)
243
249
244 author = c.file_last_commit.author
250 author = c.file_last_commit.author
245 c.authors = [(h.email(author),
251 c.authors = [(h.email(author),
@@ -260,6 +266,32 b' class FilesController(BaseRepoController'
260 return render('files/files.mako')
266 return render('files/files.mako')
261
267
262 @LoginRequired()
268 @LoginRequired()
269 @HasRepoPermissionAnyDecorator(
270 'repository.read', 'repository.write', 'repository.admin')
271 def annotate_previous(self, repo_name, revision, f_path):
272
273 commit_id = revision
274 commit = self.__get_commit_or_redirect(commit_id, repo_name)
275 prev_commit_id = commit.raw_id
276
277 f_path = f_path
278 is_file = False
279 try:
280 _file = commit.get_node(f_path)
281 is_file = _file.is_file()
282 except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError):
283 pass
284
285 if is_file:
286 history = commit.get_file_history(f_path)
287 prev_commit_id = history[1].raw_id \
288 if len(history) > 1 else prev_commit_id
289
290 return redirect(h.url(
291 'files_annotate_home', repo_name=repo_name,
292 revision=prev_commit_id, f_path=f_path))
293
294 @LoginRequired()
263 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
295 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
264 'repository.admin')
296 'repository.admin')
265 @jsonify
297 @jsonify
@@ -317,6 +349,14 b' class FilesController(BaseRepoController'
317 commit = self.__get_commit_or_redirect(revision, repo_name)
349 commit = self.__get_commit_or_redirect(revision, repo_name)
318 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
350 file_node = self.__get_filenode_or_redirect(repo_name, commit, f_path)
319
351
352 if request.GET.get('lf'):
353 # only if lf get flag is passed, we download this file
354 # as LFS/Largefile
355 lf_node = file_node.get_largefile_node()
356 if lf_node:
357 # overwrite our pointer with the REAL large-file
358 file_node = lf_node
359
320 response.content_disposition = 'attachment; filename=%s' % \
360 response.content_disposition = 'attachment; filename=%s' % \
321 safe_str(f_path.split(Repository.NAME_SEP)[-1])
361 safe_str(f_path.split(Repository.NAME_SEP)[-1])
322
362
@@ -352,6 +392,7 b' class FilesController(BaseRepoController'
352 'image/png': ('image/png', 'inline'),
392 'image/png': ('image/png', 'inline'),
353 'image/gif': ('image/gif', 'inline'),
393 'image/gif': ('image/gif', 'inline'),
354 'image/jpeg': ('image/jpeg', 'inline'),
394 'image/jpeg': ('image/jpeg', 'inline'),
395 'application/pdf': ('application/pdf', 'inline'),
355 }
396 }
356
397
357 mimetype = file_node.mimetype
398 mimetype = file_node.mimetype
@@ -34,8 +34,8 b' from webob.exc import HTTPBadRequest'
34 from pylons import request, tmpl_context as c, response, url
34 from pylons import request, tmpl_context as c, response, url
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.controllers.admin.admin import _journal_filter
37 from rhodecode.controllers.admin.admin import user_log_filter
38 from rhodecode.model.db import UserLog, UserFollowing, User
38 from rhodecode.model.db import UserLog, UserFollowing, User, UserApiKeys
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 import rhodecode.lib.helpers as h
40 import rhodecode.lib.helpers as h
41 from rhodecode.lib.helpers import Page
41 from rhodecode.lib.helpers import Page
@@ -89,7 +89,7 b' class JournalController(BaseController):'
89 .options(joinedload(UserLog.repository))
89 .options(joinedload(UserLog.repository))
90 #filter
90 #filter
91 try:
91 try:
92 journal = _journal_filter(journal, c.search_term)
92 journal = user_log_filter(journal, c.search_term)
93 except Exception:
93 except Exception:
94 # we want this to crash for now
94 # we want this to crash for now
95 raise
95 raise
@@ -211,7 +211,7 b' class JournalController(BaseController):'
211
211
212 return render('journal/journal.mako')
212 return render('journal/journal.mako')
213
213
214 @LoginRequired(auth_token_access=True)
214 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
215 @NotAnonymous()
215 @NotAnonymous()
216 def journal_atom(self):
216 def journal_atom(self):
217 """
217 """
@@ -223,7 +223,7 b' class JournalController(BaseController):'
223 .all()
223 .all()
224 return self._atom_feed(following, public=False)
224 return self._atom_feed(following, public=False)
225
225
226 @LoginRequired(auth_token_access=True)
226 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
227 @NotAnonymous()
227 @NotAnonymous()
228 def journal_rss(self):
228 def journal_rss(self):
229 """
229 """
@@ -281,7 +281,7 b' class JournalController(BaseController):'
281 return c.journal_data
281 return c.journal_data
282 return render('journal/public_journal.mako')
282 return render('journal/public_journal.mako')
283
283
284 @LoginRequired(auth_token_access=True)
284 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
285 def public_journal_atom(self):
285 def public_journal_atom(self):
286 """
286 """
287 Produce an atom-1.0 feed via feedgenerator module
287 Produce an atom-1.0 feed via feedgenerator module
@@ -293,7 +293,7 b' class JournalController(BaseController):'
293
293
294 return self._atom_feed(c.following)
294 return self._atom_feed(c.following)
295
295
296 @LoginRequired(auth_token_access=True)
296 @LoginRequired(auth_token_access=[UserApiKeys.ROLE_FEED])
297 def public_journal_rss(self):
297 def public_journal_rss(self):
298 """
298 """
299 Produce an rss2 feed via feedgenerator module
299 Produce an rss2 feed via feedgenerator module
@@ -69,6 +69,8 b' class PullrequestsController(BaseRepoCon'
69
69
70 def __before__(self):
70 def __before__(self):
71 super(PullrequestsController, self).__before__()
71 super(PullrequestsController, self).__before__()
72 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
73 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
72
74
73 def _extract_ordering(self, request):
75 def _extract_ordering(self, request):
74 column_index = safe_int(request.GET.get('order[0][column]'))
76 column_index = safe_int(request.GET.get('order[0][column]'))
@@ -440,13 +442,25 b' class PullrequestsController(BaseRepoCon'
440 resp = PullRequestModel().update_commits(pull_request)
442 resp = PullRequestModel().update_commits(pull_request)
441
443
442 if resp.executed:
444 if resp.executed:
445
446 if resp.target_changed and resp.source_changed:
447 changed = 'target and source repositories'
448 elif resp.target_changed and not resp.source_changed:
449 changed = 'target repository'
450 elif not resp.target_changed and resp.source_changed:
451 changed = 'source repository'
452 else:
453 changed = 'nothing'
454
443 msg = _(
455 msg = _(
444 u'Pull request updated to "{source_commit_id}" with '
456 u'Pull request updated to "{source_commit_id}" with '
445 u'{count_added} added, {count_removed} removed commits.')
457 u'{count_added} added, {count_removed} removed commits. '
458 u'Source of changes: {change_source}')
446 msg = msg.format(
459 msg = msg.format(
447 source_commit_id=pull_request.source_ref_parts.commit_id,
460 source_commit_id=pull_request.source_ref_parts.commit_id,
448 count_added=len(resp.changes.added),
461 count_added=len(resp.changes.added),
449 count_removed=len(resp.changes.removed))
462 count_removed=len(resp.changes.removed),
463 change_source=changed)
450 h.flash(msg, category='success')
464 h.flash(msg, category='success')
451
465
452 registry = get_current_registry()
466 registry = get_current_registry()
@@ -562,13 +576,21 b' class PullrequestsController(BaseRepoCon'
562 def delete(self, repo_name, pull_request_id):
576 def delete(self, repo_name, pull_request_id):
563 pull_request_id = safe_int(pull_request_id)
577 pull_request_id = safe_int(pull_request_id)
564 pull_request = PullRequest.get_or_404(pull_request_id)
578 pull_request = PullRequest.get_or_404(pull_request_id)
579
580 pr_closed = pull_request.is_closed()
581 allowed_to_delete = PullRequestModel().check_user_delete(
582 pull_request, c.rhodecode_user) and not pr_closed
583
565 # only owner can delete it !
584 # only owner can delete it !
566 if pull_request.author.user_id == c.rhodecode_user.user_id:
585 if allowed_to_delete:
567 PullRequestModel().delete(pull_request)
586 PullRequestModel().delete(pull_request)
568 Session().commit()
587 Session().commit()
569 h.flash(_('Successfully deleted pull request'),
588 h.flash(_('Successfully deleted pull request'),
570 category='success')
589 category='success')
571 return redirect(url('my_account_pullrequests'))
590 return redirect(url('my_account_pullrequests'))
591
592 h.flash(_('Your are not allowed to delete this pull request'),
593 category='error')
572 raise HTTPForbidden()
594 raise HTTPForbidden()
573
595
574 def _get_pr_version(self, pull_request_id, version=None):
596 def _get_pr_version(self, pull_request_id, version=None):
@@ -642,6 +664,13 b' class PullrequestsController(BaseRepoCon'
642 pull_request_display_obj,
664 pull_request_display_obj,
643 at_version) = self._get_pr_version(
665 at_version) = self._get_pr_version(
644 pull_request_id, version=version)
666 pull_request_id, version=version)
667 pr_closed = pull_request_latest.is_closed()
668
669 if pr_closed and (version or from_version):
670 # not allow to browse versions
671 return redirect(h.url('pullrequest_show', repo_name=repo_name,
672 pull_request_id=pull_request_id))
673
645 versions = pull_request_display_obj.versions()
674 versions = pull_request_display_obj.versions()
646
675
647 c.at_version = at_version
676 c.at_version = at_version
@@ -675,20 +704,21 b' class PullrequestsController(BaseRepoCon'
675 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
704 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
676 pull_request_at_ver)
705 pull_request_at_ver)
677
706
678 c.ancestor = None # empty ancestor hidden in display
679 c.pull_request = pull_request_display_obj
707 c.pull_request = pull_request_display_obj
680 c.pull_request_latest = pull_request_latest
708 c.pull_request_latest = pull_request_latest
681
709
682 pr_closed = pull_request_latest.is_closed()
683 if compare or (at_version and not at_version == 'latest'):
710 if compare or (at_version and not at_version == 'latest'):
684 c.allowed_to_change_status = False
711 c.allowed_to_change_status = False
685 c.allowed_to_update = False
712 c.allowed_to_update = False
686 c.allowed_to_merge = False
713 c.allowed_to_merge = False
687 c.allowed_to_delete = False
714 c.allowed_to_delete = False
688 c.allowed_to_comment = False
715 c.allowed_to_comment = False
716 c.allowed_to_close = False
689 else:
717 else:
690 c.allowed_to_change_status = PullRequestModel(). \
718 c.allowed_to_change_status = PullRequestModel(). \
691 check_user_change_status(pull_request_at_ver, c.rhodecode_user)
719 check_user_change_status(pull_request_at_ver, c.rhodecode_user) \
720 and not pr_closed
721
692 c.allowed_to_update = PullRequestModel().check_user_update(
722 c.allowed_to_update = PullRequestModel().check_user_update(
693 pull_request_latest, c.rhodecode_user) and not pr_closed
723 pull_request_latest, c.rhodecode_user) and not pr_closed
694 c.allowed_to_merge = PullRequestModel().check_user_merge(
724 c.allowed_to_merge = PullRequestModel().check_user_merge(
@@ -696,6 +726,7 b' class PullrequestsController(BaseRepoCon'
696 c.allowed_to_delete = PullRequestModel().check_user_delete(
726 c.allowed_to_delete = PullRequestModel().check_user_delete(
697 pull_request_latest, c.rhodecode_user) and not pr_closed
727 pull_request_latest, c.rhodecode_user) and not pr_closed
698 c.allowed_to_comment = not pr_closed
728 c.allowed_to_comment = not pr_closed
729 c.allowed_to_close = c.allowed_to_change_status and not pr_closed
699
730
700 # check merge capabilities
731 # check merge capabilities
701 _merge_check = MergeCheck.validate(
732 _merge_check = MergeCheck.validate(
@@ -704,6 +735,7 b' class PullrequestsController(BaseRepoCon'
704 c.pr_merge_possible = not _merge_check.failed
735 c.pr_merge_possible = not _merge_check.failed
705 c.pr_merge_message = _merge_check.merge_msg
736 c.pr_merge_message = _merge_check.merge_msg
706
737
738 c.pull_request_review_status = _merge_check.review_status
707 if merge_checks:
739 if merge_checks:
708 return render('/pullrequests/pullrequest_merge_checks.mako')
740 return render('/pullrequests/pullrequest_merge_checks.mako')
709
741
@@ -712,7 +744,6 b' class PullrequestsController(BaseRepoCon'
712 # reviewers and statuses
744 # reviewers and statuses
713 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
745 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
714 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
746 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
715 c.pull_request_review_status = pull_request_at_ver.calculated_review_status()
716
747
717 # GENERAL COMMENTS with versions #
748 # GENERAL COMMENTS with versions #
718 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
749 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
@@ -789,12 +820,15 b' class PullrequestsController(BaseRepoCon'
789 target_commit = EmptyCommit()
820 target_commit = EmptyCommit()
790 c.missing_requirements = False
821 c.missing_requirements = False
791
822
823 source_scm = source_repo.scm_instance()
824 target_scm = target_repo.scm_instance()
825
792 # try first shadow repo, fallback to regular repo
826 # try first shadow repo, fallback to regular repo
793 try:
827 try:
794 commits_source_repo = pull_request_latest.get_shadow_repo()
828 commits_source_repo = pull_request_latest.get_shadow_repo()
795 except Exception:
829 except Exception:
796 log.debug('Failed to get shadow repo', exc_info=True)
830 log.debug('Failed to get shadow repo', exc_info=True)
797 commits_source_repo = source_repo.scm_instance()
831 commits_source_repo = source_scm
798
832
799 c.commits_source_repo = commits_source_repo
833 c.commits_source_repo = commits_source_repo
800 commit_cache = {}
834 commit_cache = {}
@@ -818,6 +852,15 b' class PullrequestsController(BaseRepoCon'
818 'Failed to get all required data from repo', exc_info=True)
852 'Failed to get all required data from repo', exc_info=True)
819 c.missing_requirements = True
853 c.missing_requirements = True
820
854
855 c.ancestor = None # set it to None, to hide it from PR view
856
857 try:
858 ancestor_id = source_scm.get_common_ancestor(
859 source_commit.raw_id, target_commit.raw_id, target_scm)
860 c.ancestor_commit = source_scm.get_commit(ancestor_id)
861 except Exception:
862 c.ancestor_commit = None
863
821 c.statuses = source_repo.statuses(
864 c.statuses = source_repo.statuses(
822 [x.raw_id for x in c.commit_ranges])
865 [x.raw_id for x in c.commit_ranges])
823
866
@@ -860,12 +903,7 b' class PullrequestsController(BaseRepoCon'
860 # We need to swap that here to generate it properly on the html side
903 # We need to swap that here to generate it properly on the html side
861 c.target_repo = c.source_repo
904 c.target_repo = c.source_repo
862
905
863 if c.allowed_to_update:
906 c.commit_statuses = ChangesetStatus.STATUSES
864 force_close = ('forced_closed', _('Close Pull Request'))
865 statuses = ChangesetStatus.STATUSES + [force_close]
866 else:
867 statuses = ChangesetStatus.STATUSES
868 c.commit_statuses = statuses
869
907
870 c.show_version_changes = not pr_closed
908 c.show_version_changes = not pr_closed
871 if c.show_version_changes:
909 if c.show_version_changes:
@@ -924,22 +962,21 b' class PullrequestsController(BaseRepoCon'
924 if pull_request.is_closed():
962 if pull_request.is_closed():
925 raise HTTPForbidden()
963 raise HTTPForbidden()
926
964
927 # TODO: johbo: Re-think this bit, "approved_closed" does not exist
928 # as a changeset status, still we want to send it in one value.
929 status = request.POST.get('changeset_status', None)
965 status = request.POST.get('changeset_status', None)
930 text = request.POST.get('text')
966 text = request.POST.get('text')
931 comment_type = request.POST.get('comment_type')
967 comment_type = request.POST.get('comment_type')
932 resolves_comment_id = request.POST.get('resolves_comment_id', None)
968 resolves_comment_id = request.POST.get('resolves_comment_id', None)
969 close_pull_request = request.POST.get('close_pull_request')
933
970
934 if status and '_closed' in status:
971 close_pr = False
972 if close_pull_request:
935 close_pr = True
973 close_pr = True
936 status = status.replace('_closed', '')
974 pull_request_review_status = pull_request.calculated_review_status()
937 else:
975 if pull_request_review_status == ChangesetStatus.STATUS_APPROVED:
938 close_pr = False
976 # approved only if we have voting consent
939
977 status = ChangesetStatus.STATUS_APPROVED
940 forced = (status == 'forced')
978 else:
941 if forced:
979 status = ChangesetStatus.STATUS_REJECTED
942 status = 'rejected'
943
980
944 allowed_to_change_status = PullRequestModel().check_user_change_status(
981 allowed_to_change_status = PullRequestModel().check_user_change_status(
945 pull_request, c.rhodecode_user)
982 pull_request, c.rhodecode_user)
@@ -995,7 +1032,7 b' class PullrequestsController(BaseRepoCon'
995 status_completed = (
1032 status_completed = (
996 calculated_status in [ChangesetStatus.STATUS_APPROVED,
1033 calculated_status in [ChangesetStatus.STATUS_APPROVED,
997 ChangesetStatus.STATUS_REJECTED])
1034 ChangesetStatus.STATUS_REJECTED])
998 if forced or status_completed:
1035 if close_pull_request or status_completed:
999 PullRequestModel().close_pull_request(
1036 PullRequestModel().close_pull_request(
1000 pull_request_id, c.rhodecode_user)
1037 pull_request_id, c.rhodecode_user)
1001 else:
1038 else:
@@ -38,7 +38,7 b' from rhodecode.lib.utils2 import safe_st'
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, XHRRequired)
39 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, XHRRequired)
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.lib.markup_renderer import MarkupRenderer
41 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
42 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.ext_json import json
43 from rhodecode.lib.vcs.backends.base import EmptyCommit
43 from rhodecode.lib.vcs.backends.base import EmptyCommit
44 from rhodecode.lib.vcs.exceptions import (
44 from rhodecode.lib.vcs.exceptions import (
@@ -70,7 +70,12 b' class SummaryController(BaseRepoControll'
70 log.debug("Searching for a README file.")
70 log.debug("Searching for a README file.")
71 readme_node = ReadmeFinder(default_renderer).search(commit)
71 readme_node = ReadmeFinder(default_renderer).search(commit)
72 if readme_node:
72 if readme_node:
73 readme_data = self._render_readme_or_none(commit, readme_node)
73 relative_url = h.url('files_raw_home',
74 repo_name=repo_name,
75 revision=commit.raw_id,
76 f_path=readme_node.path)
77 readme_data = self._render_readme_or_none(
78 commit, readme_node, relative_url)
74 readme_filename = readme_node.path
79 readme_filename = readme_node.path
75 return readme_data, readme_filename
80 return readme_data, readme_filename
76
81
@@ -95,13 +100,16 b' class SummaryController(BaseRepoControll'
95 log.exception(
100 log.exception(
96 "Problem getting commit when trying to render the README.")
101 "Problem getting commit when trying to render the README.")
97
102
98 def _render_readme_or_none(self, commit, readme_node):
103 def _render_readme_or_none(self, commit, readme_node, relative_url):
99 log.debug(
104 log.debug(
100 'Found README file `%s` rendering...', readme_node.path)
105 'Found README file `%s` rendering...', readme_node.path)
101 renderer = MarkupRenderer()
106 renderer = MarkupRenderer()
102 try:
107 try:
103 return renderer.render(
108 html_source = renderer.render(
104 readme_node.content, filename=readme_node.path)
109 readme_node.content, filename=readme_node.path)
110 if relative_url:
111 return relative_links(html_source, relative_url)
112 return html_source
105 except Exception:
113 except Exception:
106 log.exception(
114 log.exception(
107 "Exception while trying to render the README")
115 "Exception while trying to render the README")
@@ -56,8 +56,15 b' class RhodecodeEvent(object):'
56 @property
56 @property
57 def actor(self):
57 def actor(self):
58 auth_user = self.auth_user
58 auth_user = self.auth_user
59
59 if auth_user:
60 if auth_user:
60 return auth_user.get_instance()
61 instance = auth_user.get_instance()
62 if not instance:
63 return AttributeDict(dict(
64 username=auth_user.username
65 ))
66 return instance
67
61 return SYSTEM_USER
68 return SYSTEM_USER
62
69
63 @property
70 @property
@@ -128,6 +128,9 b' class PullRequestCommentEvent(PullReques'
128 'comment': {
128 'comment': {
129 'status': status,
129 'status': status,
130 'text': self.comment.text,
130 'text': self.comment.text,
131 'type': self.comment.comment_type,
132 'file': self.comment.f_path,
133 'line': self.comment.line_no,
131 'url': CommentsModel().get_url(self.comment)
134 'url': CommentsModel().get_url(self.comment)
132 }
135 }
133 })
136 })
@@ -34,10 +34,9 b' def _commits_as_dict(commit_ids, repos):'
34 :param repos: list of repos to check
34 :param repos: list of repos to check
35 """
35 """
36 from rhodecode.lib.utils2 import extract_mentioned_users
36 from rhodecode.lib.utils2 import extract_mentioned_users
37 from rhodecode.model.db import Repository
38 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
39 from rhodecode.lib.helpers import process_patterns
38 from rhodecode.lib.helpers import (
40 from rhodecode.lib.helpers import urlify_commit_message
39 urlify_commit_message, process_patterns, chop_at_smart)
41
40
42 if not repos:
41 if not repos:
43 raise Exception('no repo defined')
42 raise Exception('no repo defined')
@@ -78,14 +77,15 b' def _commits_as_dict(commit_ids, repos):'
78 cs_data['issues'] = issues_data
77 cs_data['issues'] = issues_data
79 cs_data['message_html'] = urlify_commit_message(cs_data['message'],
78 cs_data['message_html'] = urlify_commit_message(cs_data['message'],
80 repo.repo_name)
79 repo.repo_name)
80 cs_data['message_html_title'] = chop_at_smart(cs_data['message'], '\n', suffix_if_chopped='...')
81 commits.append(cs_data)
81 commits.append(cs_data)
82
82
83 needed_commits.remove(commit_id)
83 needed_commits.remove(commit_id)
84
84
85 except Exception as e:
85 except Exception as e:
86 log.exception(e)
86 log.exception(e)
87 # we don't send any commits when crash happens, only full list matters
87 # we don't send any commits when crash happens, only full list
88 # we short circuit then.
88 # matters we short circuit then.
89 return []
89 return []
90
90
91 missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
91 missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -19,12 +19,14 b''
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from __future__ import unicode_literals
21 from __future__ import unicode_literals
22 import re
23 import time
24 import textwrap
25 import logging
26
22 import deform
27 import deform
23 import re
24 import logging
25 import requests
28 import requests
26 import colander
29 import colander
27 import textwrap
28 from celery.task import task
30 from celery.task import task
29 from mako.template import Template
31 from mako.template import Template
30
32
@@ -85,17 +87,6 b' class SlackSettingsSchema(colander.Schem'
85 )
87 )
86
88
87
89
88 repo_push_template = Template(r'''
89 *${data['actor']['username']}* pushed to repo <${data['repo']['url']}|${data['repo']['repo_name']}>:
90 %for branch, branch_commits in branches_commits.items():
91 branch: <${branch_commits['branch']['url']}|${branch_commits['branch']['name']}>
92 %for commit in branch_commits['commits']:
93 > <${commit['url']}|${commit['short_id']}> - ${commit['message_html']|html_to_slack_links}
94 %endfor
95 %endfor
96 ''')
97
98
99 class SlackIntegrationType(IntegrationTypeBase):
90 class SlackIntegrationType(IntegrationTypeBase):
100 key = 'slack'
91 key = 'slack'
101 display_name = _('Slack')
92 display_name = _('Slack')
@@ -124,25 +115,31 b' class SlackIntegrationType(IntegrationTy'
124
115
125 data = event.as_dict()
116 data = event.as_dict()
126
117
118 # defaults
119 title = '*%s* caused a *%s* event' % (
120 data['actor']['username'], event.name)
127 text = '*%s* caused a *%s* event' % (
121 text = '*%s* caused a *%s* event' % (
128 data['actor']['username'], event.name)
122 data['actor']['username'], event.name)
123 fields = None
124 overrides = None
129
125
130 log.debug('handling slack event for %s' % event.name)
126 log.debug('handling slack event for %s' % event.name)
131
127
132 if isinstance(event, events.PullRequestCommentEvent):
128 if isinstance(event, events.PullRequestCommentEvent):
133 text = self.format_pull_request_comment_event(event, data)
129 (title, text, fields, overrides) \
130 = self.format_pull_request_comment_event(event, data)
134 elif isinstance(event, events.PullRequestReviewEvent):
131 elif isinstance(event, events.PullRequestReviewEvent):
135 text = self.format_pull_request_review_event(event, data)
132 title, text = self.format_pull_request_review_event(event, data)
136 elif isinstance(event, events.PullRequestEvent):
133 elif isinstance(event, events.PullRequestEvent):
137 text = self.format_pull_request_event(event, data)
134 title, text = self.format_pull_request_event(event, data)
138 elif isinstance(event, events.RepoPushEvent):
135 elif isinstance(event, events.RepoPushEvent):
139 text = self.format_repo_push_event(data)
136 title, text = self.format_repo_push_event(data)
140 elif isinstance(event, events.RepoCreateEvent):
137 elif isinstance(event, events.RepoCreateEvent):
141 text = self.format_repo_create_event(data)
138 title, text = self.format_repo_create_event(data)
142 else:
139 else:
143 log.error('unhandled event type: %r' % event)
140 log.error('unhandled event type: %r' % event)
144
141
145 run_task(post_text_to_slack, self.settings, text)
142 run_task(post_text_to_slack, self.settings, title, text, fields, overrides)
146
143
147 def settings_schema(self):
144 def settings_schema(self):
148 schema = SlackSettingsSchema()
145 schema = SlackSettingsSchema()
@@ -167,37 +164,60 b' class SlackIntegrationType(IntegrationTy'
167 comment_url=data['comment']['url'],
164 comment_url=data['comment']['url'],
168 )
165 )
169
166
170 comment_status = ''
167 fields = None
168 overrides = None
169 status_text = None
170
171 if data['comment']['status']:
171 if data['comment']['status']:
172 comment_status = '[{}]: '.format(data['comment']['status'])
172 status_color = {
173 'approved': '#0ac878',
174 'rejected': '#e85e4d'}.get(data['comment']['status'])
175
176 if status_color:
177 overrides = {"color": status_color}
178
179 status_text = data['comment']['status']
180
181 if data['comment']['file']:
182 fields = [
183 {
184 "title": "file",
185 "value": data['comment']['file']
186 },
187 {
188 "title": "line",
189 "value": data['comment']['line']
190 }
191 ]
173
192
174 return (textwrap.dedent(
193 title = Template(textwrap.dedent(r'''
175 '''
194 *${data['actor']['username']}* left ${data['comment']['type']} on pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>:
176 *{user}* commented on pull request <{pr_url}|#{number}> - {pr_title}:
195 ''')).render(data=data, comment=event.comment)
177 >>> {comment_status}{comment_text}
196
178 ''').format(
197 text = Template(textwrap.dedent(r'''
179 comment_status=comment_status,
198 *pull request title*: ${pr_title}
180 user=data['actor']['username'],
199 % if status_text:
181 number=data['pullrequest']['pull_request_id'],
200 *submitted status*: `${status_text}`
182 pr_url=data['pullrequest']['url'],
201 % endif
183 pr_status=data['pullrequest']['status'],
202 >>> ${comment_text}
184 pr_title=data['pullrequest']['title'],
203 ''')).render(comment_text=comment_text,
185 comment_text=comment_text
204 pr_title=data['pullrequest']['title'],
186 )
205 status_text=status_text)
206
207 return title, text, fields, overrides
208
209 def format_pull_request_review_event(self, event, data):
210 title = Template(textwrap.dedent(r'''
211 *${data['actor']['username']}* changed status of pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']} to `${data['pullrequest']['status']}`>:
212 ''')).render(data=data)
213
214 text = Template(textwrap.dedent(r'''
215 *pull request title*: ${pr_title}
216 ''')).render(
217 pr_title=data['pullrequest']['title'],
187 )
218 )
188
219
189 def format_pull_request_review_event(self, event, data):
220 return title, text
190 return (textwrap.dedent(
191 '''
192 Status changed to {pr_status} for pull request <{pr_url}|#{number}> - {pr_title}
193 ''').format(
194 user=data['actor']['username'],
195 number=data['pullrequest']['pull_request_id'],
196 pr_url=data['pullrequest']['url'],
197 pr_status=data['pullrequest']['status'],
198 pr_title=data['pullrequest']['title'],
199 )
200 )
201
221
202 def format_pull_request_event(self, event, data):
222 def format_pull_request_event(self, event, data):
203 action = {
223 action = {
@@ -207,15 +227,22 b' class SlackIntegrationType(IntegrationTy'
207 events.PullRequestCreateEvent: 'created',
227 events.PullRequestCreateEvent: 'created',
208 }.get(event.__class__, str(event.__class__))
228 }.get(event.__class__, str(event.__class__))
209
229
210 return ('Pull request <{url}|#{number}> - {title} '
230 title = Template(textwrap.dedent(r'''
211 '`{action}` by *{user}*').format(
231 *${data['actor']['username']}* `${action}` pull request <${data['pullrequest']['url']}|#${data['pullrequest']['pull_request_id']}>:
212 user=data['actor']['username'],
232 ''')).render(data=data, action=action)
213 number=data['pullrequest']['pull_request_id'],
233
214 url=data['pullrequest']['url'],
234 text = Template(textwrap.dedent(r'''
215 title=data['pullrequest']['title'],
235 *pull request title*: ${pr_title}
216 action=action
236 %if data['pullrequest']['commits']:
237 *commits*: ${len(data['pullrequest']['commits'])}
238 %endif
239 ''')).render(
240 pr_title=data['pullrequest']['title'],
241 data=data
217 )
242 )
218
243
244 return title, text
245
219 def format_repo_push_event(self, data):
246 def format_repo_push_event(self, data):
220 branch_data = {branch['name']: branch
247 branch_data = {branch['name']: branch
221 for branch in data['push']['branches']}
248 for branch in data['push']['branches']}
@@ -230,20 +257,38 b' class SlackIntegrationType(IntegrationTy'
230 branch_commits = branches_commits[commit['branch']]
257 branch_commits = branches_commits[commit['branch']]
231 branch_commits['commits'].append(commit)
258 branch_commits['commits'].append(commit)
232
259
233 result = repo_push_template.render(
260 title = Template(r'''
261 *${data['actor']['username']}* pushed to repo <${data['repo']['url']}|${data['repo']['repo_name']}>:
262 ''').render(data=data)
263
264 repo_push_template = Template(textwrap.dedent(r'''
265 %for branch, branch_commits in branches_commits.items():
266 ${len(branch_commits['commits'])} ${'commit' if len(branch_commits['commits']) == 1 else 'commits'} on branch: <${branch_commits['branch']['url']}|${branch_commits['branch']['name']}>
267 %for commit in branch_commits['commits']:
268 `<${commit['url']}|${commit['short_id']}>` - ${commit['message_html_title']|html_to_slack_links}
269 %endfor
270 %endfor
271 '''))
272
273 text = repo_push_template.render(
234 data=data,
274 data=data,
235 branches_commits=branches_commits,
275 branches_commits=branches_commits,
236 html_to_slack_links=html_to_slack_links,
276 html_to_slack_links=html_to_slack_links,
237 )
277 )
238 return result
278
279 return title, text
239
280
240 def format_repo_create_event(self, data):
281 def format_repo_create_event(self, data):
241 return '<{}|{}> ({}) repository created by *{}*'.format(
282 title = Template(r'''
242 data['repo']['url'],
283 *${data['actor']['username']}* created new repository ${data['repo']['repo_name']}:
243 data['repo']['repo_name'],
284 ''').render(data=data)
244 data['repo']['repo_type'],
285
245 data['actor']['username'],
286 text = Template(textwrap.dedent(r'''
246 )
287 repo_url: ${data['repo']['url']}
288 repo_type: ${data['repo']['repo_type']}
289 ''')).render(data=data)
290
291 return title, text
247
292
248
293
249 def html_to_slack_links(message):
294 def html_to_slack_links(message):
@@ -252,12 +297,38 b' def html_to_slack_links(message):'
252
297
253
298
254 @task(ignore_result=True)
299 @task(ignore_result=True)
255 def post_text_to_slack(settings, text):
300 def post_text_to_slack(settings, title, text, fields=None, overrides=None):
256 log.debug('sending %s to slack %s' % (text, settings['service']))
301 log.debug('sending %s (%s) to slack %s' % (
257 resp = requests.post(settings['service'], json={
302 title, text, settings['service']))
303
304 fields = fields or []
305 overrides = overrides or {}
306
307 message_data = {
308 "fallback": text,
309 "color": "#427cc9",
310 "pretext": title,
311 #"author_name": "Bobby Tables",
312 #"author_link": "http://flickr.com/bobby/",
313 #"author_icon": "http://flickr.com/icons/bobby.jpg",
314 #"title": "Slack API Documentation",
315 #"title_link": "https://api.slack.com/",
316 "text": text,
317 "fields": fields,
318 #"image_url": "http://my-website.com/path/to/image.jpg",
319 #"thumb_url": "http://example.com/path/to/thumb.png",
320 "footer": "RhodeCode",
321 #"footer_icon": "",
322 "ts": time.time(),
323 "mrkdwn_in": ["pretext", "text"]
324 }
325 message_data.update(overrides)
326 json_message = {
327 "icon_emoji": settings.get('icon_emoji', ':studio_microphone:'),
258 "channel": settings.get('channel', ''),
328 "channel": settings.get('channel', ''),
259 "username": settings.get('username', 'Rhodecode'),
329 "username": settings.get('username', 'Rhodecode'),
260 "text": text,
330 "attachments": [message_data]
261 "icon_emoji": settings.get('icon_emoji', ':studio_microphone:')
331 }
262 })
332
333 resp = requests.post(settings['service'], json=json_message)
263 resp.raise_for_status() # raise exception on a failed request
334 resp.raise_for_status() # raise exception on a failed request
@@ -29,6 +29,7 b' from pyramid.httpexceptions import HTTPF'
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30 from pyramid.response import Response
30 from pyramid.response import Response
31
31
32 from rhodecode.apps.admin.navigation import navigation_list
32 from rhodecode.lib import auth
33 from rhodecode.lib import auth
33 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
34 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
34 from rhodecode.lib.utils2 import safe_int
35 from rhodecode.lib.utils2 import safe_int
@@ -36,7 +37,6 b' from rhodecode.lib.helpers import Page'
36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.integration import IntegrationModel
39 from rhodecode.model.integration import IntegrationModel
39 from rhodecode.admin.navigation import navigation_list
40 from rhodecode.translation import _
40 from rhodecode.translation import _
41 from rhodecode.integrations import integration_type_registry
41 from rhodecode.integrations import integration_type_registry
42 from rhodecode.model.validation_schema.schemas.integration_schema import (
42 from rhodecode.model.validation_schema.schemas.integration_schema import (
@@ -75,11 +75,10 b' class IntegrationSettingsViewBase(object'
75 repo_name = request.matchdict['repo_name']
75 repo_name = request.matchdict['repo_name']
76 self.repo = Repository.get_by_repo_name(repo_name)
76 self.repo = Repository.get_by_repo_name(repo_name)
77
77
78 if 'repo_group_name' in request.matchdict: # in group settings context
78 if 'repo_group_name' in request.matchdict: # in group settings context
79 repo_group_name = request.matchdict['repo_group_name']
79 repo_group_name = request.matchdict['repo_group_name']
80 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
80 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
81
81
82
83 if 'integration' in request.matchdict: # integration type context
82 if 'integration' in request.matchdict: # integration type context
84 integration_type = request.matchdict['integration']
83 integration_type = request.matchdict['integration']
85 self.IntegrationType = integration_type_registry[integration_type]
84 self.IntegrationType = integration_type_registry[integration_type]
@@ -380,12 +379,12 b' class GlobalIntegrationsView(Integration'
380
379
381 class RepoIntegrationsView(IntegrationSettingsViewBase):
380 class RepoIntegrationsView(IntegrationSettingsViewBase):
382 def perm_check(self, user):
381 def perm_check(self, user):
383 return auth.HasRepoPermissionAll('repository.admin'
382 return auth.HasRepoPermissionAll('repository.admin')(
384 )(repo_name=self.repo.repo_name, user=user)
383 repo_name=self.repo.repo_name, user=user)
385
384
386
385
387 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
386 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
388 def perm_check(self, user):
387 def perm_check(self, user):
389 return auth.HasRepoGroupPermissionAll('group.admin'
388 return auth.HasRepoGroupPermissionAll('group.admin')(
390 )(group_name=self.repo_group.group_name, user=user)
389 group_name=self.repo_group.group_name, user=user)
391
390
@@ -22,24 +22,22 b''
22 authentication and permission libraries
22 authentication and permission libraries
23 """
23 """
24
24
25 import os
25 import inspect
26 import inspect
26 import collections
27 import collections
27 import fnmatch
28 import fnmatch
28 import hashlib
29 import hashlib
29 import itertools
30 import itertools
30 import logging
31 import logging
31 import os
32 import random
32 import random
33 import time
34 import traceback
33 import traceback
35 from functools import wraps
34 from functools import wraps
36
35
37 import ipaddress
36 import ipaddress
38 from pyramid.httpexceptions import HTTPForbidden
37 from pyramid.httpexceptions import HTTPForbidden, HTTPFound
39 from pylons import url, request
38 from pylons import url, request
40 from pylons.controllers.util import abort, redirect
39 from pylons.controllers.util import abort, redirect
41 from pylons.i18n.translation import _
40 from pylons.i18n.translation import _
42 from sqlalchemy import or_
43 from sqlalchemy.orm.exc import ObjectDeletedError
41 from sqlalchemy.orm.exc import ObjectDeletedError
44 from sqlalchemy.orm import joinedload
42 from sqlalchemy.orm import joinedload
45 from zope.cachedescriptors.property import Lazy as LazyProperty
43 from zope.cachedescriptors.property import Lazy as LazyProperty
@@ -99,6 +97,7 b' class PasswordGenerator(object):'
99
97
100
98
101 class _RhodeCodeCryptoBase(object):
99 class _RhodeCodeCryptoBase(object):
100 ENC_PREF = None
102
101
103 def hash_create(self, str_):
102 def hash_create(self, str_):
104 """
103 """
@@ -139,6 +138,7 b' class _RhodeCodeCryptoBase(object):'
139
138
140
139
141 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
140 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
141 ENC_PREF = '$2a$10'
142
142
143 def hash_create(self, str_):
143 def hash_create(self, str_):
144 self._assert_bytes(str_)
144 self._assert_bytes(str_)
@@ -194,6 +194,7 b' class _RhodeCodeCryptoBCrypt(_RhodeCodeC'
194
194
195
195
196 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
196 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
197 ENC_PREF = '_'
197
198
198 def hash_create(self, str_):
199 def hash_create(self, str_):
199 self._assert_bytes(str_)
200 self._assert_bytes(str_)
@@ -211,6 +212,7 b' class _RhodeCodeCryptoSha256(_RhodeCodeC'
211
212
212
213
213 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
214 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
215 ENC_PREF = '_'
214
216
215 def hash_create(self, str_):
217 def hash_create(self, str_):
216 self._assert_bytes(str_)
218 self._assert_bytes(str_)
@@ -567,8 +569,14 b' class PermissionCalculator(object):'
567 # on given user group
569 # on given user group
568 for perm in self.default_user_group_perms:
570 for perm in self.default_user_group_perms:
569 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
571 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
570 p = perm.Permission.permission_name
571 o = PermOrigin.USERGROUP_DEFAULT
572 o = PermOrigin.USERGROUP_DEFAULT
573 if perm.UserGroup.user_id == self.user_id:
574 # set admin if owner
575 p = 'usergroup.admin'
576 o = PermOrigin.USERGROUP_OWNER
577 else:
578 p = perm.Permission.permission_name
579
572 # if we decide this user isn't inheriting permissions from default
580 # if we decide this user isn't inheriting permissions from default
573 # user we set him to .none so only explicit permissions work
581 # user we set him to .none so only explicit permissions work
574 if not user_inherit_object_permissions:
582 if not user_inherit_object_permissions:
@@ -647,7 +655,7 b' class PermissionCalculator(object):'
647 multiple_counter[g_k] += 1
655 multiple_counter[g_k] += 1
648 p = perm.Permission.permission_name
656 p = perm.Permission.permission_name
649 if perm.RepoGroup.user_id == self.user_id:
657 if perm.RepoGroup.user_id == self.user_id:
650 # set admin if owner
658 # set admin if owner, even for member of other user group
651 p = 'group.admin'
659 p = 'group.admin'
652 o = PermOrigin.REPOGROUP_OWNER
660 o = PermOrigin.REPOGROUP_OWNER
653 else:
661 else:
@@ -683,7 +691,7 b' class PermissionCalculator(object):'
683 # user group for user group permissions
691 # user group for user group permissions
684 user_group_from_user_group = Permission\
692 user_group_from_user_group = Permission\
685 .get_default_user_group_perms_from_user_group(
693 .get_default_user_group_perms_from_user_group(
686 self.user_id, self.scope_repo_group_id)
694 self.user_id, self.scope_user_group_id)
687
695
688 multiple_counter = collections.defaultdict(int)
696 multiple_counter = collections.defaultdict(int)
689 for perm in user_group_from_user_group:
697 for perm in user_group_from_user_group:
@@ -694,9 +702,15 b' class PermissionCalculator(object):'
694 o = PermOrigin.USERGROUP_USERGROUP % u_k
702 o = PermOrigin.USERGROUP_USERGROUP % u_k
695 multiple_counter[g_k] += 1
703 multiple_counter[g_k] += 1
696 p = perm.Permission.permission_name
704 p = perm.Permission.permission_name
697 if multiple_counter[g_k] > 1:
705
698 cur_perm = self.permissions_user_groups[g_k]
706 if perm.UserGroup.user_id == self.user_id:
699 p = self._choose_permission(p, cur_perm)
707 # set admin if owner, even for member of other user group
708 p = 'usergroup.admin'
709 o = PermOrigin.USERGROUP_OWNER
710 else:
711 if multiple_counter[g_k] > 1:
712 cur_perm = self.permissions_user_groups[g_k]
713 p = self._choose_permission(p, cur_perm)
700 self.permissions_user_groups[g_k] = p, o
714 self.permissions_user_groups[g_k] = p, o
701
715
702 # user explicit permission for user groups
716 # user explicit permission for user groups
@@ -705,12 +719,18 b' class PermissionCalculator(object):'
705 for perm in user_user_groups_perms:
719 for perm in user_user_groups_perms:
706 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
720 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
707 u_k = perm.UserUserGroupToPerm.user.username
721 u_k = perm.UserUserGroupToPerm.user.username
708 p = perm.Permission.permission_name
709 o = PermOrigin.USERGROUP_USER % u_k
722 o = PermOrigin.USERGROUP_USER % u_k
710 if not self.explicit:
723
711 cur_perm = self.permissions_user_groups.get(
724 if perm.UserGroup.user_id == self.user_id:
712 ug_k, 'usergroup.none')
725 # set admin if owner
713 p = self._choose_permission(p, cur_perm)
726 p = 'usergroup.admin'
727 o = PermOrigin.USERGROUP_OWNER
728 else:
729 p = perm.Permission.permission_name
730 if not self.explicit:
731 cur_perm = self.permissions_user_groups.get(
732 ug_k, 'usergroup.none')
733 p = self._choose_permission(p, cur_perm)
714 self.permissions_user_groups[ug_k] = p, o
734 self.permissions_user_groups[ug_k] = p, o
715
735
716 def _choose_permission(self, new_perm, cur_perm):
736 def _choose_permission(self, new_perm, cur_perm):
@@ -831,10 +851,6 b' class AuthUser(object):'
831 self._permissions_scoped_cache[cache_key] = res
851 self._permissions_scoped_cache[cache_key] = res
832 return self._permissions_scoped_cache[cache_key]
852 return self._permissions_scoped_cache[cache_key]
833
853
834 @property
835 def auth_tokens(self):
836 return self.get_auth_tokens()
837
838 def get_instance(self):
854 def get_instance(self):
839 return User.get(self.user_id)
855 return User.get(self.user_id)
840
856
@@ -925,16 +941,6 b' class AuthUser(object):'
925 log.debug('PERMISSION tree computed %s' % (result_repr,))
941 log.debug('PERMISSION tree computed %s' % (result_repr,))
926 return result
942 return result
927
943
928 def get_auth_tokens(self):
929 auth_tokens = [self.api_key]
930 for api_key in UserApiKeys.query()\
931 .filter(UserApiKeys.user_id == self.user_id)\
932 .filter(or_(UserApiKeys.expires == -1,
933 UserApiKeys.expires >= time.time())).all():
934 auth_tokens.append(api_key.api_key)
935
936 return auth_tokens
937
938 @property
944 @property
939 def is_default(self):
945 def is_default(self):
940 return self.username == User.DEFAULT_USER
946 return self.username == User.DEFAULT_USER
@@ -952,25 +958,27 b' class AuthUser(object):'
952 """
958 """
953 Returns list of repositories you're an admin of
959 Returns list of repositories you're an admin of
954 """
960 """
955 return [x[0] for x in self.permissions['repositories'].iteritems()
961 return [
956 if x[1] == 'repository.admin']
962 x[0] for x in self.permissions['repositories'].iteritems()
963 if x[1] == 'repository.admin']
957
964
958 @property
965 @property
959 def repository_groups_admin(self):
966 def repository_groups_admin(self):
960 """
967 """
961 Returns list of repository groups you're an admin of
968 Returns list of repository groups you're an admin of
962 """
969 """
963 return [x[0]
970 return [
964 for x in self.permissions['repositories_groups'].iteritems()
971 x[0] for x in self.permissions['repositories_groups'].iteritems()
965 if x[1] == 'group.admin']
972 if x[1] == 'group.admin']
966
973
967 @property
974 @property
968 def user_groups_admin(self):
975 def user_groups_admin(self):
969 """
976 """
970 Returns list of user groups you're an admin of
977 Returns list of user groups you're an admin of
971 """
978 """
972 return [x[0] for x in self.permissions['user_groups'].iteritems()
979 return [
973 if x[1] == 'usergroup.admin']
980 x[0] for x in self.permissions['user_groups'].iteritems()
981 if x[1] == 'usergroup.admin']
974
982
975 @property
983 @property
976 def ip_allowed(self):
984 def ip_allowed(self):
@@ -1171,7 +1179,7 b' class LoginRequired(object):'
1171 :param api_access: if enabled this checks only for valid auth token
1179 :param api_access: if enabled this checks only for valid auth token
1172 and grants access based on valid token
1180 and grants access based on valid token
1173 """
1181 """
1174 def __init__(self, auth_token_access=False):
1182 def __init__(self, auth_token_access=None):
1175 self.auth_token_access = auth_token_access
1183 self.auth_token_access = auth_token_access
1176
1184
1177 def __call__(self, func):
1185 def __call__(self, func):
@@ -1191,7 +1199,7 b' class LoginRequired(object):'
1191 ip_access_valid = False
1199 ip_access_valid = False
1192
1200
1193 # check if we used an APIKEY and it's a valid one
1201 # check if we used an APIKEY and it's a valid one
1194 # defined whitelist of controllers which API access will be enabled
1202 # defined white-list of controllers which API access will be enabled
1195 _auth_token = request.GET.get(
1203 _auth_token = request.GET.get(
1196 'auth_token', '') or request.GET.get('api_key', '')
1204 'auth_token', '') or request.GET.get('api_key', '')
1197 auth_token_access_valid = allowed_auth_token_access(
1205 auth_token_access_valid = allowed_auth_token_access(
@@ -1200,8 +1208,20 b' class LoginRequired(object):'
1200 # explicit controller is enabled or API is in our whitelist
1208 # explicit controller is enabled or API is in our whitelist
1201 if self.auth_token_access or auth_token_access_valid:
1209 if self.auth_token_access or auth_token_access_valid:
1202 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1210 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1211 db_user = user.get_instance()
1203
1212
1204 if _auth_token and _auth_token in user.auth_tokens:
1213 if db_user:
1214 if self.auth_token_access:
1215 roles = self.auth_token_access
1216 else:
1217 roles = [UserApiKeys.ROLE_HTTP]
1218 token_match = db_user.authenticate_by_token(
1219 _auth_token, roles=roles)
1220 else:
1221 log.debug('Unable to fetch db instance for auth user: %s', user)
1222 token_match = False
1223
1224 if _auth_token and token_match:
1205 auth_token_access_valid = True
1225 auth_token_access_valid = True
1206 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1226 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1207 else:
1227 else:
@@ -1234,7 +1254,6 b' class LoginRequired(object):'
1234 auth_token_access_valid))
1254 auth_token_access_valid))
1235 # we preserve the get PARAM
1255 # we preserve the get PARAM
1236 came_from = request.path_qs
1256 came_from = request.path_qs
1237
1238 log.debug('redirecting to login page with %s' % (came_from,))
1257 log.debug('redirecting to login page with %s' % (came_from,))
1239 return redirect(
1258 return redirect(
1240 h.route_path('login', _query={'came_from': came_from}))
1259 h.route_path('login', _query={'came_from': came_from}))
@@ -1249,6 +1268,7 b' class NotAnonymous(object):'
1249 return get_cython_compat_decorator(self.__wrapper, func)
1268 return get_cython_compat_decorator(self.__wrapper, func)
1250
1269
1251 def __wrapper(self, func, *fargs, **fkwargs):
1270 def __wrapper(self, func, *fargs, **fkwargs):
1271 import rhodecode.lib.helpers as h
1252 cls = fargs[0]
1272 cls = fargs[0]
1253 self.user = cls._rhodecode_user
1273 self.user = cls._rhodecode_user
1254
1274
@@ -1258,8 +1278,6 b' class NotAnonymous(object):'
1258
1278
1259 if anonymous:
1279 if anonymous:
1260 came_from = request.path_qs
1280 came_from = request.path_qs
1261
1262 import rhodecode.lib.helpers as h
1263 h.flash(_('You need to be a registered user to '
1281 h.flash(_('You need to be a registered user to '
1264 'perform this action'),
1282 'perform this action'),
1265 category='warning')
1283 category='warning')
@@ -1296,6 +1314,7 b' class HasAcceptedRepoType(object):'
1296 return get_cython_compat_decorator(self.__wrapper, func)
1314 return get_cython_compat_decorator(self.__wrapper, func)
1297
1315
1298 def __wrapper(self, func, *fargs, **fkwargs):
1316 def __wrapper(self, func, *fargs, **fkwargs):
1317 import rhodecode.lib.helpers as h
1299 cls = fargs[0]
1318 cls = fargs[0]
1300 rhodecode_repo = cls.rhodecode_repo
1319 rhodecode_repo = cls.rhodecode_repo
1301
1320
@@ -1306,7 +1325,6 b' class HasAcceptedRepoType(object):'
1306 if rhodecode_repo.alias in self.repo_type_list:
1325 if rhodecode_repo.alias in self.repo_type_list:
1307 return func(*fargs, **fkwargs)
1326 return func(*fargs, **fkwargs)
1308 else:
1327 else:
1309 import rhodecode.lib.helpers as h
1310 h.flash(h.literal(
1328 h.flash(h.literal(
1311 _('Action not supported for %s.' % rhodecode_repo.alias)),
1329 _('Action not supported for %s.' % rhodecode_repo.alias)),
1312 category='warning')
1330 category='warning')
@@ -1326,7 +1344,22 b' class PermsDecorator(object):'
1326 def __call__(self, func):
1344 def __call__(self, func):
1327 return get_cython_compat_decorator(self.__wrapper, func)
1345 return get_cython_compat_decorator(self.__wrapper, func)
1328
1346
1347 def _get_request(self):
1348 from pyramid.threadlocal import get_current_request
1349 pyramid_request = get_current_request()
1350 if not pyramid_request:
1351 # return global request of pylons in case pyramid isn't available
1352 return request
1353 return pyramid_request
1354
1355 def _get_came_from(self):
1356 _request = self._get_request()
1357
1358 # both pylons/pyramid has this attribute
1359 return _request.path_qs
1360
1329 def __wrapper(self, func, *fargs, **fkwargs):
1361 def __wrapper(self, func, *fargs, **fkwargs):
1362 import rhodecode.lib.helpers as h
1330 cls = fargs[0]
1363 cls = fargs[0]
1331 _user = cls._rhodecode_user
1364 _user = cls._rhodecode_user
1332
1365
@@ -1342,17 +1375,15 b' class PermsDecorator(object):'
1342 anonymous = _user.username == User.DEFAULT_USER
1375 anonymous = _user.username == User.DEFAULT_USER
1343
1376
1344 if anonymous:
1377 if anonymous:
1345 came_from = request.path_qs
1378 came_from = self._get_came_from()
1346
1347 import rhodecode.lib.helpers as h
1348 h.flash(_('You need to be signed in to view this page'),
1379 h.flash(_('You need to be signed in to view this page'),
1349 category='warning')
1380 category='warning')
1350 return redirect(
1381 raise HTTPFound(
1351 h.route_path('login', _query={'came_from': came_from}))
1382 h.route_path('login', _query={'came_from': came_from}))
1352
1383
1353 else:
1384 else:
1354 # redirect with forbidden ret code
1385 # redirect with forbidden ret code
1355 return abort(403)
1386 raise HTTPForbidden()
1356
1387
1357 def check_permissions(self, user):
1388 def check_permissions(self, user):
1358 """Dummy function for overriding"""
1389 """Dummy function for overriding"""
@@ -1391,10 +1422,13 b' class HasRepoPermissionAllDecorator(Perm'
1391 Checks for access permission for all given predicates for specific
1422 Checks for access permission for all given predicates for specific
1392 repository. All of them have to be meet in order to fulfill the request
1423 repository. All of them have to be meet in order to fulfill the request
1393 """
1424 """
1425 def _get_repo_name(self):
1426 _request = self._get_request()
1427 return get_repo_slug(_request)
1394
1428
1395 def check_permissions(self, user):
1429 def check_permissions(self, user):
1396 perms = user.permissions
1430 perms = user.permissions
1397 repo_name = get_repo_slug(request)
1431 repo_name = self._get_repo_name()
1398 try:
1432 try:
1399 user_perms = set([perms['repositories'][repo_name]])
1433 user_perms = set([perms['repositories'][repo_name]])
1400 except KeyError:
1434 except KeyError:
@@ -1409,10 +1443,13 b' class HasRepoPermissionAnyDecorator(Perm'
1409 Checks for access permission for any of given predicates for specific
1443 Checks for access permission for any of given predicates for specific
1410 repository. In order to fulfill the request any of predicates must be meet
1444 repository. In order to fulfill the request any of predicates must be meet
1411 """
1445 """
1446 def _get_repo_name(self):
1447 _request = self._get_request()
1448 return get_repo_slug(_request)
1412
1449
1413 def check_permissions(self, user):
1450 def check_permissions(self, user):
1414 perms = user.permissions
1451 perms = user.permissions
1415 repo_name = get_repo_slug(request)
1452 repo_name = self._get_repo_name()
1416 try:
1453 try:
1417 user_perms = set([perms['repositories'][repo_name]])
1454 user_perms = set([perms['repositories'][repo_name]])
1418 except KeyError:
1455 except KeyError:
@@ -1429,10 +1466,13 b' class HasRepoGroupPermissionAllDecorator'
1429 repository group. All of them have to be meet in order to
1466 repository group. All of them have to be meet in order to
1430 fulfill the request
1467 fulfill the request
1431 """
1468 """
1469 def _get_repo_group_name(self):
1470 _request = self._get_request()
1471 return get_repo_group_slug(_request)
1432
1472
1433 def check_permissions(self, user):
1473 def check_permissions(self, user):
1434 perms = user.permissions
1474 perms = user.permissions
1435 group_name = get_repo_group_slug(request)
1475 group_name = self._get_repo_group_name()
1436 try:
1476 try:
1437 user_perms = set([perms['repositories_groups'][group_name]])
1477 user_perms = set([perms['repositories_groups'][group_name]])
1438 except KeyError:
1478 except KeyError:
@@ -1449,10 +1489,13 b' class HasRepoGroupPermissionAnyDecorator'
1449 repository group. In order to fulfill the request any
1489 repository group. In order to fulfill the request any
1450 of predicates must be met
1490 of predicates must be met
1451 """
1491 """
1492 def _get_repo_group_name(self):
1493 _request = self._get_request()
1494 return get_repo_group_slug(_request)
1452
1495
1453 def check_permissions(self, user):
1496 def check_permissions(self, user):
1454 perms = user.permissions
1497 perms = user.permissions
1455 group_name = get_repo_group_slug(request)
1498 group_name = self._get_repo_group_name()
1456 try:
1499 try:
1457 user_perms = set([perms['repositories_groups'][group_name]])
1500 user_perms = set([perms['repositories_groups'][group_name]])
1458 except KeyError:
1501 except KeyError:
@@ -1468,10 +1511,13 b' class HasUserGroupPermissionAllDecorator'
1468 Checks for access permission for all given predicates for specific
1511 Checks for access permission for all given predicates for specific
1469 user group. All of them have to be meet in order to fulfill the request
1512 user group. All of them have to be meet in order to fulfill the request
1470 """
1513 """
1514 def _get_user_group_name(self):
1515 _request = self._get_request()
1516 return get_user_group_slug(_request)
1471
1517
1472 def check_permissions(self, user):
1518 def check_permissions(self, user):
1473 perms = user.permissions
1519 perms = user.permissions
1474 group_name = get_user_group_slug(request)
1520 group_name = self._get_user_group_name()
1475 try:
1521 try:
1476 user_perms = set([perms['user_groups'][group_name]])
1522 user_perms = set([perms['user_groups'][group_name]])
1477 except KeyError:
1523 except KeyError:
@@ -1487,10 +1533,13 b' class HasUserGroupPermissionAnyDecorator'
1487 Checks for access permission for any of given predicates for specific
1533 Checks for access permission for any of given predicates for specific
1488 user group. In order to fulfill the request any of predicates must be meet
1534 user group. In order to fulfill the request any of predicates must be meet
1489 """
1535 """
1536 def _get_user_group_name(self):
1537 _request = self._get_request()
1538 return get_user_group_slug(_request)
1490
1539
1491 def check_permissions(self, user):
1540 def check_permissions(self, user):
1492 perms = user.permissions
1541 perms = user.permissions
1493 group_name = get_user_group_slug(request)
1542 group_name = self._get_user_group_name()
1494 try:
1543 try:
1495 user_perms = set([perms['user_groups'][group_name]])
1544 user_perms = set([perms['user_groups'][group_name]])
1496 except KeyError:
1545 except KeyError:
@@ -1553,6 +1602,14 b' class PermsFunction(object):'
1553 check_scope, user, check_location)
1602 check_scope, user, check_location)
1554 return False
1603 return False
1555
1604
1605 def _get_request(self):
1606 from pyramid.threadlocal import get_current_request
1607 pyramid_request = get_current_request()
1608 if not pyramid_request:
1609 # return global request of pylons incase pyramid one isn't available
1610 return request
1611 return pyramid_request
1612
1556 def _get_check_scope(self, cls_name):
1613 def _get_check_scope(self, cls_name):
1557 return {
1614 return {
1558 'HasPermissionAll': 'GLOBAL',
1615 'HasPermissionAll': 'GLOBAL',
@@ -1591,10 +1648,14 b' class HasRepoPermissionAll(PermsFunction'
1591 self.repo_name = repo_name
1648 self.repo_name = repo_name
1592 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1649 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1593
1650
1594 def check_permissions(self, user):
1651 def _get_repo_name(self):
1595 if not self.repo_name:
1652 if not self.repo_name:
1596 self.repo_name = get_repo_slug(request)
1653 _request = self._get_request()
1654 self.repo_name = get_repo_slug(_request)
1655 return self.repo_name
1597
1656
1657 def check_permissions(self, user):
1658 self.repo_name = self._get_repo_name()
1598 perms = user.permissions
1659 perms = user.permissions
1599 try:
1660 try:
1600 user_perms = set([perms['repositories'][self.repo_name]])
1661 user_perms = set([perms['repositories'][self.repo_name]])
@@ -1610,10 +1671,13 b' class HasRepoPermissionAny(PermsFunction'
1610 self.repo_name = repo_name
1671 self.repo_name = repo_name
1611 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1672 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1612
1673
1613 def check_permissions(self, user):
1674 def _get_repo_name(self):
1614 if not self.repo_name:
1675 if not self.repo_name:
1615 self.repo_name = get_repo_slug(request)
1676 self.repo_name = get_repo_slug(request)
1677 return self.repo_name
1616
1678
1679 def check_permissions(self, user):
1680 self.repo_name = self._get_repo_name()
1617 perms = user.permissions
1681 perms = user.permissions
1618 try:
1682 try:
1619 user_perms = set([perms['repositories'][self.repo_name]])
1683 user_perms = set([perms['repositories'][self.repo_name]])
@@ -1905,3 +1969,5 b' def get_cython_compat_decorator(wrapper,'
1905 return wrapper(func, *args, **kwds)
1969 return wrapper(func, *args, **kwds)
1906 local_wrapper.__wrapped__ = func
1970 local_wrapper.__wrapped__ = func
1907 return local_wrapper
1971 return local_wrapper
1972
1973
@@ -209,11 +209,12 b' def vcs_operation_context('
209 class BasicAuth(AuthBasicAuthenticator):
209 class BasicAuth(AuthBasicAuthenticator):
210
210
211 def __init__(self, realm, authfunc, registry, auth_http_code=None,
211 def __init__(self, realm, authfunc, registry, auth_http_code=None,
212 initial_call_detection=False):
212 initial_call_detection=False, acl_repo_name=None):
213 self.realm = realm
213 self.realm = realm
214 self.initial_call = initial_call_detection
214 self.initial_call = initial_call_detection
215 self.authfunc = authfunc
215 self.authfunc = authfunc
216 self.registry = registry
216 self.registry = registry
217 self.acl_repo_name = acl_repo_name
217 self._rc_auth_http_code = auth_http_code
218 self._rc_auth_http_code = auth_http_code
218
219
219 def _get_response_from_code(self, http_code):
220 def _get_response_from_code(self, http_code):
@@ -247,7 +248,7 b' class BasicAuth(AuthBasicAuthenticator):'
247 username, password = _parts
248 username, password = _parts
248 if self.authfunc(
249 if self.authfunc(
249 username, password, environ, VCS_TYPE,
250 username, password, environ, VCS_TYPE,
250 registry=self.registry):
251 registry=self.registry, acl_repo_name=self.acl_repo_name):
251 return username
252 return username
252 if username and password:
253 if username and password:
253 # we mark that we actually executed authentication once, at
254 # we mark that we actually executed authentication once, at
@@ -488,21 +489,12 b' class BaseController(WSGIController):'
488 _route_name)
489 _route_name)
489 )
490 )
490
491
491 # TODO: Maybe this should be move to pyramid to cover all views.
492 # check user attributes for password change flag
493 user_obj = auth_user.get_instance()
492 user_obj = auth_user.get_instance()
494 if user_obj and user_obj.user_data.get('force_password_change'):
493 if user_obj and user_obj.user_data.get('force_password_change'):
495 h.flash('You are required to change your password', 'warning',
494 h.flash('You are required to change your password', 'warning',
496 ignore_duplicate=True)
495 ignore_duplicate=True)
497
496 return self._dispatch_redirect(
498 skip_user_check_urls = [
497 url('my_account_password'), environ, start_response)
499 'error.document', 'login.logout', 'login.index',
500 'admin/my_account.my_account_password',
501 'admin/my_account.my_account_password_update'
502 ]
503 if _route_name not in skip_user_check_urls:
504 return self._dispatch_redirect(
505 url('my_account_password'), environ, start_response)
506
498
507 return WSGIController.__call__(self, environ, start_response)
499 return WSGIController.__call__(self, environ, start_response)
508
500
@@ -226,7 +226,6 b' def vcsconnection(func):'
226 for alias in rhodecode.BACKENDS.keys():
226 for alias in rhodecode.BACKENDS.keys():
227 if alias not in backends:
227 if alias not in backends:
228 del rhodecode.BACKENDS[alias]
228 del rhodecode.BACKENDS[alias]
229 utils.configure_pyro4(settings)
230 utils.configure_vcs(settings)
229 utils.configure_vcs(settings)
231 connect_vcs(
230 connect_vcs(
232 settings['vcs.server'],
231 settings['vcs.server'],
@@ -25,7 +25,7 b' from itertools import groupby'
25 from pygments import lex
25 from pygments import lex
26 from pygments.formatters.html import _get_ttype_class as pygment_token_class
26 from pygments.formatters.html import _get_ttype_class as pygment_token_class
27 from rhodecode.lib.helpers import (
27 from rhodecode.lib.helpers import (
28 get_lexer_for_filenode, html_escape)
28 get_lexer_for_filenode, html_escape, get_custom_lexer)
29 from rhodecode.lib.utils2 import AttributeDict
29 from rhodecode.lib.utils2 import AttributeDict
30 from rhodecode.lib.vcs.nodes import FileNode
30 from rhodecode.lib.vcs.nodes import FileNode
31 from rhodecode.lib.diff_match_patch import diff_match_patch
31 from rhodecode.lib.diff_match_patch import diff_match_patch
@@ -407,8 +407,12 b' class DiffSet(object):'
407 if filename not in self._lexer_cache:
407 if filename not in self._lexer_cache:
408 if filenode:
408 if filenode:
409 lexer = filenode.lexer
409 lexer = filenode.lexer
410 extension = filenode.extension
410 else:
411 else:
411 lexer = FileNode.get_lexer(filename=filename)
412 lexer = FileNode.get_lexer(filename=filename)
413 extension = filename.split('.')[-1]
414
415 lexer = get_custom_lexer(extension) or lexer
412 self._lexer_cache[filename] = lexer
416 self._lexer_cache[filename] = lexer
413 return self._lexer_cache[filename]
417 return self._lexer_cache[filename]
414
418
@@ -292,13 +292,13 b' class DbManage(object):'
292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
292 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
293
293
294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
294 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
295 TEST_USER_ADMIN_EMAIL, True)
295 TEST_USER_ADMIN_EMAIL, True, api_key=True)
296
296
297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
297 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
298 TEST_USER_REGULAR_EMAIL, False)
298 TEST_USER_REGULAR_EMAIL, False, api_key=True)
299
299
300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
300 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
301 TEST_USER_REGULAR2_EMAIL, False)
301 TEST_USER_REGULAR2_EMAIL, False, api_key=True)
302
302
303 def create_ui_settings(self, repo_store_path):
303 def create_ui_settings(self, repo_store_path):
304 """
304 """
@@ -306,6 +306,8 b' class DbManage(object):'
306 and disables dotencode
306 and disables dotencode
307 """
307 """
308 settings_model = SettingsModel(sa=self.sa)
308 settings_model = SettingsModel(sa=self.sa)
309 from rhodecode.lib.vcs.backends.hg import largefiles_store
310 from rhodecode.lib.vcs.backends.git import lfs_store
309
311
310 # Build HOOKS
312 # Build HOOKS
311 hooks = [
313 hooks = [
@@ -315,6 +317,7 b' class DbManage(object):'
315 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
317 (RhodeCodeUi.HOOK_PRE_PULL, 'python:vcsserver.hooks.pre_pull'),
316 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
318 (RhodeCodeUi.HOOK_PULL, 'python:vcsserver.hooks.log_pull_action'),
317 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
319 (RhodeCodeUi.HOOK_PRE_PUSH, 'python:vcsserver.hooks.pre_push'),
320 (RhodeCodeUi.HOOK_PRETX_PUSH, 'python:vcsserver.hooks.pre_push'),
318 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
321 (RhodeCodeUi.HOOK_PUSH, 'python:vcsserver.hooks.log_push_action'),
319
322
320 ]
323 ]
@@ -335,14 +338,23 b' class DbManage(object):'
335 self.sa.add(largefiles)
338 self.sa.add(largefiles)
336
339
337 # set default largefiles cache dir, defaults to
340 # set default largefiles cache dir, defaults to
338 # /repo location/.cache/largefiles
341 # /repo_store_location/.cache/largefiles
339 largefiles = RhodeCodeUi()
342 largefiles = RhodeCodeUi()
340 largefiles.ui_section = 'largefiles'
343 largefiles.ui_section = 'largefiles'
341 largefiles.ui_key = 'usercache'
344 largefiles.ui_key = 'usercache'
342 largefiles.ui_value = os.path.join(repo_store_path, '.cache',
345 largefiles.ui_value = largefiles_store(repo_store_path)
343 'largefiles')
346
344 self.sa.add(largefiles)
347 self.sa.add(largefiles)
345
348
349 # set default lfs cache dir, defaults to
350 # /repo_store_location/.cache/lfs_store
351 lfsstore = RhodeCodeUi()
352 lfsstore.ui_section = 'vcs_git_lfs'
353 lfsstore.ui_key = 'store_location'
354 lfsstore.ui_value = lfs_store(repo_store_path)
355
356 self.sa.add(lfsstore)
357
346 # enable hgsubversion disabled by default
358 # enable hgsubversion disabled by default
347 hgsubversion = RhodeCodeUi()
359 hgsubversion = RhodeCodeUi()
348 hgsubversion.ui_section = 'extensions'
360 hgsubversion.ui_section = 'extensions'
@@ -506,12 +518,12 b' class DbManage(object):'
506 self.create_ui_settings(path)
518 self.create_ui_settings(path)
507
519
508 ui_config = [
520 ui_config = [
509 ('web', 'push_ssl', 'false'),
521 ('web', 'push_ssl', 'False'),
510 ('web', 'allow_archive', 'gz zip bz2'),
522 ('web', 'allow_archive', 'gz zip bz2'),
511 ('web', 'allow_push', '*'),
523 ('web', 'allow_push', '*'),
512 ('web', 'baseurl', '/'),
524 ('web', 'baseurl', '/'),
513 ('paths', '/', path),
525 ('paths', '/', path),
514 ('phases', 'publish', 'true')
526 ('phases', 'publish', 'True')
515 ]
527 ]
516 for section, key, value in ui_config:
528 for section, key, value in ui_config:
517 ui_conf = RhodeCodeUi()
529 ui_conf = RhodeCodeUi()
@@ -560,7 +572,9 b' class DbManage(object):'
560
572
561 if api_key:
573 if api_key:
562 log.info('setting a provided api key for the user %s', username)
574 log.info('setting a provided api key for the user %s', username)
563 user.api_key = api_key
575 from rhodecode.model.auth_token import AuthTokenModel
576 AuthTokenModel().create(
577 user=user, description='BUILTIN TOKEN')
564
578
565 def create_default_user(self):
579 def create_default_user(self):
566 log.info('creating default user')
580 log.info('creating default user')
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file copied from rhodecode/templates/admin/user_groups/user_groups.mako to rhodecode/templates/admin/users/user_edit_groups.mako
NO CONTENT: file copied from rhodecode/templates/admin/user_groups/user_groups.mako to rhodecode/templates/admin/users/user_edit_groups.mako
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed
NO CONTENT: file was removed
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now