##// END OF EJS Templates
ssh: improve logging, and make the UI show last accessed date for key.
marcink -
r2973:40c25cc7 default
parent child Browse files
Show More
@@ -1,207 +1,208 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 re
22 import re
23 import logging
23 import logging
24 import datetime
24 import datetime
25 from pyramid.compat import configparser
25 from pyramid.compat import configparser
26
26
27 from rhodecode.model.db import Session, User, UserSshKeys
27 from rhodecode.model.db import Session, User, UserSshKeys
28 from rhodecode.model.scm import ScmModel
28 from rhodecode.model.scm import ScmModel
29
29
30 from .hg import MercurialServer
30 from .hg import MercurialServer
31 from .git import GitServer
31 from .git import GitServer
32 from .svn import SubversionServer
32 from .svn import SubversionServer
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 class SshWrapper(object):
36 class SshWrapper(object):
37
37
38 def __init__(self, command, connection_info, mode,
38 def __init__(self, command, connection_info, mode,
39 user, user_id, key_id, shell, ini_path, env):
39 user, user_id, key_id, shell, ini_path, env):
40 self.command = command
40 self.command = command
41 self.connection_info = connection_info
41 self.connection_info = connection_info
42 self.mode = mode
42 self.mode = mode
43 self.user = user
43 self.user = user
44 self.user_id = user_id
44 self.user_id = user_id
45 self.key_id = key_id
45 self.key_id = key_id
46 self.shell = shell
46 self.shell = shell
47 self.ini_path = ini_path
47 self.ini_path = ini_path
48 self.env = env
48 self.env = env
49
49
50 self.config = self.parse_config(ini_path)
50 self.config = self.parse_config(ini_path)
51 self.server_impl = None
51 self.server_impl = None
52
52
53 def parse_config(self, config_path):
53 def parse_config(self, config_path):
54 parser = configparser.ConfigParser()
54 parser = configparser.ConfigParser()
55 parser.read(config_path)
55 parser.read(config_path)
56 return parser
56 return parser
57
57
58 def update_key_access_time(self, key_id):
58 def update_key_access_time(self, key_id):
59 key = UserSshKeys().query().filter(
59 key = UserSshKeys().query().filter(
60 UserSshKeys.ssh_key_id == key_id).scalar()
60 UserSshKeys.ssh_key_id == key_id).scalar()
61 if key:
61 if key:
62 key.accessed_on = datetime.datetime.utcnow()
62 key.accessed_on = datetime.datetime.utcnow()
63 Session().add(key)
63 Session().add(key)
64 Session().commit()
64 Session().commit()
65 log.debug('Update key `%s` access time', key_id)
65 log.debug('Update key id:`%s` fingerprint:`%s` access time',
66 key_id, key.ssh_key_fingerprint)
66
67
67 def get_connection_info(self):
68 def get_connection_info(self):
68 """
69 """
69 connection_info
70 connection_info
70
71
71 Identifies the client and server ends of the connection.
72 Identifies the client and server ends of the connection.
72 The variable contains four space-separated values: client IP address,
73 The variable contains four space-separated values: client IP address,
73 client port number, server IP address, and server port number.
74 client port number, server IP address, and server port number.
74 """
75 """
75 conn = dict(
76 conn = dict(
76 client_ip=None,
77 client_ip=None,
77 client_port=None,
78 client_port=None,
78 server_ip=None,
79 server_ip=None,
79 server_port=None,
80 server_port=None,
80 )
81 )
81
82
82 info = self.connection_info.split(' ')
83 info = self.connection_info.split(' ')
83 if len(info) == 4:
84 if len(info) == 4:
84 conn['client_ip'] = info[0]
85 conn['client_ip'] = info[0]
85 conn['client_port'] = info[1]
86 conn['client_port'] = info[1]
86 conn['server_ip'] = info[2]
87 conn['server_ip'] = info[2]
87 conn['server_port'] = info[3]
88 conn['server_port'] = info[3]
88
89
89 return conn
90 return conn
90
91
91 def get_repo_details(self, mode):
92 def get_repo_details(self, mode):
92 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
93 vcs_type = mode if mode in ['svn', 'hg', 'git'] else None
93 mode = mode
94 mode = mode
94 repo_name = None
95 repo_name = None
95
96
96 hg_pattern = r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$'
97 hg_pattern = r'^hg\s+\-R\s+(\S+)\s+serve\s+\-\-stdio$'
97 hg_match = re.match(hg_pattern, self.command)
98 hg_match = re.match(hg_pattern, self.command)
98 if hg_match is not None:
99 if hg_match is not None:
99 vcs_type = 'hg'
100 vcs_type = 'hg'
100 repo_name = hg_match.group(1).strip('/')
101 repo_name = hg_match.group(1).strip('/')
101 return vcs_type, repo_name, mode
102 return vcs_type, repo_name, mode
102
103
103 git_pattern = (
104 git_pattern = (
104 r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$')
105 r'^git-(receive-pack|upload-pack)\s\'[/]?(\S+?)(|\.git)\'$')
105 git_match = re.match(git_pattern, self.command)
106 git_match = re.match(git_pattern, self.command)
106 if git_match is not None:
107 if git_match is not None:
107 vcs_type = 'git'
108 vcs_type = 'git'
108 repo_name = git_match.group(2).strip('/')
109 repo_name = git_match.group(2).strip('/')
109 mode = git_match.group(1)
110 mode = git_match.group(1)
110 return vcs_type, repo_name, mode
111 return vcs_type, repo_name, mode
111
112
112 svn_pattern = r'^svnserve -t'
113 svn_pattern = r'^svnserve -t'
113 svn_match = re.match(svn_pattern, self.command)
114 svn_match = re.match(svn_pattern, self.command)
114
115
115 if svn_match is not None:
116 if svn_match is not None:
116 vcs_type = 'svn'
117 vcs_type = 'svn'
117 # Repo name should be extracted from the input stream
118 # Repo name should be extracted from the input stream
118 return vcs_type, repo_name, mode
119 return vcs_type, repo_name, mode
119
120
120 return vcs_type, repo_name, mode
121 return vcs_type, repo_name, mode
121
122
122 def serve(self, vcs, repo, mode, user, permissions):
123 def serve(self, vcs, repo, mode, user, permissions):
123 store = ScmModel().repos_path
124 store = ScmModel().repos_path
124
125
125 log.debug(
126 log.debug(
126 'VCS detected:`%s` mode: `%s` repo_name: %s', vcs, mode, repo)
127 'VCS detected:`%s` mode: `%s` repo_name: %s', vcs, mode, repo)
127
128
128 if vcs == 'hg':
129 if vcs == 'hg':
129 server = MercurialServer(
130 server = MercurialServer(
130 store=store, ini_path=self.ini_path,
131 store=store, ini_path=self.ini_path,
131 repo_name=repo, user=user,
132 repo_name=repo, user=user,
132 user_permissions=permissions, config=self.config, env=self.env)
133 user_permissions=permissions, config=self.config, env=self.env)
133 self.server_impl = server
134 self.server_impl = server
134 return server.run()
135 return server.run()
135
136
136 elif vcs == 'git':
137 elif vcs == 'git':
137 server = GitServer(
138 server = GitServer(
138 store=store, ini_path=self.ini_path,
139 store=store, ini_path=self.ini_path,
139 repo_name=repo, repo_mode=mode, user=user,
140 repo_name=repo, repo_mode=mode, user=user,
140 user_permissions=permissions, config=self.config, env=self.env)
141 user_permissions=permissions, config=self.config, env=self.env)
141 self.server_impl = server
142 self.server_impl = server
142 return server.run()
143 return server.run()
143
144
144 elif vcs == 'svn':
145 elif vcs == 'svn':
145 server = SubversionServer(
146 server = SubversionServer(
146 store=store, ini_path=self.ini_path,
147 store=store, ini_path=self.ini_path,
147 repo_name=None, user=user,
148 repo_name=None, user=user,
148 user_permissions=permissions, config=self.config, env=self.env)
149 user_permissions=permissions, config=self.config, env=self.env)
149 self.server_impl = server
150 self.server_impl = server
150 return server.run()
151 return server.run()
151
152
152 else:
153 else:
153 raise Exception('Unrecognised VCS: {}'.format(vcs))
154 raise Exception('Unrecognised VCS: {}'.format(vcs))
154
155
155 def wrap(self):
156 def wrap(self):
156 mode = self.mode
157 mode = self.mode
157 user = self.user
158 user = self.user
158 user_id = self.user_id
159 user_id = self.user_id
159 key_id = self.key_id
160 key_id = self.key_id
160 shell = self.shell
161 shell = self.shell
161
162
162 scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
163 scm_detected, scm_repo, scm_mode = self.get_repo_details(mode)
163
164
164 log.debug(
165 log.debug(
165 'Mode: `%s` User: `%s:%s` Shell: `%s` SSH Command: `\"%s\"` '
166 'Mode: `%s` User: `%s:%s` Shell: `%s` SSH Command: `\"%s\"` '
166 'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
167 'SCM_DETECTED: `%s` SCM Mode: `%s` SCM Repo: `%s`',
167 mode, user, user_id, shell, self.command,
168 mode, user, user_id, shell, self.command,
168 scm_detected, scm_mode, scm_repo)
169 scm_detected, scm_mode, scm_repo)
169
170
170 # update last access time for this key
171 # update last access time for this key
171 self.update_key_access_time(key_id)
172 self.update_key_access_time(key_id)
172
173
173 log.debug('SSH Connection info %s', self.get_connection_info())
174 log.debug('SSH Connection info %s', self.get_connection_info())
174
175
175 if shell and self.command is None:
176 if shell and self.command is None:
176 log.info(
177 log.info(
177 'Dropping to shell, no command given and shell is allowed')
178 'Dropping to shell, no command given and shell is allowed')
178 os.execl('/bin/bash', '-l')
179 os.execl('/bin/bash', '-l')
179 exit_code = 1
180 exit_code = 1
180
181
181 elif scm_detected:
182 elif scm_detected:
182 user = User.get(user_id)
183 user = User.get(user_id)
183 if not user:
184 if not user:
184 log.warning('User with id %s not found', user_id)
185 log.warning('User with id %s not found', user_id)
185 exit_code = -1
186 exit_code = -1
186 return exit_code
187 return exit_code
187
188
188 auth_user = user.AuthUser()
189 auth_user = user.AuthUser()
189 permissions = auth_user.permissions['repositories']
190 permissions = auth_user.permissions['repositories']
190
191
191 try:
192 try:
192 exit_code, is_updated = self.serve(
193 exit_code, is_updated = self.serve(
193 scm_detected, scm_repo, scm_mode, user, permissions)
194 scm_detected, scm_repo, scm_mode, user, permissions)
194 except Exception:
195 except Exception:
195 log.exception('Error occurred during execution of SshWrapper')
196 log.exception('Error occurred during execution of SshWrapper')
196 exit_code = -1
197 exit_code = -1
197
198
198 elif self.command is None and shell is False:
199 elif self.command is None and shell is False:
199 log.error('No Command given.')
200 log.error('No Command given.')
200 exit_code = -1
201 exit_code = -1
201
202
202 else:
203 else:
203 log.error(
204 log.error(
204 'Unhandled Command: "%s" Aborting.', self.command)
205 'Unhandled Command: "%s" Aborting.', self.command)
205 exit_code = -1
206 exit_code = -1
206
207
207 return exit_code
208 return exit_code
@@ -1,151 +1,156 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 sys
22 import sys
23 import json
23 import json
24 import logging
24 import logging
25
25
26 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
26 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
27 from rhodecode.lib.vcs.conf import settings as vcs_settings
27 from rhodecode.lib.vcs.conf import settings as vcs_settings
28 from rhodecode.model.scm import ScmModel
28 from rhodecode.model.scm import ScmModel
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32
33 class VcsServer(object):
33 class VcsServer(object):
34 _path = None # set executable path for hg/git/svn binary
34 _path = None # set executable path for hg/git/svn binary
35 backend = None # set in child classes
35 backend = None # set in child classes
36 tunnel = None # subprocess handling tunnel
36 tunnel = None # subprocess handling tunnel
37 write_perms = ['repository.admin', 'repository.write']
37 write_perms = ['repository.admin', 'repository.write']
38 read_perms = ['repository.read', 'repository.admin', 'repository.write']
38 read_perms = ['repository.read', 'repository.admin', 'repository.write']
39
39
40 def __init__(self, user, user_permissions, config, env):
40 def __init__(self, user, user_permissions, config, env):
41 self.user = user
41 self.user = user
42 self.user_permissions = user_permissions
42 self.user_permissions = user_permissions
43 self.config = config
43 self.config = config
44 self.env = env
44 self.env = env
45 self.stdin = sys.stdin
45 self.stdin = sys.stdin
46
46
47 self.repo_name = None
47 self.repo_name = None
48 self.repo_mode = None
48 self.repo_mode = None
49 self.store = ''
49 self.store = ''
50 self.ini_path = ''
50 self.ini_path = ''
51
51
52 def _invalidate_cache(self, repo_name):
52 def _invalidate_cache(self, repo_name):
53 """
53 """
54 Set's cache for this repository for invalidation on next access
54 Set's cache for this repository for invalidation on next access
55
55
56 :param repo_name: full repo name, also a cache key
56 :param repo_name: full repo name, also a cache key
57 """
57 """
58 ScmModel().mark_for_invalidation(repo_name)
58 ScmModel().mark_for_invalidation(repo_name)
59
59
60 def has_write_perm(self):
60 def has_write_perm(self):
61 permission = self.user_permissions.get(self.repo_name)
61 permission = self.user_permissions.get(self.repo_name)
62 if permission in ['repository.write', 'repository.admin']:
62 if permission in ['repository.write', 'repository.admin']:
63 return True
63 return True
64
64
65 return False
65 return False
66
66
67 def _check_permissions(self, action):
67 def _check_permissions(self, action):
68 permission = self.user_permissions.get(self.repo_name)
68 permission = self.user_permissions.get(self.repo_name)
69 log.debug(
69 log.debug(
70 'permission for %s on %s are: %s',
70 'permission for %s on %s are: %s',
71 self.user, self.repo_name, permission)
71 self.user, self.repo_name, permission)
72
72
73 if not permission:
74 log.error('user `%s` permissions to repo:%s are empty. Forbidding access.',
75 self.user, self.repo_name)
76 return -2
77
73 if action == 'pull':
78 if action == 'pull':
74 if permission in self.read_perms:
79 if permission in self.read_perms:
75 log.info(
80 log.info(
76 'READ Permissions for User "%s" detected to repo "%s"!',
81 'READ Permissions for User "%s" detected to repo "%s"!',
77 self.user, self.repo_name)
82 self.user, self.repo_name)
78 return 0
83 return 0
79 else:
84 else:
80 if permission in self.write_perms:
85 if permission in self.write_perms:
81 log.info(
86 log.info(
82 'WRITE+ Permissions for User "%s" detected to repo "%s"!',
87 'WRITE+ Permissions for User "%s" detected to repo "%s"!',
83 self.user, self.repo_name)
88 self.user, self.repo_name)
84 return 0
89 return 0
85
90
86 log.error('Cannot properly fetch or allow user %s permissions. '
91 log.error('Cannot properly fetch or verify user `%s` permissions. '
87 'Return value is: %s, req action: %s',
92 'Permissions: %s, vcs action: %s',
88 self.user, permission, action)
93 self.user, permission, action)
89 return -2
94 return -2
90
95
91 def update_environment(self, action, extras=None):
96 def update_environment(self, action, extras=None):
92
97
93 scm_data = {
98 scm_data = {
94 'ip': os.environ['SSH_CLIENT'].split()[0],
99 'ip': os.environ['SSH_CLIENT'].split()[0],
95 'username': self.user.username,
100 'username': self.user.username,
96 'user_id': self.user.user_id,
101 'user_id': self.user.user_id,
97 'action': action,
102 'action': action,
98 'repository': self.repo_name,
103 'repository': self.repo_name,
99 'scm': self.backend,
104 'scm': self.backend,
100 'config': self.ini_path,
105 'config': self.ini_path,
101 'make_lock': None,
106 'make_lock': None,
102 'locked_by': [None, None],
107 'locked_by': [None, None],
103 'server_url': None,
108 'server_url': None,
104 'is_shadow_repo': False,
109 'is_shadow_repo': False,
105 'hooks_module': 'rhodecode.lib.hooks_daemon',
110 'hooks_module': 'rhodecode.lib.hooks_daemon',
106 'hooks': ['push', 'pull'],
111 'hooks': ['push', 'pull'],
107 'SSH': True,
112 'SSH': True,
108 'SSH_PERMISSIONS': self.user_permissions.get(self.repo_name)
113 'SSH_PERMISSIONS': self.user_permissions.get(self.repo_name)
109 }
114 }
110 if extras:
115 if extras:
111 scm_data.update(extras)
116 scm_data.update(extras)
112 os.putenv("RC_SCM_DATA", json.dumps(scm_data))
117 os.putenv("RC_SCM_DATA", json.dumps(scm_data))
113
118
114 def get_root_store(self):
119 def get_root_store(self):
115 root_store = self.store
120 root_store = self.store
116 if not root_store.endswith('/'):
121 if not root_store.endswith('/'):
117 # always append trailing slash
122 # always append trailing slash
118 root_store = root_store + '/'
123 root_store = root_store + '/'
119 return root_store
124 return root_store
120
125
121 def _handle_tunnel(self, extras):
126 def _handle_tunnel(self, extras):
122 # pre-auth
127 # pre-auth
123 action = 'pull'
128 action = 'pull'
124 exit_code = self._check_permissions(action)
129 exit_code = self._check_permissions(action)
125 if exit_code:
130 if exit_code:
126 return exit_code, False
131 return exit_code, False
127
132
128 req = self.env['request']
133 req = self.env['request']
129 server_url = req.host_url + req.script_name
134 server_url = req.host_url + req.script_name
130 extras['server_url'] = server_url
135 extras['server_url'] = server_url
131
136
132 log.debug('Using %s binaries from path %s', self.backend, self._path)
137 log.debug('Using %s binaries from path %s', self.backend, self._path)
133 exit_code = self.tunnel.run(extras)
138 exit_code = self.tunnel.run(extras)
134
139
135 return exit_code, action == "push"
140 return exit_code, action == "push"
136
141
137 def run(self):
142 def run(self):
138 extras = {}
143 extras = {}
139
144
140 callback_daemon, extras = prepare_callback_daemon(
145 callback_daemon, extras = prepare_callback_daemon(
141 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
146 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
142 host=vcs_settings.HOOKS_HOST,
147 host=vcs_settings.HOOKS_HOST,
143 use_direct_calls=False)
148 use_direct_calls=False)
144
149
145 with callback_daemon:
150 with callback_daemon:
146 try:
151 try:
147 return self._handle_tunnel(extras)
152 return self._handle_tunnel(extras)
148 finally:
153 finally:
149 log.debug('Running cleanup with cache invalidation')
154 log.debug('Running cleanup with cache invalidation')
150 if self.repo_name:
155 if self.repo_name:
151 self._invalidate_cache(self.repo_name)
156 self._invalidate_cache(self.repo_name)
@@ -1,87 +1,89 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">${_('SSH Keys')}</h3>
3 <h3 class="panel-title">${_('SSH Keys')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <div class="sshkeys_wrap">
6 <div class="sshkeys_wrap">
7 <table class="rctable ssh_keys">
7 <table class="rctable ssh_keys">
8 <tr>
8 <tr>
9 <th>${_('Fingerprint')}</th>
9 <th>${_('Fingerprint')}</th>
10 <th>${_('Description')}</th>
10 <th>${_('Description')}</th>
11 <th>${_('Created')}</th>
11 <th>${_('Created on')}</th>
12 <th>${_('Accessed on')}</th>
12 <th>${_('Action')}</th>
13 <th>${_('Action')}</th>
13 </tr>
14 </tr>
14 % if not c.ssh_enabled:
15 % if not c.ssh_enabled:
15 <tr><td colspan="4"><div class="">${_('SSH Keys usage is currently disabled, please ask your administrator to enable them.')}</div></td></tr>
16 <tr><td colspan="4"><div class="">${_('SSH Keys usage is currently disabled, please ask your administrator to enable them.')}</div></td></tr>
16 % else:
17 % else:
17 %if c.user_ssh_keys:
18 %if c.user_ssh_keys:
18 %for ssh_key in c.user_ssh_keys:
19 %for ssh_key in c.user_ssh_keys:
19 <tr class="">
20 <tr class="">
20 <td class="">
21 <td class="">
21 <code>${ssh_key.ssh_key_fingerprint}</code>
22 <code>${ssh_key.ssh_key_fingerprint}</code>
22 </td>
23 </td>
23 <td class="td-wrap">${ssh_key.description}</td>
24 <td class="td-wrap">${ssh_key.description}</td>
24 <td class="td-tags">${h.format_date(ssh_key.created_on)}</td>
25 <td class="td-tags">${h.format_date(ssh_key.created_on)}</td>
26 <td class="td-tags">${h.format_date(ssh_key.accessed_on)}</td>
25
27
26 <td class="td-action">
28 <td class="td-action">
27 ${h.secure_form(h.route_path('my_account_ssh_keys_delete'), request=request)}
29 ${h.secure_form(h.route_path('my_account_ssh_keys_delete'), request=request)}
28 ${h.hidden('del_ssh_key', ssh_key.ssh_key_id)}
30 ${h.hidden('del_ssh_key', ssh_key.ssh_key_id)}
29 <button class="btn btn-link btn-danger" type="submit"
31 <button class="btn btn-link btn-danger" type="submit"
30 onclick="return confirm('${_('Confirm to remove ssh key %s') % ssh_key.ssh_key_fingerprint}');">
32 onclick="return confirm('${_('Confirm to remove ssh key %s') % ssh_key.ssh_key_fingerprint}');">
31 ${_('Delete')}
33 ${_('Delete')}
32 </button>
34 </button>
33 ${h.end_form()}
35 ${h.end_form()}
34 </td>
36 </td>
35 </tr>
37 </tr>
36 %endfor
38 %endfor
37 %else:
39 %else:
38 <tr><td colspan="4"><div class="">${_('No additional ssh keys specified')}</div></td></tr>
40 <tr><td colspan="4"><div class="">${_('No additional ssh keys specified')}</div></td></tr>
39 %endif
41 %endif
40 % endif
42 % endif
41 </table>
43 </table>
42 </div>
44 </div>
43
45
44 % if c.ssh_enabled:
46 % if c.ssh_enabled:
45 <div class="user_ssh_keys">
47 <div class="user_ssh_keys">
46 ${h.secure_form(h.route_path('my_account_ssh_keys_add'), request=request)}
48 ${h.secure_form(h.route_path('my_account_ssh_keys_add'), request=request)}
47 <div class="form form-vertical">
49 <div class="form form-vertical">
48 <!-- fields -->
50 <!-- fields -->
49 <div class="fields">
51 <div class="fields">
50 <div class="field">
52 <div class="field">
51 <div class="label">
53 <div class="label">
52 <label for="new_email">${_('New ssh key')}:</label>
54 <label for="new_email">${_('New ssh key')}:</label>
53 </div>
55 </div>
54 <div class="input">
56 <div class="input">
55 ${h.text('description', class_='medium', placeholder=_('Description'))}
57 ${h.text('description', class_='medium', placeholder=_('Description'))}
56 <a href="${h.route_path('my_account_ssh_keys_generate')}">${_('Generate random RSA key')}</a>
58 <a href="${h.route_path('my_account_ssh_keys_generate')}">${_('Generate random RSA key')}</a>
57 </div>
59 </div>
58 </div>
60 </div>
59
61
60 <div class="field">
62 <div class="field">
61 <div class="textarea text-area editor">
63 <div class="textarea text-area editor">
62 ${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'"))}
64 ${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'"))}
63 </div>
65 </div>
64 </div>
66 </div>
65
67
66 <div class="buttons">
68 <div class="buttons">
67 ${h.submit('save',_('Add'),class_="btn")}
69 ${h.submit('save',_('Add'),class_="btn")}
68 ${h.reset('reset',_('Reset'),class_="btn")}
70 ${h.reset('reset',_('Reset'),class_="btn")}
69 </div>
71 </div>
70 % if c.default_key:
72 % if c.default_key:
71 ${_('Click add to use this generate SSH key')}
73 ${_('Click add to use this generate SSH key')}
72 % endif
74 % endif
73 </div>
75 </div>
74 </div>
76 </div>
75 ${h.end_form()}
77 ${h.end_form()}
76 </div>
78 </div>
77 % endif
79 % endif
78 </div>
80 </div>
79 </div>
81 </div>
80
82
81 <script>
83 <script>
82
84
83 $(document).ready(function(){
85 $(document).ready(function(){
84
86
85
87
86 });
88 });
87 </script>
89 </script>
@@ -1,81 +1,83 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">${_('SSH Keys')}</h3>
3 <h3 class="panel-title">${_('SSH Keys')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <div class="sshkeys_wrap">
6 <div class="sshkeys_wrap">
7 <table class="rctable ssh_keys">
7 <table class="rctable ssh_keys">
8 <tr>
8 <tr>
9 <th>${_('Fingerprint')}</th>
9 <th>${_('Fingerprint')}</th>
10 <th>${_('Description')}</th>
10 <th>${_('Description')}</th>
11 <th>${_('Created')}</th>
11 <th>${_('Created on')}</th>
12 <th>${_('Accessed on')}</th>
12 <th>${_('Action')}</th>
13 <th>${_('Action')}</th>
13 </tr>
14 </tr>
14 %if c.user_ssh_keys:
15 %if c.user_ssh_keys:
15 %for ssh_key in c.user_ssh_keys:
16 %for ssh_key in c.user_ssh_keys:
16 <tr class="">
17 <tr class="">
17 <td class="">
18 <td class="">
18 <code>${ssh_key.ssh_key_fingerprint}</code>
19 <code>${ssh_key.ssh_key_fingerprint}</code>
19 </td>
20 </td>
20 <td class="td-wrap">${ssh_key.description}</td>
21 <td class="td-wrap">${ssh_key.description}</td>
21 <td class="td-tags">${h.format_date(ssh_key.created_on)}</td>
22 <td class="td-tags">${h.format_date(ssh_key.created_on)}</td>
23 <td class="td-tags">${h.format_date(ssh_key.accessed_on)}</td>
22
24
23 <td class="td-action">
25 <td class="td-action">
24 ${h.secure_form(h.route_path('edit_user_ssh_keys_delete', user_id=c.user.user_id), request=request)}
26 ${h.secure_form(h.route_path('edit_user_ssh_keys_delete', user_id=c.user.user_id), request=request)}
25 ${h.hidden('del_ssh_key', ssh_key.ssh_key_id)}
27 ${h.hidden('del_ssh_key', ssh_key.ssh_key_id)}
26 <button class="btn btn-link btn-danger" type="submit"
28 <button class="btn btn-link btn-danger" type="submit"
27 onclick="return confirm('${_('Confirm to remove ssh key %s') % ssh_key.ssh_key_fingerprint}');">
29 onclick="return confirm('${_('Confirm to remove ssh key %s') % ssh_key.ssh_key_fingerprint}');">
28 ${_('Delete')}
30 ${_('Delete')}
29 </button>
31 </button>
30 ${h.end_form()}
32 ${h.end_form()}
31 </td>
33 </td>
32 </tr>
34 </tr>
33 %endfor
35 %endfor
34 %else:
36 %else:
35 <tr><td><div class="ip">${_('No additional ssh keys specified')}</div></td></tr>
37 <tr><td><div class="ip">${_('No additional ssh keys specified')}</div></td></tr>
36 %endif
38 %endif
37 </table>
39 </table>
38 </div>
40 </div>
39
41
40 <div class="user_ssh_keys">
42 <div class="user_ssh_keys">
41 ${h.secure_form(h.route_path('edit_user_ssh_keys_add', user_id=c.user.user_id), request=request)}
43 ${h.secure_form(h.route_path('edit_user_ssh_keys_add', user_id=c.user.user_id), request=request)}
42 <div class="form form-vertical">
44 <div class="form form-vertical">
43 <!-- fields -->
45 <!-- fields -->
44 <div class="fields">
46 <div class="fields">
45 <div class="field">
47 <div class="field">
46 <div class="label">
48 <div class="label">
47 <label for="new_email">${_('New ssh key')}:</label>
49 <label for="new_email">${_('New ssh key')}:</label>
48 </div>
50 </div>
49 <div class="input">
51 <div class="input">
50 ${h.text('description', class_='medium', placeholder=_('Description'))}
52 ${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>
53 <a href="${h.route_path('edit_user_ssh_keys_generate_keypair', user_id=c.user.user_id)}">${_('Generate random RSA key')}</a>
52 </div>
54 </div>
53 </div>
55 </div>
54
56
55 <div class="field">
57 <div class="field">
56 <div class="textarea text-area editor">
58 <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'"))}
59 ${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>
60 </div>
59 </div>
61 </div>
60
62
61 <div class="buttons">
63 <div class="buttons">
62 ${h.submit('save',_('Add'),class_="btn")}
64 ${h.submit('save',_('Add'),class_="btn")}
63 ${h.reset('reset',_('Reset'),class_="btn")}
65 ${h.reset('reset',_('Reset'),class_="btn")}
64 </div>
66 </div>
65 % if c.default_key:
67 % if c.default_key:
66 ${_('Click add to use this generate SSH key')}
68 ${_('Click add to use this generate SSH key')}
67 % endif
69 % endif
68 </div>
70 </div>
69 </div>
71 </div>
70 ${h.end_form()}
72 ${h.end_form()}
71 </div>
73 </div>
72 </div>
74 </div>
73 </div>
75 </div>
74
76
75 <script>
77 <script>
76
78
77 $(document).ready(function(){
79 $(document).ready(function(){
78
80
79
81
80 });
82 });
81 </script>
83 </script>
General Comments 0
You need to be logged in to leave comments. Login now