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