Show More
The requested changes are too big and content was truncated. Show full diff
@@ -0,0 +1,173 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2010-2017 RhodeCode GmbH | |
|
4 | # | |
|
5 | # This program is free software: you can redistribute it and/or modify | |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
|
7 | # (only), as published by the Free Software Foundation. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
16 | # | |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
20 | ||
|
21 | import pytest | |
|
22 | ||
|
23 | from rhodecode.model.db import User, UserSshKeys | |
|
24 | ||
|
25 | from rhodecode.tests import TestController, assert_session_flash | |
|
26 | from rhodecode.tests.fixture import Fixture | |
|
27 | ||
|
28 | fixture = Fixture() | |
|
29 | ||
|
30 | ||
|
31 | def route_path(name, params=None, **kwargs): | |
|
32 | import urllib | |
|
33 | from rhodecode.apps._base import ADMIN_PREFIX | |
|
34 | ||
|
35 | base_url = { | |
|
36 | 'edit_user_ssh_keys': | |
|
37 | ADMIN_PREFIX + '/users/{user_id}/edit/ssh_keys', | |
|
38 | 'edit_user_ssh_keys_generate_keypair': | |
|
39 | ADMIN_PREFIX + '/users/{user_id}/edit/ssh_keys/generate', | |
|
40 | 'edit_user_ssh_keys_add': | |
|
41 | ADMIN_PREFIX + '/users/{user_id}/edit/ssh_keys/new', | |
|
42 | 'edit_user_ssh_keys_delete': | |
|
43 | ADMIN_PREFIX + '/users/{user_id}/edit/ssh_keys/delete', | |
|
44 | ||
|
45 | }[name].format(**kwargs) | |
|
46 | ||
|
47 | if params: | |
|
48 | base_url = '{}?{}'.format(base_url, urllib.urlencode(params)) | |
|
49 | return base_url | |
|
50 | ||
|
51 | ||
|
52 | class TestAdminUsersSshKeysView(TestController): | |
|
53 | INVALID_KEY = """\ | |
|
54 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDk+77sjDzVeB6vevJsuZds1iNU5 | |
|
55 | LANOa5CU5G/9JYIA6RYsWWMO7mbsR82IUckdqOHmxSykfR1D1TdluyIpQLrwgH5kb | |
|
56 | n8FkVI8zBMCKakxowvN67B0R7b1BT4PPzW2JlOXei/m9W12ZY484VTow6/B+kf2Q8 | |
|
57 | cP8tmCJmKWZma5Em7OTUhvjyQVNz3v7HfeY5Hq0Ci4ECJ59hepFDabJvtAXg9XrI6 | |
|
58 | jvdphZTc30I4fG8+hBHzpeFxUGvSGNtXPUbwaAY8j/oHYrTpMgkj6pUEFsiKfC5zP | |
|
59 | qPFR5HyKTCHW0nFUJnZsbyFT5hMiF/hZkJc9A0ZbdSvJwCRQ/g3bmdL | |
|
60 | your_email@example.com | |
|
61 | """ | |
|
62 | VALID_KEY = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDk+77sjDzVeB6vev' \ | |
|
63 | 'JsuZds1iNU5LANOa5CU5G/9JYIA6RYsWWMO7mbsR82IUckdqOHmxSy' \ | |
|
64 | 'kfR1D1TdluyIpQLrwgH5kbn8FkVI8zBMCKakxowvN67B0R7b1BT4PP' \ | |
|
65 | 'zW2JlOXei/m9W12ZY484VTow6/B+kf2Q8cP8tmCJmKWZma5Em7OTUh' \ | |
|
66 | 'vjyQVNz3v7HfeY5Hq0Ci4ECJ59hepFDabJvtAXg9XrI6jvdphZTc30' \ | |
|
67 | 'I4fG8+hBHzpeFxUGvSGNtXPUbwaAY8j/oHYrTpMgkj6pUEFsiKfC5zPq' \ | |
|
68 | 'PFR5HyKTCHW0nFUJnZsbyFT5hMiF/hZkJc9A0ZbdSvJwCRQ/g3bmdL ' \ | |
|
69 | 'your_email@example.com' | |
|
70 | ||
|
71 | def test_ssh_keys_default_user(self): | |
|
72 | self.log_user() | |
|
73 | user = User.get_default_user() | |
|
74 | self.app.get( | |
|
75 | route_path('edit_user_ssh_keys', user_id=user.user_id), | |
|
76 | status=302) | |
|
77 | ||
|
78 | def test_add_ssh_key_error(self, user_util): | |
|
79 | self.log_user() | |
|
80 | user = user_util.create_user() | |
|
81 | user_id = user.user_id | |
|
82 | ||
|
83 | key_data = self.INVALID_KEY | |
|
84 | ||
|
85 | desc = 'MY SSH KEY' | |
|
86 | response = self.app.post( | |
|
87 | route_path('edit_user_ssh_keys_add', user_id=user_id), | |
|
88 | {'description': desc, 'key_data': key_data, | |
|
89 | 'csrf_token': self.csrf_token}) | |
|
90 | assert_session_flash(response, 'An error occurred during ssh ' | |
|
91 | 'key saving: Unable to decode the key') | |
|
92 | ||
|
93 | def test_ssh_key_duplicate(self, user_util): | |
|
94 | self.log_user() | |
|
95 | user = user_util.create_user() | |
|
96 | user_id = user.user_id | |
|
97 | ||
|
98 | key_data = self.VALID_KEY | |
|
99 | ||
|
100 | desc = 'MY SSH KEY' | |
|
101 | response = self.app.post( | |
|
102 | route_path('edit_user_ssh_keys_add', user_id=user_id), | |
|
103 | {'description': desc, 'key_data': key_data, | |
|
104 | 'csrf_token': self.csrf_token}) | |
|
105 | assert_session_flash(response, 'Ssh Key successfully created') | |
|
106 | response.follow() # flush session flash | |
|
107 | ||
|
108 | # add the same key AGAIN | |
|
109 | desc = 'MY SSH KEY' | |
|
110 | response = self.app.post( | |
|
111 | route_path('edit_user_ssh_keys_add', user_id=user_id), | |
|
112 | {'description': desc, 'key_data': key_data, | |
|
113 | 'csrf_token': self.csrf_token}) | |
|
114 | assert_session_flash(response, 'An error occurred during ssh key ' | |
|
115 | 'saving: Such key already exists, ' | |
|
116 | 'please use a different one') | |
|
117 | ||
|
118 | def test_add_ssh_key(self, user_util): | |
|
119 | self.log_user() | |
|
120 | user = user_util.create_user() | |
|
121 | user_id = user.user_id | |
|
122 | ||
|
123 | key_data = self.VALID_KEY | |
|
124 | ||
|
125 | desc = 'MY SSH KEY' | |
|
126 | response = self.app.post( | |
|
127 | route_path('edit_user_ssh_keys_add', user_id=user_id), | |
|
128 | {'description': desc, 'key_data': key_data, | |
|
129 | 'csrf_token': self.csrf_token}) | |
|
130 | assert_session_flash(response, 'Ssh Key successfully created') | |
|
131 | ||
|
132 | response = response.follow() | |
|
133 | response.mustcontain(desc) | |
|
134 | ||
|
135 | def test_delete_ssh_key(self, user_util): | |
|
136 | self.log_user() | |
|
137 | user = user_util.create_user() | |
|
138 | user_id = user.user_id | |
|
139 | ||
|
140 | key_data = self.VALID_KEY | |
|
141 | ||
|
142 | desc = 'MY SSH KEY' | |
|
143 | response = self.app.post( | |
|
144 | route_path('edit_user_ssh_keys_add', user_id=user_id), | |
|
145 | {'description': desc, 'key_data': key_data, | |
|
146 | 'csrf_token': self.csrf_token}) | |
|
147 | assert_session_flash(response, 'Ssh Key successfully created') | |
|
148 | response = response.follow() # flush the Session flash | |
|
149 | ||
|
150 | # now delete our key | |
|
151 | keys = UserSshKeys.query().filter(UserSshKeys.user_id == user_id).all() | |
|
152 | assert 1 == len(keys) | |
|
153 | ||
|
154 | response = self.app.post( | |
|
155 | route_path('edit_user_ssh_keys_delete', user_id=user_id), | |
|
156 | {'del_ssh_key': keys[0].ssh_key_id, | |
|
157 | 'csrf_token': self.csrf_token}) | |
|
158 | ||
|
159 | assert_session_flash(response, 'Ssh key successfully deleted') | |
|
160 | keys = UserSshKeys.query().filter(UserSshKeys.user_id == user_id).all() | |
|
161 | assert 0 == len(keys) | |
|
162 | ||
|
163 | def test_generate_keypair(self, user_util): | |
|
164 | self.log_user() | |
|
165 | user = user_util.create_user() | |
|
166 | user_id = user.user_id | |
|
167 | ||
|
168 | response = self.app.get( | |
|
169 | route_path('edit_user_ssh_keys_generate_keypair', user_id=user_id)) | |
|
170 | ||
|
171 | response.mustcontain('Private key') | |
|
172 | response.mustcontain('Public key') | |
|
173 | response.mustcontain('-----BEGIN RSA PRIVATE KEY-----') |
|
1 | NO CONTENT: new file 100644 | |
The requested commit or file is too big and content was truncated. Show full diff |
@@ -0,0 +1,29 b'' | |||
|
1 | import logging | |
|
2 | ||
|
3 | from sqlalchemy import * | |
|
4 | from rhodecode.model import meta | |
|
5 | from rhodecode.lib.dbmigrate.versions import _reset_base, notify | |
|
6 | ||
|
7 | log = logging.getLogger(__name__) | |
|
8 | ||
|
9 | ||
|
10 | def upgrade(migrate_engine): | |
|
11 | """ | |
|
12 | Upgrade operations go here. | |
|
13 | Don't create your own engine; bind migrate_engine to your metadata | |
|
14 | """ | |
|
15 | _reset_base(migrate_engine) | |
|
16 | from rhodecode.lib.dbmigrate.schema import db_4_9_0_0 as db | |
|
17 | ||
|
18 | db.UserSshKeys.__table__.create() | |
|
19 | ||
|
20 | fixups(db, meta.Session) | |
|
21 | ||
|
22 | ||
|
23 | def downgrade(migrate_engine): | |
|
24 | meta = MetaData() | |
|
25 | meta.bind = migrate_engine | |
|
26 | ||
|
27 | ||
|
28 | def fixups(models, _SESSION): | |
|
29 | pass |
@@ -0,0 +1,123 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2013-2017 RhodeCode GmbH | |
|
4 | # | |
|
5 | # This program is free software: you can redistribute it and/or modify | |
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |
|
7 | # (only), as published by the Free Software Foundation. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
14 | # You should have received a copy of the GNU Affero General Public License | |
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
|
16 | # | |
|
17 | # This program is dual-licensed. If you wish to learn more about the | |
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
|
20 | ||
|
21 | import logging | |
|
22 | import traceback | |
|
23 | ||
|
24 | import sshpubkeys | |
|
25 | import sshpubkeys.exceptions | |
|
26 | ||
|
27 | from rhodecode.model import BaseModel | |
|
28 | from rhodecode.model.db import UserSshKeys | |
|
29 | from rhodecode.model.meta import Session | |
|
30 | ||
|
31 | log = logging.getLogger(__name__) | |
|
32 | ||
|
33 | ||
|
34 | class SshKeyModel(BaseModel): | |
|
35 | cls = UserSshKeys | |
|
36 | ||
|
37 | def parse_key(self, key_data): | |
|
38 | """ | |
|
39 | print(ssh.bits) # 768 | |
|
40 | print(ssh.hash_md5()) # 56:84:1e:90:08:3b:60:c7:29:70:5f:5e:25:a6:3b:86 | |
|
41 | print(ssh.hash_sha256()) # SHA256:xk3IEJIdIoR9MmSRXTP98rjDdZocmXJje/28ohMQEwM | |
|
42 | print(ssh.hash_sha512()) # SHA512:1C3lNBhjpDVQe39hnyy+xvlZYU3IPwzqK1rVneGavy6O3/ebjEQSFvmeWoyMTplIanmUK1hmr9nA8Skmj516HA | |
|
43 | print(ssh.comment) # ojar@ojar-laptop | |
|
44 | print(ssh.options_raw) # None (string of optional options at the beginning of public key) | |
|
45 | print(ssh.options) # None (options as a dictionary, parsed and validated) | |
|
46 | ||
|
47 | :param key_data: | |
|
48 | :return: | |
|
49 | """ | |
|
50 | ssh = sshpubkeys.SSHKey(strict_mode=True) | |
|
51 | try: | |
|
52 | ssh.parse(key_data) | |
|
53 | return ssh | |
|
54 | except sshpubkeys.exceptions.InvalidKeyException as err: | |
|
55 | log.error("Invalid key: %s", err) | |
|
56 | raise | |
|
57 | except NotImplementedError as err: | |
|
58 | log.error("Invalid key type: %s", err) | |
|
59 | raise | |
|
60 | except Exception as err: | |
|
61 | log.error("Key Parse error: %s", err) | |
|
62 | raise | |
|
63 | ||
|
64 | def generate_keypair(self, comment=None): | |
|
65 | from Crypto.PublicKey import RSA | |
|
66 | ||
|
67 | key = RSA.generate(2048) | |
|
68 | private = key.exportKey('PEM') | |
|
69 | ||
|
70 | pubkey = key.publickey() | |
|
71 | public = pubkey.exportKey('OpenSSH') | |
|
72 | if comment: | |
|
73 | public = public + " " + comment | |
|
74 | return private, public | |
|
75 | ||
|
76 | def create(self, user, fingerprint, key_data, description): | |
|
77 | """ | |
|
78 | """ | |
|
79 | user = self._get_user(user) | |
|
80 | ||
|
81 | new_ssh_key = UserSshKeys() | |
|
82 | new_ssh_key.ssh_key_fingerprint = fingerprint | |
|
83 | new_ssh_key.ssh_key_data = key_data | |
|
84 | new_ssh_key.user_id = user.user_id | |
|
85 | new_ssh_key.description = description | |
|
86 | ||
|
87 | Session().add(new_ssh_key) | |
|
88 | ||
|
89 | return new_ssh_key | |
|
90 | ||
|
91 | def delete(self, ssh_key_id, user=None): | |
|
92 | """ | |
|
93 | Deletes given api_key, if user is set it also filters the object for | |
|
94 | deletion by given user. | |
|
95 | """ | |
|
96 | ssh_key = UserSshKeys.query().filter( | |
|
97 | UserSshKeys.ssh_key_id == ssh_key_id) | |
|
98 | ||
|
99 | if user: | |
|
100 | user = self._get_user(user) | |
|
101 | ssh_key = ssh_key.filter(UserSshKeys.user_id == user.user_id) | |
|
102 | ssh_key = ssh_key.scalar() | |
|
103 | ||
|
104 | if ssh_key: | |
|
105 | try: | |
|
106 | Session().delete(ssh_key) | |
|
107 | except Exception: | |
|
108 | log.error(traceback.format_exc()) | |
|
109 | raise | |
|
110 | ||
|
111 | def get_ssh_keys(self, user): | |
|
112 | user = self._get_user(user) | |
|
113 | user_ssh_keys = UserSshKeys.query()\ | |
|
114 | .filter(UserSshKeys.user_id == user.user_id) | |
|
115 | user_ssh_keys = user_ssh_keys.order_by(UserSshKeys.ssh_key_id) | |
|
116 | return user_ssh_keys | |
|
117 | ||
|
118 | def get_ssh_key_by_fingerprint(self, ssh_key_fingerprint): | |
|
119 | user_ssh_key = UserSshKeys.query()\ | |
|
120 | .filter(UserSshKeys.ssh_key_fingerprint == ssh_key_fingerprint)\ | |
|
121 | .first() | |
|
122 | ||
|
123 | return user_ssh_key |
@@ -0,0 +1,78 b'' | |||
|
1 | <div class="panel panel-default"> | |
|
2 | <div class="panel-heading"> | |
|
3 | <h3 class="panel-title">${_('SSH Keys')}</h3> | |
|
4 | </div> | |
|
5 | <div class="panel-body"> | |
|
6 | <div class="sshkeys_wrap"> | |
|
7 | <table class="rctable ssh_keys"> | |
|
8 | <tr> | |
|
9 | <th>${_('Fingerprint')}</th> | |
|
10 | <th>${_('Description')}</th> | |
|
11 | <th>${_('Created')}</th> | |
|
12 | <th>${_('Action')}</th> | |
|
13 | </tr> | |
|
14 | %if c.user_ssh_keys: | |
|
15 | %for ssh_key in c.user_ssh_keys: | |
|
16 | <tr class=""> | |
|
17 | <td class=""> | |
|
18 | <code>${ssh_key.ssh_key_fingerprint}</code> | |
|
19 | </td> | |
|
20 | <td class="td-wrap">${ssh_key.description}</td> | |
|
21 | <td class="td-tags">${h.format_date(ssh_key.created_on)}</td> | |
|
22 | ||
|
23 | <td class="td-action"> | |
|
24 | ${h.secure_form(h.route_path('edit_user_ssh_keys_delete', user_id=c.user.user_id), method='POST', request=request)} | |
|
25 | ${h.hidden('del_ssh_key', ssh_key.ssh_key_id)} | |
|
26 | <button class="btn btn-link btn-danger" type="submit" | |
|
27 | onclick="return confirm('${_('Confirm to remove ssh key %s') % ssh_key.ssh_key_fingerprint}');"> | |
|
28 | ${_('Delete')} | |
|
29 | </button> | |
|
30 | ${h.end_form()} | |
|
31 | </td> | |
|
32 | </tr> | |
|
33 | %endfor | |
|
34 | %else: | |
|
35 | <tr><td><div class="ip">${_('No additional ssh keys specified')}</div></td></tr> | |
|
36 | %endif | |
|
37 | </table> | |
|
38 | </div> | |
|
39 | ||
|
40 | <div class="user_ssh_keys"> | |
|
41 | ${h.secure_form(h.route_path('edit_user_ssh_keys_add', user_id=c.user.user_id), method='POST', request=request)} | |
|
42 | <div class="form form-vertical"> | |
|
43 | <!-- fields --> | |
|
44 | <div class="fields"> | |
|
45 | <div class="field"> | |
|
46 | <div class="label"> | |
|
47 | <label for="new_email">${_('New ssh key')}:</label> | |
|
48 | </div> | |
|
49 | <div class="input"> | |
|
50 | ${h.text('description', class_='medium', placeholder=_('Description'))} | |
|
51 | <a href="${h.route_path('edit_user_ssh_keys_generate_keypair', user_id=c.user.user_id)}">${_('Generate random RSA key')}</a> | |
|
52 | </div> | |
|
53 | </div> | |
|
54 | ||
|
55 | <div class="field"> | |
|
56 | <div class="textarea text-area editor"> | |
|
57 | ${h.textarea('key_data',c.default_key, size=30, placeholder=_("Public key, begins with 'ssh-rsa', 'ssh-dss', 'ssh-ed25519', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', or 'ecdsa-sha2-nistp521'"))} | |
|
58 | </div> | |
|
59 | </div> | |
|
60 | ||
|
61 | <div class="buttons"> | |
|
62 | ${h.submit('save',_('Add'),class_="btn")} | |
|
63 | ${h.reset('reset',_('Reset'),class_="btn")} | |
|
64 | </div> | |
|
65 | </div> | |
|
66 | </div> | |
|
67 | ${h.end_form()} | |
|
68 | </div> | |
|
69 | </div> | |
|
70 | </div> | |
|
71 | ||
|
72 | <script> | |
|
73 | ||
|
74 | $(document).ready(function(){ | |
|
75 | ||
|
76 | ||
|
77 | }); | |
|
78 | </script> |
@@ -0,0 +1,45 b'' | |||
|
1 | <div class="panel panel-default"> | |
|
2 | <div class="panel-heading"> | |
|
3 | <h3 class="panel-title">${_('New SSH Key generated')}</h3> | |
|
4 | </div> | |
|
5 | <div class="panel-body"> | |
|
6 | <p> | |
|
7 | ${_('Below is a 2048 bit generated SSH RSA key. You can use it to access RhodeCode via the SSH wrapper.')} | |
|
8 | </p> | |
|
9 | <h4>${_('Private key')}</h4> | |
|
10 | <pre> | |
|
11 | # Save the content as | |
|
12 | ~/.ssh/id_rsa_rhodecode_access_priv.key | |
|
13 | # Change permissions | |
|
14 | chmod 0600 ~/.ssh/id_rsa_rhodecode_access_priv.key | |
|
15 | </pre> | |
|
16 | ||
|
17 | <div> | |
|
18 | <textarea style="height: 300px">${c.private}</textarea> | |
|
19 | </div> | |
|
20 | <br/> | |
|
21 | ||
|
22 | ||
|
23 | <h4>${_('Public key')}</h4> | |
|
24 | <pre> | |
|
25 | # Save the content as | |
|
26 | ~/.ssh/id_rsa_rhodecode_access_pub.key | |
|
27 | # Change permissions | |
|
28 | chmod 0600 ~/.ssh/id_rsa_rhodecode_access_pub.key | |
|
29 | </pre> | |
|
30 | ||
|
31 | <input type="text" value="${c.public}" class="large text" size="100"/> | |
|
32 | <p> | |
|
33 | <a href="${h.route_path('edit_user_ssh_keys', user_id=c.user.user_id, _query=dict(default_key=c.public))}">${_('Add this generated key')}</a> | |
|
34 | </p> | |
|
35 | ||
|
36 | </div> | |
|
37 | </div> | |
|
38 | ||
|
39 | <script> | |
|
40 | ||
|
41 | $(document).ready(function(){ | |
|
42 | ||
|
43 | ||
|
44 | }); | |
|
45 | </script> |
@@ -653,13 +653,13 b'' | |||
|
653 | 653 | }; |
|
654 | 654 | }; |
|
655 | 655 | ecdsa = super.buildPythonPackage { |
|
656 |
name = "ecdsa-0.1 |
|
|
656 | name = "ecdsa-0.13"; | |
|
657 | 657 | buildInputs = with self; []; |
|
658 | 658 | doCheck = false; |
|
659 | 659 | propagatedBuildInputs = with self; []; |
|
660 | 660 | src = fetchurl { |
|
661 |
url = "https://pypi.python.org/packages/ |
|
|
662 | md5 = "8ef586fe4dbb156697d756900cb41d7c"; | |
|
661 | url = "https://pypi.python.org/packages/f9/e5/99ebb176e47f150ac115ffeda5fedb6a3dbb3c00c74a59fd84ddf12f5857/ecdsa-0.13.tar.gz"; | |
|
662 | md5 = "1f60eda9cb5c46722856db41a3ae6670"; | |
|
663 | 663 | }; |
|
664 | 664 | meta = { |
|
665 | 665 | license = [ pkgs.lib.licenses.mit ]; |
@@ -1172,19 +1172,6 b'' | |||
|
1172 | 1172 | license = [ pkgs.lib.licenses.bsdOriginal ]; |
|
1173 | 1173 | }; |
|
1174 | 1174 | }; |
|
1175 | paramiko = super.buildPythonPackage { | |
|
1176 | name = "paramiko-1.15.1"; | |
|
1177 | buildInputs = with self; []; | |
|
1178 | doCheck = false; | |
|
1179 | propagatedBuildInputs = with self; [pycrypto ecdsa]; | |
|
1180 | src = fetchurl { | |
|
1181 | url = "https://pypi.python.org/packages/04/2b/a22d2a560c1951abbbf95a0628e245945565f70dc082d9e784666887222c/paramiko-1.15.1.tar.gz"; | |
|
1182 | md5 = "48c274c3f9b1282932567b21f6acf3b5"; | |
|
1183 | }; | |
|
1184 | meta = { | |
|
1185 | license = [ { fullName = "LGPL"; } { fullName = "GNU Library or Lesser General Public License (LGPL)"; } ]; | |
|
1186 | }; | |
|
1187 | }; | |
|
1188 | 1175 | pathlib2 = super.buildPythonPackage { |
|
1189 | 1176 | name = "pathlib2-2.3.0"; |
|
1190 | 1177 | buildInputs = with self; []; |
@@ -1722,7 +1709,7 b'' | |||
|
1722 | 1709 | name = "rhodecode-enterprise-ce-4.9.0"; |
|
1723 | 1710 | buildInputs = with self; [pytest py pytest-cov pytest-sugar pytest-runner pytest-catchlog pytest-profiling gprof2dot pytest-timeout mock WebTest cov-core coverage configobj]; |
|
1724 | 1711 | doCheck = true; |
|
1725 | propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments pygments-markdown-lexer Pylons Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic cssselect celery channelstream colander decorator deform docutils gevent gunicorn infrae.cache ipython iso8601 kombu lxml msgpack-python nbconvert packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson subprocess32 waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt]; | |
|
1712 | propagatedBuildInputs = with self; [Babel Beaker FormEncode Mako Markdown MarkupSafe MySQL-python Paste PasteDeploy PasteScript Pygments pygments-markdown-lexer Pylons Routes SQLAlchemy Tempita URLObject WebError WebHelpers WebHelpers2 WebOb WebTest Whoosh alembic amqplib anyjson appenlight-client authomatic cssselect celery channelstream colander decorator deform docutils gevent gunicorn infrae.cache ipython iso8601 kombu lxml msgpack-python nbconvert packaging psycopg2 py-gfm pycrypto pycurl pyparsing pyramid pyramid-debugtoolbar pyramid-mako pyramid-beaker pysqlite python-dateutil python-ldap python-memcached python-pam recaptcha-client repoze.lru requests simplejson sshpubkeys subprocess32 waitress zope.cachedescriptors dogpile.cache dogpile.core psutil py-bcrypt]; | |
|
1726 | 1713 | src = ./.; |
|
1727 | 1714 | meta = { |
|
1728 | 1715 | license = [ { fullName = "Affero GNU General Public License v3 or later (AGPLv3+)"; } { fullName = "AGPLv3, and Commercial License"; } ]; |
@@ -1832,6 +1819,19 b'' | |||
|
1832 | 1819 | license = [ pkgs.lib.licenses.mit ]; |
|
1833 | 1820 | }; |
|
1834 | 1821 | }; |
|
1822 | sshpubkeys = super.buildPythonPackage { | |
|
1823 | name = "sshpubkeys-2.2.0"; | |
|
1824 | buildInputs = with self; []; | |
|
1825 | doCheck = false; | |
|
1826 | propagatedBuildInputs = with self; [pycrypto ecdsa]; | |
|
1827 | src = fetchurl { | |
|
1828 | url = "https://pypi.python.org/packages/27/da/337fabeb3dca6b62039a93ceaa636f25065e0ae92b575b1235342076cf0a/sshpubkeys-2.2.0.tar.gz"; | |
|
1829 | md5 = "458e45f6b92b1afa84f0ffe1f1c90935"; | |
|
1830 | }; | |
|
1831 | meta = { | |
|
1832 | license = [ pkgs.lib.licenses.bsdOriginal ]; | |
|
1833 | }; | |
|
1834 | }; | |
|
1835 | 1835 | subprocess32 = super.buildPythonPackage { |
|
1836 | 1836 | name = "subprocess32-3.2.7"; |
|
1837 | 1837 | buildInputs = with self; []; |
@@ -19,7 +19,7 b' deform==2.0.4' | |||
|
19 | 19 | docutils==0.13.1 |
|
20 | 20 | dogpile.cache==0.6.4 |
|
21 | 21 | dogpile.core==0.4.1 |
|
22 |
ecdsa==0.1 |
|
|
22 | ecdsa==0.13 | |
|
23 | 23 | FormEncode==1.2.4 |
|
24 | 24 | future==0.14.3 |
|
25 | 25 | futures==3.0.2 |
@@ -39,7 +39,6 b' MySQL-python==1.2.5' | |||
|
39 | 39 | nose==1.3.6 |
|
40 | 40 | objgraph==3.1.0 |
|
41 | 41 | packaging==15.2 |
|
42 | paramiko==1.15.1 | |
|
43 | 42 | Paste==2.0.3 |
|
44 | 43 | PasteDeploy==1.5.2 |
|
45 | 44 | PasteScript==1.7.5 |
@@ -74,6 +73,7 b' simplejson==3.11.1' | |||
|
74 | 73 | six==1.9.0 |
|
75 | 74 | Sphinx==1.2.2 |
|
76 | 75 | SQLAlchemy==1.1.11 |
|
76 | sshpubkeys==2.2.0 | |
|
77 | 77 | subprocess32==3.2.7 |
|
78 | 78 | supervisor==3.3.2 |
|
79 | 79 | Tempita==0.5.2 |
@@ -51,7 +51,7 b' PYRAMID_SETTINGS = {}' | |||
|
51 | 51 | EXTENSIONS = {} |
|
52 | 52 | |
|
53 | 53 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) |
|
54 |
__dbversion__ = |
|
|
54 | __dbversion__ = 80 # defines current db version for migrations | |
|
55 | 55 | __platform__ = platform.system() |
|
56 | 56 | __license__ = 'AGPLv3, and Commercial License' |
|
57 | 57 | __author__ = 'RhodeCode GmbH' |
@@ -126,6 +126,20 b' def admin_routes(config):' | |||
|
126 | 126 | name='edit_user_auth_tokens_delete', |
|
127 | 127 | pattern='/users/{user_id:\d+}/edit/auth_tokens/delete') |
|
128 | 128 | |
|
129 | # user ssh keys | |
|
130 | config.add_route( | |
|
131 | name='edit_user_ssh_keys', | |
|
132 | pattern='/users/{user_id:\d+}/edit/ssh_keys') | |
|
133 | config.add_route( | |
|
134 | name='edit_user_ssh_keys_generate_keypair', | |
|
135 | pattern='/users/{user_id:\d+}/edit/ssh_keys/generate') | |
|
136 | config.add_route( | |
|
137 | name='edit_user_ssh_keys_add', | |
|
138 | pattern='/users/{user_id:\d+}/edit/ssh_keys/new') | |
|
139 | config.add_route( | |
|
140 | name='edit_user_ssh_keys_delete', | |
|
141 | pattern='/users/{user_id:\d+}/edit/ssh_keys/delete') | |
|
142 | ||
|
129 | 143 | # user emails |
|
130 | 144 | config.add_route( |
|
131 | 145 | name='edit_user_emails', |
@@ -25,6 +25,7 b' import formencode' | |||
|
25 | 25 | from pyramid.httpexceptions import HTTPFound |
|
26 | 26 | from pyramid.view import view_config |
|
27 | 27 | from sqlalchemy.sql.functions import coalesce |
|
28 | from sqlalchemy.exc import IntegrityError | |
|
28 | 29 | |
|
29 | 30 | from rhodecode.apps._base import BaseAppView, DataGridAppView |
|
30 | 31 | |
@@ -35,9 +36,11 b' from rhodecode.lib.auth import (' | |||
|
35 | 36 | from rhodecode.lib import helpers as h |
|
36 | 37 | from rhodecode.lib.utils2 import safe_int, safe_unicode |
|
37 | 38 | from rhodecode.model.auth_token import AuthTokenModel |
|
39 | from rhodecode.model.ssh_key import SshKeyModel | |
|
38 | 40 | from rhodecode.model.user import UserModel |
|
39 | 41 | from rhodecode.model.user_group import UserGroupModel |
|
40 | from rhodecode.model.db import User, or_, UserIpMap, UserEmailMap, UserApiKeys | |
|
42 | from rhodecode.model.db import ( | |
|
43 | or_, User, UserIpMap, UserEmailMap, UserApiKeys, UserSshKeys) | |
|
41 | 44 | from rhodecode.model.meta import Session |
|
42 | 45 | |
|
43 | 46 | log = logging.getLogger(__name__) |
@@ -255,6 +258,123 b' class AdminUsersView(BaseAppView, DataGr' | |||
|
255 | 258 | @LoginRequired() |
|
256 | 259 | @HasPermissionAllDecorator('hg.admin') |
|
257 | 260 | @view_config( |
|
261 | route_name='edit_user_ssh_keys', request_method='GET', | |
|
262 | renderer='rhodecode:templates/admin/users/user_edit.mako') | |
|
263 | def ssh_keys(self): | |
|
264 | _ = self.request.translate | |
|
265 | c = self.load_default_context() | |
|
266 | ||
|
267 | user_id = self.request.matchdict.get('user_id') | |
|
268 | c.user = User.get_or_404(user_id) | |
|
269 | self._redirect_for_default_user(c.user.username) | |
|
270 | ||
|
271 | c.active = 'ssh_keys' | |
|
272 | c.default_key = self.request.GET.get('default_key') | |
|
273 | c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id) | |
|
274 | return self._get_template_context(c) | |
|
275 | ||
|
276 | @LoginRequired() | |
|
277 | @HasPermissionAllDecorator('hg.admin') | |
|
278 | @view_config( | |
|
279 | route_name='edit_user_ssh_keys_generate_keypair', request_method='GET', | |
|
280 | renderer='rhodecode:templates/admin/users/user_edit.mako') | |
|
281 | def ssh_keys_generate_keypair(self): | |
|
282 | _ = self.request.translate | |
|
283 | c = self.load_default_context() | |
|
284 | ||
|
285 | user_id = self.request.matchdict.get('user_id') | |
|
286 | c.user = User.get_or_404(user_id) | |
|
287 | self._redirect_for_default_user(c.user.username) | |
|
288 | ||
|
289 | c.active = 'ssh_keys_generate' | |
|
290 | comment = 'RhodeCode-SSH {}'.format(c.user.email or '') | |
|
291 | c.private, c.public = SshKeyModel().generate_keypair(comment=comment) | |
|
292 | ||
|
293 | return self._get_template_context(c) | |
|
294 | ||
|
295 | @LoginRequired() | |
|
296 | @HasPermissionAllDecorator('hg.admin') | |
|
297 | @CSRFRequired() | |
|
298 | @view_config( | |
|
299 | route_name='edit_user_ssh_keys_add', request_method='POST') | |
|
300 | def ssh_keys_add(self): | |
|
301 | _ = self.request.translate | |
|
302 | c = self.load_default_context() | |
|
303 | ||
|
304 | user_id = self.request.matchdict.get('user_id') | |
|
305 | c.user = User.get_or_404(user_id) | |
|
306 | ||
|
307 | self._redirect_for_default_user(c.user.username) | |
|
308 | ||
|
309 | user_data = c.user.get_api_data() | |
|
310 | key_data = self.request.POST.get('key_data') | |
|
311 | description = self.request.POST.get('description') | |
|
312 | ||
|
313 | try: | |
|
314 | if not key_data: | |
|
315 | raise ValueError('Please add a valid public key') | |
|
316 | ||
|
317 | key = SshKeyModel().parse_key(key_data.strip()) | |
|
318 | fingerprint = key.hash_md5() | |
|
319 | ||
|
320 | ssh_key = SshKeyModel().create( | |
|
321 | c.user.user_id, fingerprint, key_data, description) | |
|
322 | ssh_key_data = ssh_key.get_api_data() | |
|
323 | ||
|
324 | audit_logger.store_web( | |
|
325 | 'user.edit.ssh_key.add', action_data={ | |
|
326 | 'data': {'ssh_key': ssh_key_data, 'user': user_data}}, | |
|
327 | user=self._rhodecode_user, ) | |
|
328 | Session().commit() | |
|
329 | ||
|
330 | h.flash(_("Ssh Key successfully created"), category='success') | |
|
331 | ||
|
332 | except IntegrityError: | |
|
333 | log.exception("Exception during ssh key saving") | |
|
334 | h.flash(_('An error occurred during ssh key saving: {}').format( | |
|
335 | 'Such key already exists, please use a different one'), | |
|
336 | category='error') | |
|
337 | except Exception as e: | |
|
338 | log.exception("Exception during ssh key saving") | |
|
339 | h.flash(_('An error occurred during ssh key saving: {}').format(e), | |
|
340 | category='error') | |
|
341 | ||
|
342 | return HTTPFound( | |
|
343 | h.route_path('edit_user_ssh_keys', user_id=user_id)) | |
|
344 | ||
|
345 | @LoginRequired() | |
|
346 | @HasPermissionAllDecorator('hg.admin') | |
|
347 | @CSRFRequired() | |
|
348 | @view_config( | |
|
349 | route_name='edit_user_ssh_keys_delete', request_method='POST') | |
|
350 | def ssh_keys_delete(self): | |
|
351 | _ = self.request.translate | |
|
352 | c = self.load_default_context() | |
|
353 | ||
|
354 | user_id = self.request.matchdict.get('user_id') | |
|
355 | c.user = User.get_or_404(user_id) | |
|
356 | self._redirect_for_default_user(c.user.username) | |
|
357 | user_data = c.user.get_api_data() | |
|
358 | ||
|
359 | del_ssh_key = self.request.POST.get('del_ssh_key') | |
|
360 | ||
|
361 | if del_ssh_key: | |
|
362 | ssh_key = UserSshKeys.get_or_404(del_ssh_key) | |
|
363 | ssh_key_data = ssh_key.get_api_data() | |
|
364 | ||
|
365 | SshKeyModel().delete(del_ssh_key, c.user.user_id) | |
|
366 | audit_logger.store_web( | |
|
367 | 'user.edit.ssh_key.delete', action_data={ | |
|
368 | 'data': {'ssh_key': ssh_key_data, 'user': user_data}}, | |
|
369 | user=self._rhodecode_user,) | |
|
370 | Session().commit() | |
|
371 | h.flash(_("Ssh key successfully deleted"), category='success') | |
|
372 | ||
|
373 | return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id)) | |
|
374 | ||
|
375 | @LoginRequired() | |
|
376 | @HasPermissionAllDecorator('hg.admin') | |
|
377 | @view_config( | |
|
258 | 378 | route_name='edit_user_emails', request_method='GET', |
|
259 | 379 | renderer='rhodecode:templates/admin/users/user_edit.mako') |
|
260 | 380 | def emails(self): |
@@ -46,6 +46,8 b' ACTIONS_V1 = {' | |||
|
46 | 46 | 'user.edit.token.delete': {'token': {}, 'user': {}}, |
|
47 | 47 | 'user.edit.email.add': {'email': ''}, |
|
48 | 48 | 'user.edit.email.delete': {'email': ''}, |
|
49 | 'user.edit.ssh_key.add': {'token': {}, 'user': {}}, | |
|
50 | 'user.edit.ssh_key.delete': {'token': {}, 'user': {}}, | |
|
49 | 51 | 'user.edit.password_reset.enabled': {}, |
|
50 | 52 | 'user.edit.password_reset.disabled': {}, |
|
51 | 53 |
@@ -545,6 +545,8 b' class User(Base, BaseModel):' | |||
|
545 | 545 | user_emails = relationship('UserEmailMap', cascade='all') |
|
546 | 546 | user_ip_map = relationship('UserIpMap', cascade='all') |
|
547 | 547 | user_auth_tokens = relationship('UserApiKeys', cascade='all') |
|
548 | user_ssh_keys = relationship('UserSshKeys', cascade='all') | |
|
549 | ||
|
548 | 550 | # gists |
|
549 | 551 | user_gists = relationship('Gist', cascade='all') |
|
550 | 552 | # user pull requests |
@@ -1143,6 +1145,43 b' class UserIpMap(Base, BaseModel):' | |||
|
1143 | 1145 | self.user_id, self.ip_addr) |
|
1144 | 1146 | |
|
1145 | 1147 | |
|
1148 | class UserSshKeys(Base, BaseModel): | |
|
1149 | __tablename__ = 'user_ssh_keys' | |
|
1150 | __table_args__ = ( | |
|
1151 | Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'), | |
|
1152 | ||
|
1153 | UniqueConstraint('ssh_key_fingerprint'), | |
|
1154 | ||
|
1155 | {'extend_existing': True, 'mysql_engine': 'InnoDB', | |
|
1156 | 'mysql_charset': 'utf8', 'sqlite_autoincrement': True} | |
|
1157 | ) | |
|
1158 | __mapper_args__ = {} | |
|
1159 | ||
|
1160 | ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True) | |
|
1161 | ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None) | |
|
1162 | ssh_key_fingerprint = Column('ssh_key_fingerprint', String(1024), nullable=False, unique=None, default=None) | |
|
1163 | ||
|
1164 | description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql')) | |
|
1165 | ||
|
1166 | created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now) | |
|
1167 | accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None) | |
|
1168 | user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None) | |
|
1169 | ||
|
1170 | user = relationship('User', lazy='joined') | |
|
1171 | ||
|
1172 | def __json__(self): | |
|
1173 | data = { | |
|
1174 | 'ssh_fingerprint': self.ssh_key_fingerprint, | |
|
1175 | 'description': self.description, | |
|
1176 | 'created_on': self.created_on | |
|
1177 | } | |
|
1178 | return data | |
|
1179 | ||
|
1180 | def get_api_data(self): | |
|
1181 | data = self.__json__() | |
|
1182 | return data | |
|
1183 | ||
|
1184 | ||
|
1146 | 1185 | class UserLog(Base, BaseModel): |
|
1147 | 1186 | __tablename__ = 'user_logs' |
|
1148 | 1187 | __table_args__ = ( |
@@ -62,6 +62,10 b' function registerRCRoutes() {' | |||
|
62 | 62 | pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']); |
|
63 | 63 | pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']); |
|
64 | 64 | pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']); |
|
65 | pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']); | |
|
66 | pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']); | |
|
67 | pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']); | |
|
68 | pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']); | |
|
65 | 69 | pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']); |
|
66 | 70 | pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']); |
|
67 | 71 | pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']); |
@@ -37,6 +37,7 b'' | |||
|
37 | 37 | <ul class="nav nav-pills nav-stacked"> |
|
38 | 38 | <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', user_id=c.user.user_id)}">${_('User Profile')}</a></li> |
|
39 | 39 | <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('edit_user_auth_tokens', user_id=c.user.user_id)}">${_('Auth tokens')}</a></li> |
|
40 | <li class="${'active' if c.active in ['ssh_keys','ssh_keys_generate'] else ''}"><a href="${h.route_path('edit_user_ssh_keys', user_id=c.user.user_id)}">${_('SSH Keys')}</a></li> | |
|
40 | 41 | <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li> |
|
41 | 42 | <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_global_perms', user_id=c.user.user_id)}">${_('Global permissions')}</a></li> |
|
42 | 43 | <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li> |
General Comments 0
You need to be logged in to leave comments.
Login now