##// END OF EJS Templates
release: merge back stable branch into default
marcink -
r2757:4b0930b4 merge default
parent child Browse files
Show More
@@ -0,0 +1,41 b''
1 |RCE| 4.12.2 |RNS|
2 ------------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2018-05-16
8
9
10 New Features
11 ^^^^^^^^^^^^
12
13
14
15 General
16 ^^^^^^^
17
18 - Jenkins: further improve handling of proxied Jenkins server.
19
20
21 Security
22 ^^^^^^^^
23
24 - SSH: fixed found problem with key-storage that could allow remote logins
25 performed by rhodecode authorized users with specially crafted SSH Keys.
26
27
28 Performance
29 ^^^^^^^^^^^
30
31
32
33 Fixes
34 ^^^^^
35
36
37
38 Upgrade notes
39 ^^^^^^^^^^^^^
40
41 - Unscheduled release addressing found security problem.
@@ -1,39 +1,40 b''
1 1 1bd3e92b7e2e2d2024152b34bb88dff1db544a71 v4.0.0
2 2 170c5398320ea6cddd50955e88d408794c21d43a v4.0.1
3 3 c3fe200198f5aa34cf2e4066df2881a9cefe3704 v4.1.0
4 4 7fd5c850745e2ea821fb4406af5f4bff9b0a7526 v4.1.1
5 5 41c87da28a179953df86061d817bc35533c66dd2 v4.1.2
6 6 baaf9f5bcea3bae0ef12ae20c8b270482e62abb6 v4.2.0
7 7 32a70c7e56844a825f61df496ee5eaf8c3c4e189 v4.2.1
8 8 fa695cdb411d294679ac081d595ac654e5613b03 v4.3.0
9 9 0e4dc11b58cad833c513fe17bac39e6850edf959 v4.3.1
10 10 8a876f48f5cb1d018b837db28ff928500cb32cfb v4.4.0
11 11 8dd86b410b1aac086ffdfc524ef300f896af5047 v4.4.1
12 12 d2514226abc8d3b4f6fb57765f47d1b6fb360a05 v4.4.2
13 13 27d783325930af6dad2741476c0d0b1b7c8415c2 v4.5.0
14 14 7f2016f352abcbdba4a19d4039c386e9629449da v4.5.1
15 15 416fec799314c70a5c780fb28b3357b08869333a v4.5.2
16 16 27c3b85fafc83143e6678fbc3da69e1615bcac55 v4.6.0
17 17 5ad13deb9118c2a5243d4032d4d9cc174e5872db v4.6.1
18 18 2be921e01fa24bb102696ada596f87464c3666f6 v4.7.0
19 19 7198bdec29c2872c974431d55200d0398354cdb1 v4.7.1
20 20 bd1c8d230fe741c2dfd7100a0ef39fd0774fd581 v4.7.2
21 21 9731914f89765d9628dc4dddc84bc9402aa124c8 v4.8.0
22 22 c5a2b7d0e4bbdebc4a62d7b624befe375207b659 v4.9.0
23 23 d9aa3b27ac9f7e78359775c75fedf7bfece232f1 v4.9.1
24 24 4ba4d74981cec5d6b28b158f875a2540952c2f74 v4.10.0
25 25 0a6821cbd6b0b3c21503002f88800679fa35ab63 v4.10.1
26 26 434ad90ec8d621f4416074b84f6e9ce03964defb v4.10.2
27 27 68baee10e698da2724c6e0f698c03a6abb993bf2 v4.10.3
28 28 00821d3afd1dce3f4767cc353f84a17f7d5218a1 v4.10.4
29 29 22f6744ad8cc274311825f63f953e4dee2ea5cb9 v4.10.5
30 30 96eb24bea2f5f9258775245e3f09f6fa0a4dda01 v4.10.6
31 31 3121217a812c956d7dd5a5875821bd73e8002a32 v4.11.0
32 32 fa98b454715ac5b912f39e84af54345909a2a805 v4.11.1
33 33 3982abcfdcc229a723cebe52d3a9bcff10bba08e v4.11.2
34 34 33195f145db9172f0a8f1487e09207178a6ab065 v4.11.3
35 35 194c74f33e32bbae6fc4d71ec5a999cff3c13605 v4.11.4
36 36 8fbd8b0c3ddc2fa4ac9e4ca16942a03eb593df2d v4.11.5
37 37 f0609aa5d5d05a1ca2f97c3995542236131c9d8a v4.11.6
38 38 b5b30547d90d2e088472a70c84878f429ffbf40d v4.12.0
39 39 9072253aa8894d20c00b4a43dc61c2168c1eff94 v4.12.1
40 6a517543ea9ef9987d74371bd2a315eb0b232dc9 v4.12.2
@@ -1,143 +1,144 b''
1 1 |RCE| 4.12.0 |RNS|
2 2 ------------------
3 3
4 4 Release Date
5 5 ^^^^^^^^^^^^
6 6
7 7 - 2018-04-24
8 8
9 9
10 10 New Features
11 11 ^^^^^^^^^^^^
12 12
13 13 - Svn: added support for RhodeCode integration framework. All integrations like
14 14 slack, email, Jenkins now also fully work for SVN.
15 15 - Integrations: added new dedicated Jenkins integration with the support of
16 16 CSRF authentication. Available in EE edition only.
17 17 - Automation: added new bi-directional remote sync. RhodeCode instances can now
18 18 automatically push or pull from/to remote locations. This feature is powered
19 19 by the Scheduler of 4.11 release, and it is required to be enabled for this feature to work.
20 20 Available in EE edition only.
21 21 - Mercurial: path-based permissions. RhodeCode can now use Mercurials narrowhg
22 22 to implement path-based permissions. All permissions are read from .hg/hgacl.
23 23 Thanks to the great contribution from Sandu Turcan.
24 24 - VCS: added new diff caches. Available as an option under vcs settings.
25 25 Diff caches work on pull-request, or individual commits for greater
26 26 performance and reduced memory usage. This feature increases speed of large
27 27 pull requests significantly. In addition for pull requests it will allow
28 28 showing old closed pull requests even if commits from source were removed,
29 29 further enhancing auditing capabilities.
30 30 - Audit: added few new audit log entries especially around changing permissions.
31 31 - LDAP: added connection pinning and timeout option to ldap plugin. This should
32 32 prevent problems when connection to LDAP is not stable causing RhodeCode
33 33 instances to freeze waiting on LDAP connections.
34 34 - User groups: expose public user group profiles. Allows to see members of a user
35 groups by other team members, if they have proper permissions.
35 group by other team members, if they have proper permissions.
36 36 - UI: show pull request page in quick nav menu on my account for quicker access.
37 37 - UI: hidden/outdated comments now have visible markers next to line numbers.
38 38 This allows access to them without showing all hidden comments.
39 39
40 40
41 41 General
42 42 ^^^^^^^
43 43
44 44 - Ssh: show conflicting fingerprint when adding an already existing key.
45 45 Helps to track why adding a key failed.
46 46 - System info: added ulimit to system info. This is causing lots of problems
47 47 when we hit any of those limits, that is why it's important to show this.
48 48 - Repository settings: add hidden view to force re-install hooks.
49 49 Available under /{repo_name}/settings/advanced/hooks
50 50 - Integrations: Webhook now handles response errors and show response for
51 51 easier debugging.
52 52 - Cli: speed up CLI execution start by skipping auth plugin search/registry.
53 53 - SVN: added an example in the docs on how to enable path-based permissions.
54 54 - LDAP: enable connection recycling on LDAP plugin.
55 55 - Auth plugins: use a nicer visual display of auth plugins that would
56 56 highlight that order of enabled plugins does matter.
57 57 - Events: expose shadow repo build url.
58 58 - Events: expose pull request title and uid in event data.
59 59 - API: enable setting sync flag for user groups on create/edit.
60 60 - API: update pull method with a possible specification of the url
61 61 - Logging: improved consistency of auth plugins logs.
62 62 - Logging: improved log for ssl required
63 63 - Dependencies: bumped mercurial to 4.4 series
64 64 - Dependencies: bumped zope.cachedescriptors==4.3.1
65 65 - Dependencies: bumped zope.deprecation==4.3.0
66 66 - Dependencies: bumped zope.event==4.3.0
67 67 - Dependencies: bumped zope.interface==4.4.3
68 68 - Dependencies: bumped graphviz 0.8.2
69 69 - Dependencies: bumped to ipaddress 0.1.19
70 70 - Dependencies: bumped pyexpect to 4.3.1
71 71 - Dependencies: bumped ws4py to 0.4.3
72 72 - Dependencies: bumped bleach to 2.1.2
73 73 - Dependencies: bumped html5lib 1.0.1
74 74 - Dependencies: bumped greenlet to 0.4.13
75 75 - Dependencies: bumped markdown to 2.6.11
76 76 - Dependencies: bumped psutil to 5.4.3
77 77 - Dependencies: bumped beaker to 1.9.1
78 78 - Dependencies: bumped alembic to 0.6.8 release.
79 79 - Dependencies: bumped supervisor to 3.3.4
80 80 - Dependencies: bumped pyexpect to 4.4.0 and scandir to 1.7
81 81 - Dependencies: bumped appenlight client to 0.6.25
82 82 - Dependencies: don't require full mysql lib for the db driver.
83 83 Reduces installation package size by around 100MB.
84 84
85 85
86 86 Security
87 87 ^^^^^^^^
88 88
89 89 - My account: changing email in my account now requires providing user
90 90 access password. This is a case for only RhodeCode built-in accounts.
91 91 Prevents adding recovery email by unauthorized users who gain
92 92 access to logged in session of user.
93 93 - Logging: fix leaking of tokens to logging.
94 94 - General: serialize the repo name in repo checks to prevent potential
95 95 html injections by providing a malformed url.
96 96
97 97
98 98 Performance
99 99 ^^^^^^^^^^^
100 100
101 101 - Diffs: don't use recurred diffset attachment in diffs. This makes
102 this structure much harder to garbage collect. Reduces memory usage.
102 this structure much harder to garbage collect. Reduces memory usage.
103 103 - Diff cache: added caching for better performance of large pull requests.
104 104
105 105
106 106 Fixes
107 107 ^^^^^
108 108
109 109 - Age helper: fix issues with proper timezone detection for certain timezones.
110 110 Fixes wrong age display in few cases.
111 111 - API: added audit logs for user group related calls that were
112 112 accidentally missing.
113 113 - Diffs: fix and improve line selections and anchor links.
114 114 - Pull requests: fixed cases with default expected refs are closed or unavailable.
115 115 For Mercurial with closed default branch a compare across forks could fail.
116 116 - Core: properly report 502 errors for gevent and gunicorn.
117 Gevent wtih Gunicorn doesn't raise normal pycurl errors.
117 Gevent with Gunicorn doesn't raise normal pycurl errors.
118 118 - Auth plugins: fixed problem with cache of settings in multi-worker mode.
119 119 The previous implementation had a bug that cached the settings in each class,
120 120 caused not refreshing the update of settings in multi-worker mode.
121 121 Only restart of RhodeCode loaded new settings.
122 122 - Audit logs: properly handle query syntax in the search field.
123 123 - Repositories: better handling of missing requirements errors for repositories.
124 124 - API: fixed problems with repository fork/create using celery backend.
125 125 - VCS settings: added missing flash message on validation errors to prevent
126 126 missing out some field input validation problems.
127 127
128 128
129 129 Upgrade notes
130 130 ^^^^^^^^^^^^^
131 131
132 132 - This release adds support for SVN hook. This required lots of changes on how we
133 handle SVN protocol. We did thoughtful tests for SVN compatibility.
134 Please be advised to check the behaviour of SVN repositories during this update.
133 handle SVN protocol. We did thoughtful tests for SVN compatibility.
134 Please be advised to check the behaviour of SVN repositories during this update.
135 135
136 136 A check and migrate of SVN hooks is required. In order to do so, please execute
137 137 `Rescan filesystem` from admin > settings > Remap and Rescan. This will migrate
138 138 all SVN hook to latest available version. To migrate single repository only,
139 139 please go to the following url: `your-rhodecode-server.com/REPO_NAME/settings/advanced/hooks`
140 140
141 - Diff caches are turned off by default for backward compatibility. We however recommend
142 turning them on either individually for bigger repositories or globally for every repository.
143 This setting can be found in admin > settings > vcs, or repository > settings > vcs
141 - Diff caches are turned off by default for backward compatibility.
142 We however recommend turning them on either individually for bigger
143 repositories or globally for every repository.
144 This setting can be found in admin > settings > vcs, or repository > settings > vcs
@@ -1,116 +1,117 b''
1 1 .. _rhodecode-release-notes-ref:
2 2
3 3 Release Notes
4 4 =============
5 5
6 6 |RCE| 4.x Versions
7 7 ------------------
8 8
9 9 .. toctree::
10 10 :maxdepth: 1
11 11
12 release-notes-4.12.2.rst
12 13 release-notes-4.12.1.rst
13 14 release-notes-4.12.0.rst
14 15 release-notes-4.11.6.rst
15 16 release-notes-4.11.5.rst
16 17 release-notes-4.11.4.rst
17 18 release-notes-4.11.3.rst
18 19 release-notes-4.11.2.rst
19 20 release-notes-4.11.1.rst
20 21 release-notes-4.11.0.rst
21 22 release-notes-4.10.6.rst
22 23 release-notes-4.10.5.rst
23 24 release-notes-4.10.4.rst
24 25 release-notes-4.10.3.rst
25 26 release-notes-4.10.2.rst
26 27 release-notes-4.10.1.rst
27 28 release-notes-4.10.0.rst
28 29 release-notes-4.9.1.rst
29 30 release-notes-4.9.0.rst
30 31 release-notes-4.8.0.rst
31 32 release-notes-4.7.2.rst
32 33 release-notes-4.7.1.rst
33 34 release-notes-4.7.0.rst
34 35 release-notes-4.6.1.rst
35 36 release-notes-4.6.0.rst
36 37 release-notes-4.5.2.rst
37 38 release-notes-4.5.1.rst
38 39 release-notes-4.5.0.rst
39 40 release-notes-4.4.2.rst
40 41 release-notes-4.4.1.rst
41 42 release-notes-4.4.0.rst
42 43 release-notes-4.3.1.rst
43 44 release-notes-4.3.0.rst
44 45 release-notes-4.2.1.rst
45 46 release-notes-4.2.0.rst
46 47 release-notes-4.1.2.rst
47 48 release-notes-4.1.1.rst
48 49 release-notes-4.1.0.rst
49 50 release-notes-4.0.1.rst
50 51 release-notes-4.0.0.rst
51 52
52 53 |RCE| 3.x Versions
53 54 ------------------
54 55
55 56 .. toctree::
56 57 :maxdepth: 1
57 58
58 59 release-notes-3.8.4.rst
59 60 release-notes-3.8.3.rst
60 61 release-notes-3.8.2.rst
61 62 release-notes-3.8.1.rst
62 63 release-notes-3.8.0.rst
63 64 release-notes-3.7.1.rst
64 65 release-notes-3.7.0.rst
65 66 release-notes-3.6.1.rst
66 67 release-notes-3.6.0.rst
67 68 release-notes-3.5.2.rst
68 69 release-notes-3.5.1.rst
69 70 release-notes-3.5.0.rst
70 71 release-notes-3.4.1.rst
71 72 release-notes-3.4.0.rst
72 73 release-notes-3.3.4.rst
73 74 release-notes-3.3.3.rst
74 75 release-notes-3.3.2.rst
75 76 release-notes-3.3.1.rst
76 77 release-notes-3.3.0.rst
77 78 release-notes-3.2.3.rst
78 79 release-notes-3.2.2.rst
79 80 release-notes-3.2.1.rst
80 81 release-notes-3.2.0.rst
81 82 release-notes-3.1.1.rst
82 83 release-notes-3.1.0.rst
83 84 release-notes-3.0.2.rst
84 85 release-notes-3.0.1.rst
85 86 release-notes-3.0.0.rst
86 87
87 88 |RCE| 2.x Versions
88 89 ------------------
89 90
90 91 .. toctree::
91 92 :maxdepth: 1
92 93
93 94 release-notes-2.2.8.rst
94 95 release-notes-2.2.7.rst
95 96 release-notes-2.2.6.rst
96 97 release-notes-2.2.5.rst
97 98 release-notes-2.2.4.rst
98 99 release-notes-2.2.3.rst
99 100 release-notes-2.2.2.rst
100 101 release-notes-2.2.1.rst
101 102 release-notes-2.2.0.rst
102 103 release-notes-2.1.0.rst
103 104 release-notes-2.0.2.rst
104 105 release-notes-2.0.1.rst
105 106 release-notes-2.0.0.rst
106 107
107 108 |RCE| 1.x Versions
108 109 ------------------
109 110
110 111 .. toctree::
111 112 :maxdepth: 1
112 113
113 114 release-notes-1.7.2.rst
114 115 release-notes-1.7.1.rst
115 116 release-notes-1.7.0.rst
116 117 release-notes-1.6.0.rst
@@ -1,1200 +1,1200 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.renderers import render
29 29 from pyramid.response import Response
30 30
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 33 from rhodecode.authentication.plugins import auth_rhodecode
34 34 from rhodecode.events import trigger
35 35 from rhodecode.model.db import true
36 36
37 37 from rhodecode.lib import audit_logger
38 38 from rhodecode.lib.exceptions import (
39 39 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
40 40 UserOwnsUserGroupsException, DefaultUserException)
41 41 from rhodecode.lib.ext_json import json
42 42 from rhodecode.lib.auth import (
43 43 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
44 44 from rhodecode.lib import helpers as h
45 45 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
46 46 from rhodecode.model.auth_token import AuthTokenModel
47 47 from rhodecode.model.forms import (
48 48 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
49 49 UserExtraEmailForm, UserExtraIpForm)
50 50 from rhodecode.model.permission import PermissionModel
51 51 from rhodecode.model.repo_group import RepoGroupModel
52 52 from rhodecode.model.ssh_key import SshKeyModel
53 53 from rhodecode.model.user import UserModel
54 54 from rhodecode.model.user_group import UserGroupModel
55 55 from rhodecode.model.db import (
56 56 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
57 57 UserApiKeys, UserSshKeys, RepoGroup)
58 58 from rhodecode.model.meta import Session
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class AdminUsersView(BaseAppView, DataGridAppView):
64 64
65 65 def load_default_context(self):
66 66 c = self._get_local_tmpl_context()
67 67 return c
68 68
69 69 @LoginRequired()
70 70 @HasPermissionAllDecorator('hg.admin')
71 71 @view_config(
72 72 route_name='users', request_method='GET',
73 73 renderer='rhodecode:templates/admin/users/users.mako')
74 74 def users_list(self):
75 75 c = self.load_default_context()
76 76 return self._get_template_context(c)
77 77
78 78 @LoginRequired()
79 79 @HasPermissionAllDecorator('hg.admin')
80 80 @view_config(
81 81 # renderer defined below
82 82 route_name='users_data', request_method='GET',
83 83 renderer='json_ext', xhr=True)
84 84 def users_list_data(self):
85 85 self.load_default_context()
86 86 column_map = {
87 87 'first_name': 'name',
88 88 'last_name': 'lastname',
89 89 }
90 90 draw, start, limit = self._extract_chunk(self.request)
91 91 search_q, order_by, order_dir = self._extract_ordering(
92 92 self.request, column_map=column_map)
93 93 _render = self.request.get_partial_renderer(
94 94 'rhodecode:templates/data_table/_dt_elements.mako')
95 95
96 96 def user_actions(user_id, username):
97 97 return _render("user_actions", user_id, username)
98 98
99 99 users_data_total_count = User.query()\
100 100 .filter(User.username != User.DEFAULT_USER) \
101 101 .count()
102 102
103 103 users_data_total_inactive_count = User.query()\
104 104 .filter(User.username != User.DEFAULT_USER) \
105 105 .filter(User.active != true())\
106 106 .count()
107 107
108 108 # json generate
109 109 base_q = User.query().filter(User.username != User.DEFAULT_USER)
110 110 base_inactive_q = base_q.filter(User.active != true())
111 111
112 112 if search_q:
113 113 like_expression = u'%{}%'.format(safe_unicode(search_q))
114 114 base_q = base_q.filter(or_(
115 115 User.username.ilike(like_expression),
116 116 User._email.ilike(like_expression),
117 117 User.name.ilike(like_expression),
118 118 User.lastname.ilike(like_expression),
119 119 ))
120 120 base_inactive_q = base_q.filter(User.active != true())
121 121
122 122 users_data_total_filtered_count = base_q.count()
123 123 users_data_total_filtered_inactive_count = base_inactive_q.count()
124 124
125 125 sort_col = getattr(User, order_by, None)
126 126 if sort_col:
127 127 if order_dir == 'asc':
128 128 # handle null values properly to order by NULL last
129 129 if order_by in ['last_activity']:
130 130 sort_col = coalesce(sort_col, datetime.date.max)
131 131 sort_col = sort_col.asc()
132 132 else:
133 133 # handle null values properly to order by NULL last
134 134 if order_by in ['last_activity']:
135 135 sort_col = coalesce(sort_col, datetime.date.min)
136 136 sort_col = sort_col.desc()
137 137
138 138 base_q = base_q.order_by(sort_col)
139 139 base_q = base_q.offset(start).limit(limit)
140 140
141 141 users_list = base_q.all()
142 142
143 143 users_data = []
144 144 for user in users_list:
145 145 users_data.append({
146 146 "username": h.gravatar_with_user(self.request, user.username),
147 147 "email": user.email,
148 148 "first_name": user.first_name,
149 149 "last_name": user.last_name,
150 150 "last_login": h.format_date(user.last_login),
151 151 "last_activity": h.format_date(user.last_activity),
152 152 "active": h.bool2icon(user.active),
153 153 "active_raw": user.active,
154 154 "admin": h.bool2icon(user.admin),
155 155 "extern_type": user.extern_type,
156 156 "extern_name": user.extern_name,
157 157 "action": user_actions(user.user_id, user.username),
158 158 })
159 159 data = ({
160 160 'draw': draw,
161 161 'data': users_data,
162 162 'recordsTotal': users_data_total_count,
163 163 'recordsFiltered': users_data_total_filtered_count,
164 164 'recordsTotalInactive': users_data_total_inactive_count,
165 165 'recordsFilteredInactive': users_data_total_filtered_inactive_count
166 166 })
167 167
168 168 return data
169 169
170 170 def _set_personal_repo_group_template_vars(self, c_obj):
171 171 DummyUser = AttributeDict({
172 172 'username': '${username}',
173 173 'user_id': '${user_id}',
174 174 })
175 175 c_obj.default_create_repo_group = RepoGroupModel() \
176 176 .get_default_create_personal_repo_group()
177 177 c_obj.personal_repo_group_name = RepoGroupModel() \
178 178 .get_personal_group_name(DummyUser)
179 179
180 180 @LoginRequired()
181 181 @HasPermissionAllDecorator('hg.admin')
182 182 @view_config(
183 183 route_name='users_new', request_method='GET',
184 184 renderer='rhodecode:templates/admin/users/user_add.mako')
185 185 def users_new(self):
186 186 _ = self.request.translate
187 187 c = self.load_default_context()
188 188 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
189 189 self._set_personal_repo_group_template_vars(c)
190 190 return self._get_template_context(c)
191 191
192 192 @LoginRequired()
193 193 @HasPermissionAllDecorator('hg.admin')
194 194 @CSRFRequired()
195 195 @view_config(
196 196 route_name='users_create', request_method='POST',
197 197 renderer='rhodecode:templates/admin/users/user_add.mako')
198 198 def users_create(self):
199 199 _ = self.request.translate
200 200 c = self.load_default_context()
201 201 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
202 202 user_model = UserModel()
203 203 user_form = UserForm(self.request.translate)()
204 204 try:
205 205 form_result = user_form.to_python(dict(self.request.POST))
206 206 user = user_model.create(form_result)
207 207 Session().flush()
208 208 creation_data = user.get_api_data()
209 209 username = form_result['username']
210 210
211 211 audit_logger.store_web(
212 212 'user.create', action_data={'data': creation_data},
213 213 user=c.rhodecode_user)
214 214
215 215 user_link = h.link_to(
216 216 h.escape(username),
217 217 h.route_path('user_edit', user_id=user.user_id))
218 218 h.flash(h.literal(_('Created user %(user_link)s')
219 219 % {'user_link': user_link}), category='success')
220 220 Session().commit()
221 221 except formencode.Invalid as errors:
222 222 self._set_personal_repo_group_template_vars(c)
223 223 data = render(
224 224 'rhodecode:templates/admin/users/user_add.mako',
225 225 self._get_template_context(c), self.request)
226 226 html = formencode.htmlfill.render(
227 227 data,
228 228 defaults=errors.value,
229 229 errors=errors.error_dict or {},
230 230 prefix_error=False,
231 231 encoding="UTF-8",
232 232 force_defaults=False
233 233 )
234 234 return Response(html)
235 235 except UserCreationError as e:
236 236 h.flash(e, 'error')
237 237 except Exception:
238 238 log.exception("Exception creation of user")
239 239 h.flash(_('Error occurred during creation of user %s')
240 240 % self.request.POST.get('username'), category='error')
241 241 raise HTTPFound(h.route_path('users'))
242 242
243 243
244 244 class UsersView(UserAppView):
245 245 ALLOW_SCOPED_TOKENS = False
246 246 """
247 247 This view has alternative version inside EE, if modified please take a look
248 248 in there as well.
249 249 """
250 250
251 251 def load_default_context(self):
252 252 c = self._get_local_tmpl_context()
253 253 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
254 254 c.allowed_languages = [
255 255 ('en', 'English (en)'),
256 256 ('de', 'German (de)'),
257 257 ('fr', 'French (fr)'),
258 258 ('it', 'Italian (it)'),
259 259 ('ja', 'Japanese (ja)'),
260 260 ('pl', 'Polish (pl)'),
261 261 ('pt', 'Portuguese (pt)'),
262 262 ('ru', 'Russian (ru)'),
263 263 ('zh', 'Chinese (zh)'),
264 264 ]
265 265 req = self.request
266 266
267 267 c.available_permissions = req.registry.settings['available_permissions']
268 268 PermissionModel().set_global_permission_choices(
269 269 c, gettext_translator=req.translate)
270 270
271 271 return c
272 272
273 273 @LoginRequired()
274 274 @HasPermissionAllDecorator('hg.admin')
275 275 @CSRFRequired()
276 276 @view_config(
277 277 route_name='user_update', request_method='POST',
278 278 renderer='rhodecode:templates/admin/users/user_edit.mako')
279 279 def user_update(self):
280 280 _ = self.request.translate
281 281 c = self.load_default_context()
282 282
283 283 user_id = self.db_user_id
284 284 c.user = self.db_user
285 285
286 286 c.active = 'profile'
287 287 c.extern_type = c.user.extern_type
288 288 c.extern_name = c.user.extern_name
289 289 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
290 290 available_languages = [x[0] for x in c.allowed_languages]
291 291 _form = UserForm(self.request.translate, edit=True,
292 292 available_languages=available_languages,
293 293 old_data={'user_id': user_id,
294 294 'email': c.user.email})()
295 295 form_result = {}
296 296 old_values = c.user.get_api_data()
297 297 try:
298 298 form_result = _form.to_python(dict(self.request.POST))
299 299 skip_attrs = ['extern_type', 'extern_name']
300 300 # TODO: plugin should define if username can be updated
301 301 if c.extern_type != "rhodecode":
302 302 # forbid updating username for external accounts
303 303 skip_attrs.append('username')
304 304
305 305 UserModel().update_user(
306 306 user_id, skip_attrs=skip_attrs, **form_result)
307 307
308 308 audit_logger.store_web(
309 309 'user.edit', action_data={'old_data': old_values},
310 310 user=c.rhodecode_user)
311 311
312 312 Session().commit()
313 313 h.flash(_('User updated successfully'), category='success')
314 314 except formencode.Invalid as errors:
315 315 data = render(
316 316 'rhodecode:templates/admin/users/user_edit.mako',
317 317 self._get_template_context(c), self.request)
318 318 html = formencode.htmlfill.render(
319 319 data,
320 320 defaults=errors.value,
321 321 errors=errors.error_dict or {},
322 322 prefix_error=False,
323 323 encoding="UTF-8",
324 324 force_defaults=False
325 325 )
326 326 return Response(html)
327 327 except UserCreationError as e:
328 328 h.flash(e, 'error')
329 329 except Exception:
330 330 log.exception("Exception updating user")
331 331 h.flash(_('Error occurred during update of user %s')
332 332 % form_result.get('username'), category='error')
333 333 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
334 334
335 335 @LoginRequired()
336 336 @HasPermissionAllDecorator('hg.admin')
337 337 @CSRFRequired()
338 338 @view_config(
339 339 route_name='user_delete', request_method='POST',
340 340 renderer='rhodecode:templates/admin/users/user_edit.mako')
341 341 def user_delete(self):
342 342 _ = self.request.translate
343 343 c = self.load_default_context()
344 344 c.user = self.db_user
345 345
346 346 _repos = c.user.repositories
347 347 _repo_groups = c.user.repository_groups
348 348 _user_groups = c.user.user_groups
349 349
350 350 handle_repos = None
351 351 handle_repo_groups = None
352 352 handle_user_groups = None
353 353 # dummy call for flash of handle
354 354 set_handle_flash_repos = lambda: None
355 355 set_handle_flash_repo_groups = lambda: None
356 356 set_handle_flash_user_groups = lambda: None
357 357
358 358 if _repos and self.request.POST.get('user_repos'):
359 359 do = self.request.POST['user_repos']
360 360 if do == 'detach':
361 361 handle_repos = 'detach'
362 362 set_handle_flash_repos = lambda: h.flash(
363 363 _('Detached %s repositories') % len(_repos),
364 364 category='success')
365 365 elif do == 'delete':
366 366 handle_repos = 'delete'
367 367 set_handle_flash_repos = lambda: h.flash(
368 368 _('Deleted %s repositories') % len(_repos),
369 369 category='success')
370 370
371 371 if _repo_groups and self.request.POST.get('user_repo_groups'):
372 372 do = self.request.POST['user_repo_groups']
373 373 if do == 'detach':
374 374 handle_repo_groups = 'detach'
375 375 set_handle_flash_repo_groups = lambda: h.flash(
376 376 _('Detached %s repository groups') % len(_repo_groups),
377 377 category='success')
378 378 elif do == 'delete':
379 379 handle_repo_groups = 'delete'
380 380 set_handle_flash_repo_groups = lambda: h.flash(
381 381 _('Deleted %s repository groups') % len(_repo_groups),
382 382 category='success')
383 383
384 384 if _user_groups and self.request.POST.get('user_user_groups'):
385 385 do = self.request.POST['user_user_groups']
386 386 if do == 'detach':
387 387 handle_user_groups = 'detach'
388 388 set_handle_flash_user_groups = lambda: h.flash(
389 389 _('Detached %s user groups') % len(_user_groups),
390 390 category='success')
391 391 elif do == 'delete':
392 392 handle_user_groups = 'delete'
393 393 set_handle_flash_user_groups = lambda: h.flash(
394 394 _('Deleted %s user groups') % len(_user_groups),
395 395 category='success')
396 396
397 397 old_values = c.user.get_api_data()
398 398 try:
399 399 UserModel().delete(c.user, handle_repos=handle_repos,
400 400 handle_repo_groups=handle_repo_groups,
401 401 handle_user_groups=handle_user_groups)
402 402
403 403 audit_logger.store_web(
404 404 'user.delete', action_data={'old_data': old_values},
405 405 user=c.rhodecode_user)
406 406
407 407 Session().commit()
408 408 set_handle_flash_repos()
409 409 set_handle_flash_repo_groups()
410 410 set_handle_flash_user_groups()
411 411 h.flash(_('Successfully deleted user'), category='success')
412 412 except (UserOwnsReposException, UserOwnsRepoGroupsException,
413 413 UserOwnsUserGroupsException, DefaultUserException) as e:
414 414 h.flash(e, category='warning')
415 415 except Exception:
416 416 log.exception("Exception during deletion of user")
417 417 h.flash(_('An error occurred during deletion of user'),
418 418 category='error')
419 419 raise HTTPFound(h.route_path('users'))
420 420
421 421 @LoginRequired()
422 422 @HasPermissionAllDecorator('hg.admin')
423 423 @view_config(
424 424 route_name='user_edit', request_method='GET',
425 425 renderer='rhodecode:templates/admin/users/user_edit.mako')
426 426 def user_edit(self):
427 427 _ = self.request.translate
428 428 c = self.load_default_context()
429 429 c.user = self.db_user
430 430
431 431 c.active = 'profile'
432 432 c.extern_type = c.user.extern_type
433 433 c.extern_name = c.user.extern_name
434 434 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
435 435
436 436 defaults = c.user.get_dict()
437 437 defaults.update({'language': c.user.user_data.get('language')})
438 438
439 439 data = render(
440 440 'rhodecode:templates/admin/users/user_edit.mako',
441 441 self._get_template_context(c), self.request)
442 442 html = formencode.htmlfill.render(
443 443 data,
444 444 defaults=defaults,
445 445 encoding="UTF-8",
446 446 force_defaults=False
447 447 )
448 448 return Response(html)
449 449
450 450 @LoginRequired()
451 451 @HasPermissionAllDecorator('hg.admin')
452 452 @view_config(
453 453 route_name='user_edit_advanced', request_method='GET',
454 454 renderer='rhodecode:templates/admin/users/user_edit.mako')
455 455 def user_edit_advanced(self):
456 456 _ = self.request.translate
457 457 c = self.load_default_context()
458 458
459 459 user_id = self.db_user_id
460 460 c.user = self.db_user
461 461
462 462 c.active = 'advanced'
463 463 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
464 464 c.personal_repo_group_name = RepoGroupModel()\
465 465 .get_personal_group_name(c.user)
466 466
467 467 c.user_to_review_rules = sorted(
468 468 (x.user for x in c.user.user_review_rules),
469 469 key=lambda u: u.username.lower())
470 470
471 471 c.first_admin = User.get_first_super_admin()
472 472 defaults = c.user.get_dict()
473 473
474 474 # Interim workaround if the user participated on any pull requests as a
475 475 # reviewer.
476 476 has_review = len(c.user.reviewer_pull_requests)
477 477 c.can_delete_user = not has_review
478 478 c.can_delete_user_message = ''
479 479 inactive_link = h.link_to(
480 480 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
481 481 if has_review == 1:
482 482 c.can_delete_user_message = h.literal(_(
483 483 'The user participates as reviewer in {} pull request and '
484 484 'cannot be deleted. \nYou can set the user to '
485 485 '"{}" instead of deleting it.').format(
486 486 has_review, inactive_link))
487 487 elif has_review:
488 488 c.can_delete_user_message = h.literal(_(
489 489 'The user participates as reviewer in {} pull requests and '
490 490 'cannot be deleted. \nYou can set the user to '
491 491 '"{}" instead of deleting it.').format(
492 492 has_review, inactive_link))
493 493
494 494 data = render(
495 495 'rhodecode:templates/admin/users/user_edit.mako',
496 496 self._get_template_context(c), self.request)
497 497 html = formencode.htmlfill.render(
498 498 data,
499 499 defaults=defaults,
500 500 encoding="UTF-8",
501 501 force_defaults=False
502 502 )
503 503 return Response(html)
504 504
505 505 @LoginRequired()
506 506 @HasPermissionAllDecorator('hg.admin')
507 507 @view_config(
508 508 route_name='user_edit_global_perms', request_method='GET',
509 509 renderer='rhodecode:templates/admin/users/user_edit.mako')
510 510 def user_edit_global_perms(self):
511 511 _ = self.request.translate
512 512 c = self.load_default_context()
513 513 c.user = self.db_user
514 514
515 515 c.active = 'global_perms'
516 516
517 517 c.default_user = User.get_default_user()
518 518 defaults = c.user.get_dict()
519 519 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
520 520 defaults.update(c.default_user.get_default_perms())
521 521 defaults.update(c.user.get_default_perms())
522 522
523 523 data = render(
524 524 'rhodecode:templates/admin/users/user_edit.mako',
525 525 self._get_template_context(c), self.request)
526 526 html = formencode.htmlfill.render(
527 527 data,
528 528 defaults=defaults,
529 529 encoding="UTF-8",
530 530 force_defaults=False
531 531 )
532 532 return Response(html)
533 533
534 534 @LoginRequired()
535 535 @HasPermissionAllDecorator('hg.admin')
536 536 @CSRFRequired()
537 537 @view_config(
538 538 route_name='user_edit_global_perms_update', request_method='POST',
539 539 renderer='rhodecode:templates/admin/users/user_edit.mako')
540 540 def user_edit_global_perms_update(self):
541 541 _ = self.request.translate
542 542 c = self.load_default_context()
543 543
544 544 user_id = self.db_user_id
545 545 c.user = self.db_user
546 546
547 547 c.active = 'global_perms'
548 548 try:
549 549 # first stage that verifies the checkbox
550 550 _form = UserIndividualPermissionsForm(self.request.translate)
551 551 form_result = _form.to_python(dict(self.request.POST))
552 552 inherit_perms = form_result['inherit_default_permissions']
553 553 c.user.inherit_default_permissions = inherit_perms
554 554 Session().add(c.user)
555 555
556 556 if not inherit_perms:
557 557 # only update the individual ones if we un check the flag
558 558 _form = UserPermissionsForm(
559 559 self.request.translate,
560 560 [x[0] for x in c.repo_create_choices],
561 561 [x[0] for x in c.repo_create_on_write_choices],
562 562 [x[0] for x in c.repo_group_create_choices],
563 563 [x[0] for x in c.user_group_create_choices],
564 564 [x[0] for x in c.fork_choices],
565 565 [x[0] for x in c.inherit_default_permission_choices])()
566 566
567 567 form_result = _form.to_python(dict(self.request.POST))
568 568 form_result.update({'perm_user_id': c.user.user_id})
569 569
570 570 PermissionModel().update_user_permissions(form_result)
571 571
572 572 # TODO(marcink): implement global permissions
573 573 # audit_log.store_web('user.edit.permissions')
574 574
575 575 Session().commit()
576 576 h.flash(_('User global permissions updated successfully'),
577 577 category='success')
578 578
579 579 except formencode.Invalid as errors:
580 580 data = render(
581 581 'rhodecode:templates/admin/users/user_edit.mako',
582 582 self._get_template_context(c), self.request)
583 583 html = formencode.htmlfill.render(
584 584 data,
585 585 defaults=errors.value,
586 586 errors=errors.error_dict or {},
587 587 prefix_error=False,
588 588 encoding="UTF-8",
589 589 force_defaults=False
590 590 )
591 591 return Response(html)
592 592 except Exception:
593 593 log.exception("Exception during permissions saving")
594 594 h.flash(_('An error occurred during permissions saving'),
595 595 category='error')
596 596 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
597 597
598 598 @LoginRequired()
599 599 @HasPermissionAllDecorator('hg.admin')
600 600 @CSRFRequired()
601 601 @view_config(
602 602 route_name='user_force_password_reset', request_method='POST',
603 603 renderer='rhodecode:templates/admin/users/user_edit.mako')
604 604 def user_force_password_reset(self):
605 605 """
606 606 toggle reset password flag for this user
607 607 """
608 608 _ = self.request.translate
609 609 c = self.load_default_context()
610 610
611 611 user_id = self.db_user_id
612 612 c.user = self.db_user
613 613
614 614 try:
615 615 old_value = c.user.user_data.get('force_password_change')
616 616 c.user.update_userdata(force_password_change=not old_value)
617 617
618 618 if old_value:
619 619 msg = _('Force password change disabled for user')
620 620 audit_logger.store_web(
621 621 'user.edit.password_reset.disabled',
622 622 user=c.rhodecode_user)
623 623 else:
624 624 msg = _('Force password change enabled for user')
625 625 audit_logger.store_web(
626 626 'user.edit.password_reset.enabled',
627 627 user=c.rhodecode_user)
628 628
629 629 Session().commit()
630 630 h.flash(msg, category='success')
631 631 except Exception:
632 632 log.exception("Exception during password reset for user")
633 633 h.flash(_('An error occurred during password reset for user'),
634 634 category='error')
635 635
636 636 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
637 637
638 638 @LoginRequired()
639 639 @HasPermissionAllDecorator('hg.admin')
640 640 @CSRFRequired()
641 641 @view_config(
642 642 route_name='user_create_personal_repo_group', request_method='POST',
643 643 renderer='rhodecode:templates/admin/users/user_edit.mako')
644 644 def user_create_personal_repo_group(self):
645 645 """
646 646 Create personal repository group for this user
647 647 """
648 648 from rhodecode.model.repo_group import RepoGroupModel
649 649
650 650 _ = self.request.translate
651 651 c = self.load_default_context()
652 652
653 653 user_id = self.db_user_id
654 654 c.user = self.db_user
655 655
656 656 personal_repo_group = RepoGroup.get_user_personal_repo_group(
657 657 c.user.user_id)
658 658 if personal_repo_group:
659 659 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
660 660
661 661 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
662 662 c.user)
663 663 named_personal_group = RepoGroup.get_by_group_name(
664 664 personal_repo_group_name)
665 665 try:
666 666
667 667 if named_personal_group and named_personal_group.user_id == c.user.user_id:
668 668 # migrate the same named group, and mark it as personal
669 669 named_personal_group.personal = True
670 670 Session().add(named_personal_group)
671 671 Session().commit()
672 672 msg = _('Linked repository group `%s` as personal' % (
673 673 personal_repo_group_name,))
674 674 h.flash(msg, category='success')
675 675 elif not named_personal_group:
676 676 RepoGroupModel().create_personal_repo_group(c.user)
677 677
678 678 msg = _('Created repository group `%s`' % (
679 679 personal_repo_group_name,))
680 680 h.flash(msg, category='success')
681 681 else:
682 682 msg = _('Repository group `%s` is already taken' % (
683 683 personal_repo_group_name,))
684 684 h.flash(msg, category='warning')
685 685 except Exception:
686 686 log.exception("Exception during repository group creation")
687 687 msg = _(
688 688 'An error occurred during repository group creation for user')
689 689 h.flash(msg, category='error')
690 690 Session().rollback()
691 691
692 692 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
693 693
694 694 @LoginRequired()
695 695 @HasPermissionAllDecorator('hg.admin')
696 696 @view_config(
697 697 route_name='edit_user_auth_tokens', request_method='GET',
698 698 renderer='rhodecode:templates/admin/users/user_edit.mako')
699 699 def auth_tokens(self):
700 700 _ = self.request.translate
701 701 c = self.load_default_context()
702 702 c.user = self.db_user
703 703
704 704 c.active = 'auth_tokens'
705 705
706 706 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
707 707 c.role_values = [
708 708 (x, AuthTokenModel.cls._get_role_name(x))
709 709 for x in AuthTokenModel.cls.ROLES]
710 710 c.role_options = [(c.role_values, _("Role"))]
711 711 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
712 712 c.user.user_id, show_expired=True)
713 713 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
714 714 return self._get_template_context(c)
715 715
716 716 def maybe_attach_token_scope(self, token):
717 717 # implemented in EE edition
718 718 pass
719 719
720 720 @LoginRequired()
721 721 @HasPermissionAllDecorator('hg.admin')
722 722 @CSRFRequired()
723 723 @view_config(
724 724 route_name='edit_user_auth_tokens_add', request_method='POST')
725 725 def auth_tokens_add(self):
726 726 _ = self.request.translate
727 727 c = self.load_default_context()
728 728
729 729 user_id = self.db_user_id
730 730 c.user = self.db_user
731 731
732 732 user_data = c.user.get_api_data()
733 733 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
734 734 description = self.request.POST.get('description')
735 735 role = self.request.POST.get('role')
736 736
737 737 token = AuthTokenModel().create(
738 738 c.user.user_id, description, lifetime, role)
739 739 token_data = token.get_api_data()
740 740
741 741 self.maybe_attach_token_scope(token)
742 742 audit_logger.store_web(
743 743 'user.edit.token.add', action_data={
744 744 'data': {'token': token_data, 'user': user_data}},
745 745 user=self._rhodecode_user, )
746 746 Session().commit()
747 747
748 748 h.flash(_("Auth token successfully created"), category='success')
749 749 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
750 750
751 751 @LoginRequired()
752 752 @HasPermissionAllDecorator('hg.admin')
753 753 @CSRFRequired()
754 754 @view_config(
755 755 route_name='edit_user_auth_tokens_delete', request_method='POST')
756 756 def auth_tokens_delete(self):
757 757 _ = self.request.translate
758 758 c = self.load_default_context()
759 759
760 760 user_id = self.db_user_id
761 761 c.user = self.db_user
762 762
763 763 user_data = c.user.get_api_data()
764 764
765 765 del_auth_token = self.request.POST.get('del_auth_token')
766 766
767 767 if del_auth_token:
768 768 token = UserApiKeys.get_or_404(del_auth_token)
769 769 token_data = token.get_api_data()
770 770
771 771 AuthTokenModel().delete(del_auth_token, c.user.user_id)
772 772 audit_logger.store_web(
773 773 'user.edit.token.delete', action_data={
774 774 'data': {'token': token_data, 'user': user_data}},
775 775 user=self._rhodecode_user,)
776 776 Session().commit()
777 777 h.flash(_("Auth token successfully deleted"), category='success')
778 778
779 779 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
780 780
781 781 @LoginRequired()
782 782 @HasPermissionAllDecorator('hg.admin')
783 783 @view_config(
784 784 route_name='edit_user_ssh_keys', request_method='GET',
785 785 renderer='rhodecode:templates/admin/users/user_edit.mako')
786 786 def ssh_keys(self):
787 787 _ = self.request.translate
788 788 c = self.load_default_context()
789 789 c.user = self.db_user
790 790
791 791 c.active = 'ssh_keys'
792 792 c.default_key = self.request.GET.get('default_key')
793 793 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
794 794 return self._get_template_context(c)
795 795
796 796 @LoginRequired()
797 797 @HasPermissionAllDecorator('hg.admin')
798 798 @view_config(
799 799 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
800 800 renderer='rhodecode:templates/admin/users/user_edit.mako')
801 801 def ssh_keys_generate_keypair(self):
802 802 _ = self.request.translate
803 803 c = self.load_default_context()
804 804
805 805 c.user = self.db_user
806 806
807 807 c.active = 'ssh_keys_generate'
808 808 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
809 809 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
810 810
811 811 return self._get_template_context(c)
812 812
813 813 @LoginRequired()
814 814 @HasPermissionAllDecorator('hg.admin')
815 815 @CSRFRequired()
816 816 @view_config(
817 817 route_name='edit_user_ssh_keys_add', request_method='POST')
818 818 def ssh_keys_add(self):
819 819 _ = self.request.translate
820 820 c = self.load_default_context()
821 821
822 822 user_id = self.db_user_id
823 823 c.user = self.db_user
824 824
825 825 user_data = c.user.get_api_data()
826 826 key_data = self.request.POST.get('key_data')
827 827 description = self.request.POST.get('description')
828 828
829 829 fingerprint = 'unknown'
830 830 try:
831 831 if not key_data:
832 832 raise ValueError('Please add a valid public key')
833 833
834 834 key = SshKeyModel().parse_key(key_data.strip())
835 835 fingerprint = key.hash_md5()
836 836
837 837 ssh_key = SshKeyModel().create(
838 c.user.user_id, fingerprint, key_data, description)
838 c.user.user_id, fingerprint, key.keydata, description)
839 839 ssh_key_data = ssh_key.get_api_data()
840 840
841 841 audit_logger.store_web(
842 842 'user.edit.ssh_key.add', action_data={
843 843 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
844 844 user=self._rhodecode_user, )
845 845 Session().commit()
846 846
847 847 # Trigger an event on change of keys.
848 848 trigger(SshKeyFileChangeEvent(), self.request.registry)
849 849
850 850 h.flash(_("Ssh Key successfully created"), category='success')
851 851
852 852 except IntegrityError:
853 853 log.exception("Exception during ssh key saving")
854 854 err = 'Such key with fingerprint `{}` already exists, ' \
855 855 'please use a different one'.format(fingerprint)
856 856 h.flash(_('An error occurred during ssh key saving: {}').format(err),
857 857 category='error')
858 858 except Exception as e:
859 859 log.exception("Exception during ssh key saving")
860 860 h.flash(_('An error occurred during ssh key saving: {}').format(e),
861 861 category='error')
862 862
863 863 return HTTPFound(
864 864 h.route_path('edit_user_ssh_keys', user_id=user_id))
865 865
866 866 @LoginRequired()
867 867 @HasPermissionAllDecorator('hg.admin')
868 868 @CSRFRequired()
869 869 @view_config(
870 870 route_name='edit_user_ssh_keys_delete', request_method='POST')
871 871 def ssh_keys_delete(self):
872 872 _ = self.request.translate
873 873 c = self.load_default_context()
874 874
875 875 user_id = self.db_user_id
876 876 c.user = self.db_user
877 877
878 878 user_data = c.user.get_api_data()
879 879
880 880 del_ssh_key = self.request.POST.get('del_ssh_key')
881 881
882 882 if del_ssh_key:
883 883 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
884 884 ssh_key_data = ssh_key.get_api_data()
885 885
886 886 SshKeyModel().delete(del_ssh_key, c.user.user_id)
887 887 audit_logger.store_web(
888 888 'user.edit.ssh_key.delete', action_data={
889 889 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
890 890 user=self._rhodecode_user,)
891 891 Session().commit()
892 892 # Trigger an event on change of keys.
893 893 trigger(SshKeyFileChangeEvent(), self.request.registry)
894 894 h.flash(_("Ssh key successfully deleted"), category='success')
895 895
896 896 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
897 897
898 898 @LoginRequired()
899 899 @HasPermissionAllDecorator('hg.admin')
900 900 @view_config(
901 901 route_name='edit_user_emails', request_method='GET',
902 902 renderer='rhodecode:templates/admin/users/user_edit.mako')
903 903 def emails(self):
904 904 _ = self.request.translate
905 905 c = self.load_default_context()
906 906 c.user = self.db_user
907 907
908 908 c.active = 'emails'
909 909 c.user_email_map = UserEmailMap.query() \
910 910 .filter(UserEmailMap.user == c.user).all()
911 911
912 912 return self._get_template_context(c)
913 913
914 914 @LoginRequired()
915 915 @HasPermissionAllDecorator('hg.admin')
916 916 @CSRFRequired()
917 917 @view_config(
918 918 route_name='edit_user_emails_add', request_method='POST')
919 919 def emails_add(self):
920 920 _ = self.request.translate
921 921 c = self.load_default_context()
922 922
923 923 user_id = self.db_user_id
924 924 c.user = self.db_user
925 925
926 926 email = self.request.POST.get('new_email')
927 927 user_data = c.user.get_api_data()
928 928 try:
929 929
930 930 form = UserExtraEmailForm(self.request.translate)()
931 931 data = form.to_python({'email': email})
932 932 email = data['email']
933 933
934 934 UserModel().add_extra_email(c.user.user_id, email)
935 935 audit_logger.store_web(
936 936 'user.edit.email.add',
937 937 action_data={'email': email, 'user': user_data},
938 938 user=self._rhodecode_user)
939 939 Session().commit()
940 940 h.flash(_("Added new email address `%s` for user account") % email,
941 941 category='success')
942 942 except formencode.Invalid as error:
943 943 h.flash(h.escape(error.error_dict['email']), category='error')
944 944 except IntegrityError:
945 945 log.warning("Email %s already exists", email)
946 946 h.flash(_('Email `{}` is already registered for another user.').format(email),
947 947 category='error')
948 948 except Exception:
949 949 log.exception("Exception during email saving")
950 950 h.flash(_('An error occurred during email saving'),
951 951 category='error')
952 952 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
953 953
954 954 @LoginRequired()
955 955 @HasPermissionAllDecorator('hg.admin')
956 956 @CSRFRequired()
957 957 @view_config(
958 958 route_name='edit_user_emails_delete', request_method='POST')
959 959 def emails_delete(self):
960 960 _ = self.request.translate
961 961 c = self.load_default_context()
962 962
963 963 user_id = self.db_user_id
964 964 c.user = self.db_user
965 965
966 966 email_id = self.request.POST.get('del_email_id')
967 967 user_model = UserModel()
968 968
969 969 email = UserEmailMap.query().get(email_id).email
970 970 user_data = c.user.get_api_data()
971 971 user_model.delete_extra_email(c.user.user_id, email_id)
972 972 audit_logger.store_web(
973 973 'user.edit.email.delete',
974 974 action_data={'email': email, 'user': user_data},
975 975 user=self._rhodecode_user)
976 976 Session().commit()
977 977 h.flash(_("Removed email address from user account"),
978 978 category='success')
979 979 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
980 980
981 981 @LoginRequired()
982 982 @HasPermissionAllDecorator('hg.admin')
983 983 @view_config(
984 984 route_name='edit_user_ips', request_method='GET',
985 985 renderer='rhodecode:templates/admin/users/user_edit.mako')
986 986 def ips(self):
987 987 _ = self.request.translate
988 988 c = self.load_default_context()
989 989 c.user = self.db_user
990 990
991 991 c.active = 'ips'
992 992 c.user_ip_map = UserIpMap.query() \
993 993 .filter(UserIpMap.user == c.user).all()
994 994
995 995 c.inherit_default_ips = c.user.inherit_default_permissions
996 996 c.default_user_ip_map = UserIpMap.query() \
997 997 .filter(UserIpMap.user == User.get_default_user()).all()
998 998
999 999 return self._get_template_context(c)
1000 1000
1001 1001 @LoginRequired()
1002 1002 @HasPermissionAllDecorator('hg.admin')
1003 1003 @CSRFRequired()
1004 1004 @view_config(
1005 1005 route_name='edit_user_ips_add', request_method='POST')
1006 1006 # NOTE(marcink): this view is allowed for default users, as we can
1007 1007 # edit their IP white list
1008 1008 def ips_add(self):
1009 1009 _ = self.request.translate
1010 1010 c = self.load_default_context()
1011 1011
1012 1012 user_id = self.db_user_id
1013 1013 c.user = self.db_user
1014 1014
1015 1015 user_model = UserModel()
1016 1016 desc = self.request.POST.get('description')
1017 1017 try:
1018 1018 ip_list = user_model.parse_ip_range(
1019 1019 self.request.POST.get('new_ip'))
1020 1020 except Exception as e:
1021 1021 ip_list = []
1022 1022 log.exception("Exception during ip saving")
1023 1023 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1024 1024 category='error')
1025 1025 added = []
1026 1026 user_data = c.user.get_api_data()
1027 1027 for ip in ip_list:
1028 1028 try:
1029 1029 form = UserExtraIpForm(self.request.translate)()
1030 1030 data = form.to_python({'ip': ip})
1031 1031 ip = data['ip']
1032 1032
1033 1033 user_model.add_extra_ip(c.user.user_id, ip, desc)
1034 1034 audit_logger.store_web(
1035 1035 'user.edit.ip.add',
1036 1036 action_data={'ip': ip, 'user': user_data},
1037 1037 user=self._rhodecode_user)
1038 1038 Session().commit()
1039 1039 added.append(ip)
1040 1040 except formencode.Invalid as error:
1041 1041 msg = error.error_dict['ip']
1042 1042 h.flash(msg, category='error')
1043 1043 except Exception:
1044 1044 log.exception("Exception during ip saving")
1045 1045 h.flash(_('An error occurred during ip saving'),
1046 1046 category='error')
1047 1047 if added:
1048 1048 h.flash(
1049 1049 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1050 1050 category='success')
1051 1051 if 'default_user' in self.request.POST:
1052 1052 # case for editing global IP list we do it for 'DEFAULT' user
1053 1053 raise HTTPFound(h.route_path('admin_permissions_ips'))
1054 1054 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1055 1055
1056 1056 @LoginRequired()
1057 1057 @HasPermissionAllDecorator('hg.admin')
1058 1058 @CSRFRequired()
1059 1059 @view_config(
1060 1060 route_name='edit_user_ips_delete', request_method='POST')
1061 1061 # NOTE(marcink): this view is allowed for default users, as we can
1062 1062 # edit their IP white list
1063 1063 def ips_delete(self):
1064 1064 _ = self.request.translate
1065 1065 c = self.load_default_context()
1066 1066
1067 1067 user_id = self.db_user_id
1068 1068 c.user = self.db_user
1069 1069
1070 1070 ip_id = self.request.POST.get('del_ip_id')
1071 1071 user_model = UserModel()
1072 1072 user_data = c.user.get_api_data()
1073 1073 ip = UserIpMap.query().get(ip_id).ip_addr
1074 1074 user_model.delete_extra_ip(c.user.user_id, ip_id)
1075 1075 audit_logger.store_web(
1076 1076 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1077 1077 user=self._rhodecode_user)
1078 1078 Session().commit()
1079 1079 h.flash(_("Removed ip address from user whitelist"), category='success')
1080 1080
1081 1081 if 'default_user' in self.request.POST:
1082 1082 # case for editing global IP list we do it for 'DEFAULT' user
1083 1083 raise HTTPFound(h.route_path('admin_permissions_ips'))
1084 1084 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1085 1085
1086 1086 @LoginRequired()
1087 1087 @HasPermissionAllDecorator('hg.admin')
1088 1088 @view_config(
1089 1089 route_name='edit_user_groups_management', request_method='GET',
1090 1090 renderer='rhodecode:templates/admin/users/user_edit.mako')
1091 1091 def groups_management(self):
1092 1092 c = self.load_default_context()
1093 1093 c.user = self.db_user
1094 1094 c.data = c.user.group_member
1095 1095
1096 1096 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1097 1097 for group in c.user.group_member]
1098 1098 c.groups = json.dumps(groups)
1099 1099 c.active = 'groups'
1100 1100
1101 1101 return self._get_template_context(c)
1102 1102
1103 1103 @LoginRequired()
1104 1104 @HasPermissionAllDecorator('hg.admin')
1105 1105 @CSRFRequired()
1106 1106 @view_config(
1107 1107 route_name='edit_user_groups_management_updates', request_method='POST')
1108 1108 def groups_management_updates(self):
1109 1109 _ = self.request.translate
1110 1110 c = self.load_default_context()
1111 1111
1112 1112 user_id = self.db_user_id
1113 1113 c.user = self.db_user
1114 1114
1115 1115 user_groups = set(self.request.POST.getall('users_group_id'))
1116 1116 user_groups_objects = []
1117 1117
1118 1118 for ugid in user_groups:
1119 1119 user_groups_objects.append(
1120 1120 UserGroupModel().get_group(safe_int(ugid)))
1121 1121 user_group_model = UserGroupModel()
1122 1122 added_to_groups, removed_from_groups = \
1123 1123 user_group_model.change_groups(c.user, user_groups_objects)
1124 1124
1125 1125 user_data = c.user.get_api_data()
1126 1126 for user_group_id in added_to_groups:
1127 1127 user_group = UserGroup.get(user_group_id)
1128 1128 old_values = user_group.get_api_data()
1129 1129 audit_logger.store_web(
1130 1130 'user_group.edit.member.add',
1131 1131 action_data={'user': user_data, 'old_data': old_values},
1132 1132 user=self._rhodecode_user)
1133 1133
1134 1134 for user_group_id in removed_from_groups:
1135 1135 user_group = UserGroup.get(user_group_id)
1136 1136 old_values = user_group.get_api_data()
1137 1137 audit_logger.store_web(
1138 1138 'user_group.edit.member.delete',
1139 1139 action_data={'user': user_data, 'old_data': old_values},
1140 1140 user=self._rhodecode_user)
1141 1141
1142 1142 Session().commit()
1143 1143 c.active = 'user_groups_management'
1144 1144 h.flash(_("Groups successfully changed"), category='success')
1145 1145
1146 1146 return HTTPFound(h.route_path(
1147 1147 'edit_user_groups_management', user_id=user_id))
1148 1148
1149 1149 @LoginRequired()
1150 1150 @HasPermissionAllDecorator('hg.admin')
1151 1151 @view_config(
1152 1152 route_name='edit_user_audit_logs', request_method='GET',
1153 1153 renderer='rhodecode:templates/admin/users/user_edit.mako')
1154 1154 def user_audit_logs(self):
1155 1155 _ = self.request.translate
1156 1156 c = self.load_default_context()
1157 1157 c.user = self.db_user
1158 1158
1159 1159 c.active = 'audit'
1160 1160
1161 1161 p = safe_int(self.request.GET.get('page', 1), 1)
1162 1162
1163 1163 filter_term = self.request.GET.get('filter')
1164 1164 user_log = UserModel().get_user_log(c.user, filter_term)
1165 1165
1166 1166 def url_generator(**kw):
1167 1167 if filter_term:
1168 1168 kw['filter'] = filter_term
1169 1169 return self.request.current_route_path(_query=kw)
1170 1170
1171 1171 c.audit_logs = h.Page(
1172 1172 user_log, page=p, items_per_page=10, url=url_generator)
1173 1173 c.filter_term = filter_term
1174 1174 return self._get_template_context(c)
1175 1175
1176 1176 @LoginRequired()
1177 1177 @HasPermissionAllDecorator('hg.admin')
1178 1178 @view_config(
1179 1179 route_name='edit_user_perms_summary', request_method='GET',
1180 1180 renderer='rhodecode:templates/admin/users/user_edit.mako')
1181 1181 def user_perms_summary(self):
1182 1182 _ = self.request.translate
1183 1183 c = self.load_default_context()
1184 1184 c.user = self.db_user
1185 1185
1186 1186 c.active = 'perms_summary'
1187 1187 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1188 1188
1189 1189 return self._get_template_context(c)
1190 1190
1191 1191 @LoginRequired()
1192 1192 @HasPermissionAllDecorator('hg.admin')
1193 1193 @view_config(
1194 1194 route_name='edit_user_perms_summary_json', request_method='GET',
1195 1195 renderer='json_ext')
1196 1196 def user_perms_summary_json(self):
1197 1197 self.load_default_context()
1198 1198 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1199 1199
1200 1200 return perm_user.permissions
@@ -1,155 +1,155 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 from pyramid.httpexceptions import HTTPFound
24 24 from pyramid.view import view_config
25 25
26 26 from rhodecode.apps._base import BaseAppView, DataGridAppView
27 27 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
28 28 from rhodecode.events import trigger
29 29 from rhodecode.lib import helpers as h
30 30 from rhodecode.lib import audit_logger
31 31 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
32 32 from rhodecode.model.db import IntegrityError, UserSshKeys
33 33 from rhodecode.model.meta import Session
34 34 from rhodecode.model.ssh_key import SshKeyModel
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class MyAccountSshKeysView(BaseAppView, DataGridAppView):
40 40
41 41 def load_default_context(self):
42 42 c = self._get_local_tmpl_context()
43 43 c.user = c.auth_user.get_instance()
44 44
45 45 c.ssh_enabled = self.request.registry.settings.get(
46 46 'ssh.generate_authorized_keyfile')
47 47
48 48 return c
49 49
50 50 @LoginRequired()
51 51 @NotAnonymous()
52 52 @view_config(
53 53 route_name='my_account_ssh_keys', request_method='GET',
54 54 renderer='rhodecode:templates/admin/my_account/my_account.mako')
55 55 def my_account_ssh_keys(self):
56 56 _ = self.request.translate
57 57
58 58 c = self.load_default_context()
59 59 c.active = 'ssh_keys'
60 60 c.default_key = self.request.GET.get('default_key')
61 61 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
62 62 return self._get_template_context(c)
63 63
64 64 @LoginRequired()
65 65 @NotAnonymous()
66 66 @view_config(
67 67 route_name='my_account_ssh_keys_generate', request_method='GET',
68 68 renderer='rhodecode:templates/admin/my_account/my_account.mako')
69 69 def ssh_keys_generate_keypair(self):
70 70 _ = self.request.translate
71 71 c = self.load_default_context()
72 72
73 73 c.active = 'ssh_keys_generate'
74 74 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
75 75 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
76 76 c.target_form_url = h.route_path(
77 77 'my_account_ssh_keys', _query=dict(default_key=c.public))
78 78 return self._get_template_context(c)
79 79
80 80 @LoginRequired()
81 81 @NotAnonymous()
82 82 @CSRFRequired()
83 83 @view_config(
84 84 route_name='my_account_ssh_keys_add', request_method='POST',)
85 85 def my_account_ssh_keys_add(self):
86 86 _ = self.request.translate
87 87 c = self.load_default_context()
88 88
89 89 user_data = c.user.get_api_data()
90 90 key_data = self.request.POST.get('key_data')
91 91 description = self.request.POST.get('description')
92 92 fingerprint = 'unknown'
93 93 try:
94 94 if not key_data:
95 95 raise ValueError('Please add a valid public key')
96 96
97 97 key = SshKeyModel().parse_key(key_data.strip())
98 98 fingerprint = key.hash_md5()
99 99
100 100 ssh_key = SshKeyModel().create(
101 c.user.user_id, fingerprint, key_data, description)
101 c.user.user_id, fingerprint, key.keydata, description)
102 102 ssh_key_data = ssh_key.get_api_data()
103 103
104 104 audit_logger.store_web(
105 105 'user.edit.ssh_key.add', action_data={
106 106 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
107 107 user=self._rhodecode_user, )
108 108 Session().commit()
109 109
110 110 # Trigger an event on change of keys.
111 111 trigger(SshKeyFileChangeEvent(), self.request.registry)
112 112
113 113 h.flash(_("Ssh Key successfully created"), category='success')
114 114
115 115 except IntegrityError:
116 116 log.exception("Exception during ssh key saving")
117 117 err = 'Such key with fingerprint `{}` already exists, ' \
118 118 'please use a different one'.format(fingerprint)
119 119 h.flash(_('An error occurred during ssh key saving: {}').format(err),
120 120 category='error')
121 121 except Exception as e:
122 122 log.exception("Exception during ssh key saving")
123 123 h.flash(_('An error occurred during ssh key saving: {}').format(e),
124 124 category='error')
125 125
126 126 return HTTPFound(h.route_path('my_account_ssh_keys'))
127 127
128 128 @LoginRequired()
129 129 @NotAnonymous()
130 130 @CSRFRequired()
131 131 @view_config(
132 132 route_name='my_account_ssh_keys_delete', request_method='POST')
133 133 def my_account_ssh_keys_delete(self):
134 134 _ = self.request.translate
135 135 c = self.load_default_context()
136 136
137 137 user_data = c.user.get_api_data()
138 138
139 139 del_ssh_key = self.request.POST.get('del_ssh_key')
140 140
141 141 if del_ssh_key:
142 142 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
143 143 ssh_key_data = ssh_key.get_api_data()
144 144
145 145 SshKeyModel().delete(del_ssh_key, c.user.user_id)
146 146 audit_logger.store_web(
147 147 'user.edit.ssh_key.delete', action_data={
148 148 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
149 149 user=self._rhodecode_user,)
150 150 Session().commit()
151 151 # Trigger an event on change of keys.
152 152 trigger(SshKeyFileChangeEvent(), self.request.registry)
153 153 h.flash(_("Ssh key successfully deleted"), category='success')
154 154
155 155 return HTTPFound(h.route_path('my_account_ssh_keys'))
@@ -1,125 +1,132 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import stat
23 23 import logging
24 24 import tempfile
25 25 import datetime
26 26
27 27 from . import config_keys
28 28 from rhodecode.model.db import true, joinedload, User, UserSshKeys
29 29
30 30
31 31 log = logging.getLogger(__name__)
32 32
33 33 HEADER = \
34 34 "# This file is managed by RhodeCode, please do not edit it manually. # \n" \
35 35 "# Current entries: {}, create date: UTC:{}.\n"
36 36
37 37 # Default SSH options for authorized_keys file, can be override via .ini
38 38 SSH_OPTS = 'no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding'
39 39
40 40
41 41 def get_all_active_keys():
42 42 result = UserSshKeys.query() \
43 43 .options(joinedload(UserSshKeys.user)) \
44 44 .filter(UserSshKeys.user != User.get_default_user()) \
45 45 .filter(User.active == true()) \
46 46 .all()
47 47 return result
48 48
49 49
50 50 def _generate_ssh_authorized_keys_file(
51 51 authorized_keys_file_path, ssh_wrapper_cmd, allow_shell, ssh_opts, debug):
52 52
53 53 authorized_keys_file_path = os.path.abspath(
54 54 os.path.expanduser(authorized_keys_file_path))
55 55
56 56 import rhodecode
57 57 all_active_keys = get_all_active_keys()
58 58
59 59 if allow_shell:
60 60 ssh_wrapper_cmd = ssh_wrapper_cmd + ' --shell'
61 61 if debug:
62 62 ssh_wrapper_cmd = ssh_wrapper_cmd + ' --debug'
63 63
64 64 if not os.path.isfile(authorized_keys_file_path):
65 65 log.debug('Creating file at %s', authorized_keys_file_path)
66 66 with open(authorized_keys_file_path, 'w'):
67 67 pass
68 68
69 69 if not os.access(authorized_keys_file_path, os.R_OK):
70 70 raise OSError('Access to file {} is without read access'.format(
71 71 authorized_keys_file_path))
72 72
73 73 line_tmpl = '{ssh_opts},command="{wrapper_command} {ini_path} --user-id={user_id} --user={user} --key-id={user_key_id}" {key}\n'
74 74
75 75 fd, tmp_authorized_keys = tempfile.mkstemp(
76 76 '.authorized_keys_write',
77 77 dir=os.path.dirname(authorized_keys_file_path))
78 78
79 79 now = datetime.datetime.utcnow().isoformat()
80 80 keys_file = os.fdopen(fd, 'wb')
81 81 keys_file.write(HEADER.format(len(all_active_keys), now))
82 82 ini_path = rhodecode.CONFIG['__file__']
83 83
84 84 for user_key in all_active_keys:
85 85 username = user_key.user.username
86 86 user_id = user_key.user.user_id
87 # replace all newline from ends and inside
88 safe_key_data = user_key.ssh_key_data\
89 .strip()\
90 .replace('\n', ' ') \
91 .replace('\t', ' ') \
92 .replace('\r', ' ')
87 93
88 keys_file.write(
89 line_tmpl.format(
90 ssh_opts=ssh_opts or SSH_OPTS,
91 wrapper_command=ssh_wrapper_cmd,
92 ini_path=ini_path,
93 user_id=user_id,
94 user=username,
95 user_key_id=user_key.ssh_key_id,
96 key=user_key.ssh_key_data))
94 line = line_tmpl.format(
95 ssh_opts=ssh_opts or SSH_OPTS,
96 wrapper_command=ssh_wrapper_cmd,
97 ini_path=ini_path,
98 user_id=user_id,
99 user=username,
100 user_key_id=user_key.ssh_key_id,
101 key=safe_key_data)
102
103 keys_file.write(line)
97 104 log.debug('addkey: Key added for user: `%s`', username)
98 105 keys_file.close()
99 106
100 107 # Explicitly setting read-only permissions to authorized_keys
101 108 os.chmod(tmp_authorized_keys, stat.S_IRUSR | stat.S_IWUSR)
102 109 # Rename is atomic operation
103 110 os.rename(tmp_authorized_keys, authorized_keys_file_path)
104 111
105 112
106 113 def generate_ssh_authorized_keys_file(registry):
107 114 log.info('Generating new authorized key file')
108 115
109 116 authorized_keys_file_path = registry.settings.get(
110 117 config_keys.authorized_keys_file_path)
111 118
112 119 ssh_wrapper_cmd = registry.settings.get(
113 120 config_keys.wrapper_cmd)
114 121 allow_shell = registry.settings.get(
115 122 config_keys.wrapper_allow_shell)
116 123 ssh_opts = registry.settings.get(
117 124 config_keys.authorized_keys_line_ssh_opts)
118 125 debug = registry.settings.get(
119 126 config_keys.enable_debug_logging)
120 127
121 128 _generate_ssh_authorized_keys_file(
122 129 authorized_keys_file_path, ssh_wrapper_cmd, allow_shell, ssh_opts,
123 130 debug)
124 131
125 132 return 0
@@ -1,51 +1,51 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('New SSH Key generated')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <p>
7 7 ${_('Below is a 2048 bit generated SSH RSA key. You can use it to access RhodeCode via the SSH wrapper.')}
8 8 </p>
9 9 <h4>${_('Private key')}</h4>
10 10 <pre>
11 # Save the content as
12 # Windows: /Users/<username>/.ssh/id_rsa_rhodecode_access_priv.key
13 # macOS: /Users/<yourname>/.ssh/id_rsa_rhodecode_access_priv.key
14 # Linux: /home/<username>/.ssh/id_rsa_rhodecode_access_priv.key
11 # Save the below content as
12 # Windows: /Users/{username}/.ssh/id_rsa_rhodecode_access_priv.key
13 # macOS: /Users/{yourname}/.ssh/id_rsa_rhodecode_access_priv.key
14 # Linux: /home/{username}/.ssh/id_rsa_rhodecode_access_priv.key
15 15
16 16 # Change permissions to 0600 to make it secure, and usable.
17 e.g chmod 0600 /home/<username>/.ssh/id_rsa_rhodecode_access_priv.key
17 e.g chmod 0600 /home/{username}/.ssh/id_rsa_rhodecode_access_priv.key
18 18 </pre>
19 19
20 20 <div>
21 21 <textarea style="height: 300px">${c.private}</textarea>
22 22 </div>
23 23 <br/>
24 24
25 25 <h4>${_('Public key')}</h4>
26 26 <pre>
27 # Save the content as
28 # Windows: /Users/<username>/.ssh/id_rsa_rhodecode_access_pub.key
29 # macOS: /Users/<yourname>/.ssh/id_rsa_rhodecode_access_pub.key
30 # Linux: /home/<username>/.ssh/id_rsa_rhodecode_access_pub.key
27 # Save the below content as
28 # Windows: /Users/{username}/.ssh/id_rsa_rhodecode_access_pub.key
29 # macOS: /Users/{yourname}/.ssh/id_rsa_rhodecode_access_pub.key
30 # Linux: /home/{username}/.ssh/id_rsa_rhodecode_access_pub.key
31 31 </pre>
32 32
33 33 <input type="text" value="${c.public}" class="large text" size="100"/>
34 34 <p>
35 35 % if hasattr(c, 'target_form_url'):
36 36 <a href="${c.target_form_url}">${_('Use this generated key')}.</a>
37 37 % else:
38 38 <a href="${h.route_path('edit_user_ssh_keys', user_id=c.user.user_id, _query=dict(default_key=c.public))}">${_('Use this generated key')}.</a>
39 39 % endif
40 40 ${_('Confirmation required on the next screen')}.
41 41 </p>
42 42 </div>
43 43 </div>
44 44
45 45 <script>
46 46
47 47 $(document).ready(function(){
48 48
49 49
50 50 });
51 51 </script>
General Comments 0
You need to be logged in to leave comments. Login now