Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 1 | """ |
| 2 | NETCONF connections |
| 3 | File: connections.py |
| 4 | Author: Radek Krejci <rkrejci@cesnet.cz> |
| 5 | """ |
| 6 | |
Radek Krejci | 67c922d | 2017-09-21 13:56:41 +0200 | [diff] [blame] | 7 | import json |
| 8 | import os |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 9 | import logging |
Radek Krejci | 67c922d | 2017-09-21 13:56:41 +0200 | [diff] [blame] | 10 | |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 11 | from liberouterapi import socketio, auth |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 12 | from flask import request |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 13 | from flask_socketio import emit |
| 14 | from eventlet import event |
| 15 | from eventlet.timeout import Timeout |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 16 | import yang |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 17 | import netconf2 as nc |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 18 | |
| 19 | from .inventory import INVENTORY |
Radek Krejci | a747832 | 2018-06-19 13:09:52 +0200 | [diff] [blame] | 20 | from .devices import devices_get, devices_replace |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 21 | from .error import NetopeerException |
Radek Krejci | ce4d838 | 2018-08-23 13:03:40 +0200 | [diff] [blame] | 22 | from .schemas import schemas_update |
Radek Krejci | a133960 | 2017-11-02 13:52:38 +0100 | [diff] [blame] | 23 | from .data import * |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 24 | |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 25 | log = logging.getLogger(__name__) |
| 26 | |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 27 | sessions = {} |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 28 | connect_sio_data = {} |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 29 | |
| 30 | |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 31 | def connect_sio_send(data): |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 32 | try: |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 33 | e = connect_sio_data[data['id']] |
| 34 | e.send(data) |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 35 | except KeyError: |
| 36 | pass |
| 37 | |
| 38 | |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 39 | @socketio.on('device_auth_password') |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 40 | @socketio.on('hostcheck_result') |
| 41 | def hostkey_check_answer(data): |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 42 | connect_sio_send(data) |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 43 | |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 44 | |
Radek Krejci | e20e4d8 | 2017-11-08 14:18:05 +0100 | [diff] [blame] | 45 | def hostkey_check(hostname, state, keytype, hexa, priv): |
Radek Krejci | a747832 | 2018-06-19 13:09:52 +0200 | [diff] [blame] | 46 | if 'fingerprint' in priv['device']: |
| 47 | # check according to the stored fingerprint from previous connection |
| 48 | if hexa == priv['device']['fingerprint']: |
| 49 | return True |
| 50 | elif state != 2: |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 51 | log.error("Incorrect host key state") |
Radek Krejci | a747832 | 2018-06-19 13:09:52 +0200 | [diff] [blame] | 52 | state = 2 |
| 53 | |
| 54 | # ask frontend/user for hostkey check |
| 55 | params = {'id': priv['session']['session_id'], 'hostname' : hostname, 'state' : state, 'keytype' : keytype, 'hexa' : hexa} |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 56 | socketio.emit('hostcheck', params, callback = connect_sio_send) |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 57 | |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 58 | result = False |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 59 | timeout = Timeout(30) |
| 60 | try: |
Radek Krejci | a747832 | 2018-06-19 13:09:52 +0200 | [diff] [blame] | 61 | # wait for response from the frontend |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 62 | e = connect_sio_data[priv['session']['session_id']] = event.Event() |
| 63 | data = e.wait() |
| 64 | result = data['result'] |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 65 | except Timeout: |
Radek Krejci | a747832 | 2018-06-19 13:09:52 +0200 | [diff] [blame] | 66 | # no response received within the timeout |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 67 | log.info("socketio: hostcheck timeout.") |
| 68 | except KeyError: |
| 69 | # invalid response |
| 70 | log.error("socketio: invalid hostcheck_result received.") |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 71 | finally: |
Radek Krejci | a747832 | 2018-06-19 13:09:52 +0200 | [diff] [blame] | 72 | # we have the response |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 73 | connect_sio_data.pop(priv['session']['session_id'], None) |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 74 | timeout.cancel() |
| 75 | |
Radek Krejci | a747832 | 2018-06-19 13:09:52 +0200 | [diff] [blame] | 76 | if result: |
| 77 | # store confirmed fingerprint for future connections |
| 78 | priv['device']['fingerprint'] = hexa; |
| 79 | devices_replace(priv['device']['id'], priv['session']['user'].username, priv['device']) |
| 80 | |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 81 | return result |
| 82 | |
Radek Krejci | e20e4d8 | 2017-11-08 14:18:05 +0100 | [diff] [blame] | 83 | |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 84 | def auth_common(session_id): |
| 85 | result = None |
| 86 | timeout = Timeout(60) |
| 87 | try: |
| 88 | # wait for response from the frontend |
| 89 | e = connect_sio_data[session_id] = event.Event() |
| 90 | data = e.wait() |
| 91 | print(data) |
| 92 | result = data['password'] |
| 93 | except Timeout: |
| 94 | # no response received within the timeout |
| 95 | log.info("socketio: auth request timeout.") |
| 96 | except KeyError: |
| 97 | # no password |
| 98 | log.info("socketio: invalid credential data received.") |
| 99 | finally: |
| 100 | # we have the response |
| 101 | connect_sio_data.pop(session_id, None) |
| 102 | timeout.cancel() |
| 103 | |
| 104 | return result |
| 105 | |
| 106 | |
| 107 | def auth_password(username, hostname, priv): |
| 108 | print("auth_password callback") |
| 109 | socketio.emit('device_auth', {'id': priv, 'type': 'Password Authentication', 'msg': username + '@' + hostname}, callback = connect_sio_send) |
| 110 | return auth_common(priv) |
| 111 | |
| 112 | |
| 113 | def auth_interactive(name, instruction, prompt, priv): |
| 114 | print("auth_interactive callback") |
| 115 | socketio.emit('device_auth', {'id': priv, 'type': name, 'msg': instruction, 'prompt': prompt}, callback = connect_sio_send) |
| 116 | return auth_common(priv) |
| 117 | |
| 118 | |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 119 | @auth.required() |
| 120 | def connect(): |
Jakub Man | 47834ec | 2018-06-21 15:04:35 +0200 | [diff] [blame] | 121 | session = auth.lookup(request.headers.get('lgui-Authorization', None)) |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 122 | user = session['user'] |
| 123 | path = os.path.join(INVENTORY, user.username) |
| 124 | |
Radek Krejci | a6c8b41 | 2017-10-17 16:59:38 +0200 | [diff] [blame] | 125 | data = request.get_json() |
| 126 | if 'id' in data: |
| 127 | # stored device |
| 128 | device = devices_get(data['id'], user.username) |
| 129 | elif 'device' in data: |
| 130 | # one-time connect, the device is specified in request |
| 131 | device = data['device'] |
| 132 | else: |
| 133 | raise NetopeerException('Invalid connection request.') |
| 134 | |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 135 | if not device: |
| 136 | raise NetopeerException('Unknown device to connect to request.') |
| 137 | |
| 138 | nc.setSearchpath(path) |
| 139 | |
Radek Krejci | 14b7aac | 2018-07-02 14:33:26 +0200 | [diff] [blame] | 140 | if 'password' in device: |
| 141 | ssh = nc.SSH(device['username'], password = device['password']) |
| 142 | else: |
| 143 | ssh = nc.SSH(device['username']) |
| 144 | ssh.setAuthPasswordClb(auth_password, session['session_id']) |
| 145 | ssh.setAuthInteractiveClb(auth_interactive, session['session_id']) |
| 146 | |
Radek Krejci | a747832 | 2018-06-19 13:09:52 +0200 | [diff] [blame] | 147 | ssh.setAuthHostkeyCheckClb(hostkey_check, {'session': session, 'device' : device}) |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 148 | try: |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 149 | ncs = nc.Session(device['hostname'], device['port'], ssh) |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 150 | except Exception as e: |
| 151 | return(json.dumps({'success': False, 'error-msg': str(e)})) |
| 152 | |
| 153 | if not user.username in sessions: |
| 154 | sessions[user.username] = {} |
| 155 | |
| 156 | # use key (as hostname:port:session-id) to store the created NETCONF session |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 157 | key = ncs.host + ":" + str(ncs.port) + ":" + ncs.id |
Radek Krejci | 4d3896c | 2018-01-08 17:10:43 +0100 | [diff] [blame] | 158 | sessions[user.username][key] = {} |
Radek Krejci | 39cce67 | 2018-06-15 16:15:18 +0200 | [diff] [blame] | 159 | sessions[user.username][key]['session'] = ncs |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 160 | |
Radek Krejci | ce4d838 | 2018-08-23 13:03:40 +0200 | [diff] [blame] | 161 | # update inventory's list of schemas |
| 162 | schemas_update(path) |
| 163 | |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 164 | return(json.dumps({'success': True, 'session-key': key})) |
| 165 | |
| 166 | |
| 167 | @auth.required() |
| 168 | def session_get_capabilities(): |
Jakub Man | 47834ec | 2018-06-21 15:04:35 +0200 | [diff] [blame] | 169 | session = auth.lookup(request.headers.get('lgui-Authorization', None)) |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 170 | user = session['user'] |
| 171 | req = request.args.to_dict() |
| 172 | |
| 173 | if not 'key' in req: |
| 174 | return(json.dumps({'success': False, 'error-msg': 'Missing session key.'})) |
| 175 | |
| 176 | if not user.username in sessions: |
| 177 | sessions[user.username] = {} |
Jakub Man | 47834ec | 2018-06-21 15:04:35 +0200 | [diff] [blame] | 178 | |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 179 | key = req['key'] |
| 180 | if not key in sessions[user.username]: |
| 181 | return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'})) |
| 182 | |
| 183 | cpblts = [] |
Radek Krejci | 4d3896c | 2018-01-08 17:10:43 +0100 | [diff] [blame] | 184 | for c in sessions[user.username][key]['session'].capabilities: |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 185 | cpblts.append(c) |
| 186 | |
| 187 | return(json.dumps({'success': True, 'capabilities': cpblts})) |
| 188 | |
| 189 | @auth.required() |
Radek Krejci | 2e57856 | 2017-10-17 11:11:13 +0200 | [diff] [blame] | 190 | def session_get(): |
Jakub Man | 47834ec | 2018-06-21 15:04:35 +0200 | [diff] [blame] | 191 | session = auth.lookup(request.headers.get('lgui-Authorization', None)) |
Radek Krejci | 2e57856 | 2017-10-17 11:11:13 +0200 | [diff] [blame] | 192 | user = session['user'] |
| 193 | req = request.args.to_dict() |
| 194 | |
| 195 | if not 'key' in req: |
| 196 | return(json.dumps({'success': False, 'error-msg': 'Missing session key.'})) |
Radek Krejci | a133960 | 2017-11-02 13:52:38 +0100 | [diff] [blame] | 197 | if not 'recursive' in req: |
| 198 | return(json.dumps({'success': False, 'error-msg': 'Missing recursive flag.'})) |
Radek Krejci | 2e57856 | 2017-10-17 11:11:13 +0200 | [diff] [blame] | 199 | |
| 200 | if not user.username in sessions: |
| 201 | sessions[user.username] = {} |
| 202 | |
| 203 | key = req['key'] |
| 204 | if not key in sessions[user.username]: |
| 205 | return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'})) |
| 206 | |
Radek Krejci | 2e57856 | 2017-10-17 11:11:13 +0200 | [diff] [blame] | 207 | try: |
Radek Krejci | 4d3896c | 2018-01-08 17:10:43 +0100 | [diff] [blame] | 208 | sessions[user.username][key]['data'] = sessions[user.username][key]['session'].rpcGet() |
Radek Krejci | 010475d | 2018-03-08 13:14:19 +0100 | [diff] [blame] | 209 | except ConnectionError as e: |
| 210 | reply = {'success': False, 'error': [{'msg': str(e)}]} |
| 211 | del sessions[user.username][key] |
| 212 | return(json.dumps(reply)) |
Radek Krejci | 2e57856 | 2017-10-17 11:11:13 +0200 | [diff] [blame] | 213 | except nc.ReplyError as e: |
| 214 | reply = {'success': False, 'error': []} |
| 215 | for err in e.args[0]: |
| 216 | reply['error'].append(json.loads(str(err))) |
| 217 | return(json.dumps(reply)) |
| 218 | |
Radek Krejci | a133960 | 2017-11-02 13:52:38 +0100 | [diff] [blame] | 219 | if not 'path' in req: |
Radek Krejci | 4d3896c | 2018-01-08 17:10:43 +0100 | [diff] [blame] | 220 | return(dataInfoRoots(sessions[user.username][key]['data'], True if req['recursive'] == 'true' else False)) |
Radek Krejci | a133960 | 2017-11-02 13:52:38 +0100 | [diff] [blame] | 221 | else: |
Radek Krejci | 4d3896c | 2018-01-08 17:10:43 +0100 | [diff] [blame] | 222 | return(dataInfoSubtree(sessions[user.username][key]['data'], req['path'], True if req['recursive'] == 'true' else False)) |
| 223 | |
| 224 | |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 225 | def _checkvalue(session, req, schema): |
| 226 | user = session['user']; |
Radek Krejci | 4d3896c | 2018-01-08 17:10:43 +0100 | [diff] [blame] | 227 | |
| 228 | if not 'key' in req: |
| 229 | return(json.dumps({'success': False, 'error-msg': 'Missing session key.'})) |
| 230 | if not 'path' in req: |
| 231 | return(json.dumps({'success': False, 'error-msg': 'Missing path to validate value.'})) |
| 232 | if not 'value' in req: |
| 233 | return(json.dumps({'success': False, 'error-msg': 'Missing value to validate.'})) |
| 234 | |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 235 | key = req['key'] |
| 236 | if not key in sessions[user.username]: |
| 237 | return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'})) |
| 238 | |
Radek Krejci | cf719cb | 2018-02-15 16:47:32 +0100 | [diff] [blame] | 239 | ctx = sessions[user.username][key]['session'].context; |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 240 | if schema: |
Radek Krejci | cf719cb | 2018-02-15 16:47:32 +0100 | [diff] [blame] | 241 | search = ctx.find_path(req['path']) |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 242 | else: |
| 243 | search = sessions[user.username][key]['data'].find_path(req['path']) |
| 244 | |
| 245 | if search.number() != 1: |
| 246 | return(json.dumps({'success': False, 'error-msg': 'Invalid data path.'})) |
| 247 | |
| 248 | if schema: |
| 249 | node = search.schema()[0] |
| 250 | else: |
| 251 | node = search.data()[0] |
| 252 | |
| 253 | if node.validate_value(req['value']): |
Radek Krejci | cf719cb | 2018-02-15 16:47:32 +0100 | [diff] [blame] | 254 | errors = yang.get_ly_errors(ctx) |
| 255 | if errors.size(): |
| 256 | return(json.dumps({'success': False, 'error-msg': errors[errors.size() - 1].errmsg()})) |
| 257 | else: |
| 258 | return(json.dumps({'success': False, 'error-msg': 'unknown error'})) |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 259 | |
| 260 | return(json.dumps({'success': True})) |
| 261 | |
| 262 | @auth.required() |
| 263 | def data_checkvalue(): |
Jakub Man | 47834ec | 2018-06-21 15:04:35 +0200 | [diff] [blame] | 264 | session = auth.lookup(request.headers.get('lgui-Authorization', None)) |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 265 | req = request.args.to_dict() |
| 266 | |
| 267 | return _checkvalue(session, req, False) |
| 268 | |
| 269 | |
| 270 | @auth.required() |
| 271 | def schema_checkvalue(): |
Jakub Man | 47834ec | 2018-06-21 15:04:35 +0200 | [diff] [blame] | 272 | session = auth.lookup(request.headers.get('lgui-Authorization', None)) |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 273 | req = request.args.to_dict() |
| 274 | |
| 275 | return _checkvalue(session, req, True) |
| 276 | |
| 277 | |
| 278 | @auth.required() |
Radek Krejci | d0ce4cf | 2018-02-09 14:44:34 +0100 | [diff] [blame] | 279 | def schema_values(): |
Jakub Man | 47834ec | 2018-06-21 15:04:35 +0200 | [diff] [blame] | 280 | session = auth.lookup(request.headers.get('lgui-Authorization', None)) |
Radek Krejci | d0ce4cf | 2018-02-09 14:44:34 +0100 | [diff] [blame] | 281 | user = session['user'] |
| 282 | req = request.args.to_dict() |
| 283 | |
| 284 | if not 'key' in req: |
| 285 | return(json.dumps({'success': False, 'error-msg': 'Missing session key.'})) |
| 286 | if not 'path' in req: |
| 287 | return(json.dumps({'success': False, 'error-msg': 'Missing path to validate value.'})) |
| 288 | |
| 289 | key = req['key'] |
| 290 | if not key in sessions[user.username]: |
| 291 | return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'})) |
| 292 | |
| 293 | search = sessions[user.username][key]['session'].context.find_path(req['path']) |
| 294 | if search.number() != 1: |
| 295 | return(json.dumps({'success': False, 'error-msg': 'Invalid data path.'})) |
| 296 | schema = search.schema()[0] |
| 297 | |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 298 | if schema.nodetype() != yang.LYS_LEAF and schema.nodetype != yang.LYS_LEAFLIST: |
Radek Krejci | d0ce4cf | 2018-02-09 14:44:34 +0100 | [diff] [blame] | 299 | result = None |
| 300 | else: |
| 301 | result = typeValues(schema.subtype().type(), []) |
| 302 | return(json.dumps({'success': True, 'data': result})) |
| 303 | |
| 304 | |
| 305 | @auth.required() |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 306 | def schema_info(): |
Jakub Man | 47834ec | 2018-06-21 15:04:35 +0200 | [diff] [blame] | 307 | session = auth.lookup(request.headers.get('lgui-Authorization', None)) |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 308 | user = session['user'] |
| 309 | req = request.args.to_dict() |
| 310 | |
| 311 | if not 'key' in req: |
| 312 | return(json.dumps({'success': False, 'error-msg': 'Missing session key.'})) |
| 313 | if not 'path' in req: |
| 314 | return(json.dumps({'success': False, 'error-msg': 'Missing path to validate value.'})) |
Radek Krejci | 4d3896c | 2018-01-08 17:10:43 +0100 | [diff] [blame] | 315 | |
| 316 | key = req['key'] |
| 317 | if not key in sessions[user.username]: |
| 318 | return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'})) |
| 319 | |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 320 | if req['path'] == '/': |
Radek Krejci | f71867f | 2018-01-30 13:28:28 +0100 | [diff] [blame] | 321 | node = None |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 322 | else: |
| 323 | search = sessions[user.username][key]['session'].context.find_path(req['path']) |
| 324 | if search.number() != 1: |
| 325 | return(json.dumps({'success': False, 'error-msg': 'Invalid data path.'})) |
| 326 | node = search.schema()[0] |
Radek Krejci | 4d3896c | 2018-01-08 17:10:43 +0100 | [diff] [blame] | 327 | |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 328 | result = []; |
| 329 | if 'relative' in req: |
| 330 | if req['relative'] == 'children': |
Radek Krejci | f71867f | 2018-01-30 13:28:28 +0100 | [diff] [blame] | 331 | if node: |
| 332 | instantiables = node.child_instantiables(0) |
| 333 | else: |
| 334 | # top level |
| 335 | instantiables = sessions[user.username][key]['session'].context.data_instantiables(0) |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 336 | elif req['relative'] == 'siblings': |
| 337 | if node.parent(): |
Radek Krejci | f71867f | 2018-01-30 13:28:28 +0100 | [diff] [blame] | 338 | instantiables = node.parent().child_instantiables(0) |
| 339 | else: |
| 340 | # top level |
| 341 | instantiables = sessions[user.username][key]['session'].context.data_instantiables(0) |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 342 | else: |
| 343 | return(json.dumps({'success': False, 'error-msg': 'Invalid relative parameter.'})) |
Radek Krejci | f71867f | 2018-01-30 13:28:28 +0100 | [diff] [blame] | 344 | |
| 345 | for child in instantiables: |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 346 | if child.flags() & yang.LYS_CONFIG_R: |
Radek Krejci | f71867f | 2018-01-30 13:28:28 +0100 | [diff] [blame] | 347 | # ignore status nodes |
| 348 | continue |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 349 | if child.nodetype() & (yang.LYS_RPC | yang.LYS_NOTIF | yang.LYS_ACTION): |
Radek Krejci | f71867f | 2018-01-30 13:28:28 +0100 | [diff] [blame] | 350 | # ignore RPCs, Notifications and Actions |
| 351 | continue |
| 352 | result.append(schemaInfoNode(child)) |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 353 | else: |
| 354 | result.append(schemaInfoNode(node)) |
Radek Krejci | 4d3896c | 2018-01-08 17:10:43 +0100 | [diff] [blame] | 355 | |
Radek Krejci | 6e772b2 | 2018-01-25 13:28:57 +0100 | [diff] [blame] | 356 | return(json.dumps({'success': True, 'data': result})) |
Radek Krejci | 2e57856 | 2017-10-17 11:11:13 +0200 | [diff] [blame] | 357 | |
| 358 | |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 359 | def _create_child(ctx, parent, child_def): |
Radek Krejci | ad45e57 | 2018-02-21 10:54:54 +0100 | [diff] [blame] | 360 | at = child_def['info']['module'].find('@') |
| 361 | if at == -1: |
| 362 | module = ctx.get_module(child_def['info']['module']) |
| 363 | else: |
| 364 | module = ctx.get_module(child_def['info']['module'][:at], child_def['info']['module'][at + 1:]) |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 365 | # print('child: ' + json.dumps(child_def)) |
Radek Krejci | 7a244f0 | 2018-02-21 09:46:18 +0100 | [diff] [blame] | 366 | # print('parent: ' + parent.schema().name()) |
| 367 | # print('module: ' + module.name()) |
| 368 | # print('name: ' + child_def['info']['name']) |
| 369 | if child_def['info']['type'] == 4 : |
| 370 | # print('value: ' + str(child_def['value'])) |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 371 | yang.Data_Node(parent, module, child_def['info']['name'], child_def['value']) |
Radek Krejci | 7a244f0 | 2018-02-21 09:46:18 +0100 | [diff] [blame] | 372 | elif child_def['info']['type'] == 8: |
| 373 | # print('value: ' + child_def['value'][0]) |
| 374 | yang.Data_Node(parent, module, child_def['info']['name'], child_def['value'][0]) |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 375 | else: |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 376 | child = yang.Data_Node(parent, module, child_def['info']['name']) |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 377 | if 'children' in child_def: |
| 378 | for grandchild in child_def['children']: |
| 379 | _create_child(ctx, child, grandchild) |
| 380 | |
| 381 | |
| 382 | @auth.required() |
| 383 | def session_commit(): |
Jakub Man | 47834ec | 2018-06-21 15:04:35 +0200 | [diff] [blame] | 384 | session = auth.lookup(request.headers.get('lgui-Authorization', None)) |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 385 | user = session['user'] |
| 386 | |
Radek Krejci | 5a93f9d | 2018-03-21 13:17:18 +0100 | [diff] [blame] | 387 | req = request.get_json(keep_order = True) |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 388 | if not 'key' in req: |
| 389 | return(json.dumps({'success': False, 'error-msg': 'Missing session key.'})) |
| 390 | if not 'modifications' in req: |
| 391 | return(json.dumps({'success': False, 'error-msg': 'Missing modifications.'})) |
| 392 | |
| 393 | mods = req['modifications'] |
| 394 | ctx = sessions[user.username][req['key']]['session'].context |
| 395 | root = None |
Radek Krejci | 010475d | 2018-03-08 13:14:19 +0100 | [diff] [blame] | 396 | reorders = [] |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 397 | for key in mods: |
| 398 | recursion = False |
| 399 | # get correct path and value if needed |
| 400 | path = key |
| 401 | value = None |
| 402 | if mods[key]['type'] == 'change': |
| 403 | value = mods[key]['value'] |
| 404 | elif mods[key]['type'] == 'create' or mods[key]['type'] == 'replace': |
Radek Krejci | 7a244f0 | 2018-02-21 09:46:18 +0100 | [diff] [blame] | 405 | if mods[key]['data']['info']['type'] == 1: |
| 406 | # creating/replacing container |
| 407 | recursion = True |
| 408 | elif mods[key]['data']['info']['type'] == 4: |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 409 | # creating/replacing leaf |
| 410 | value = mods[key]['data']['value'] |
Radek Krejci | 7a244f0 | 2018-02-21 09:46:18 +0100 | [diff] [blame] | 411 | elif mods[key]['data']['info']['type'] == 8: |
| 412 | # creating/replacing leaf-list |
| 413 | value = mods[key]['data']['value'][0] |
| 414 | path = mods[key]['data']['path'] |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 415 | elif mods[key]['data']['info']['type'] == 16: |
| 416 | recursion = True |
| 417 | path = mods[key]['data']['path'] |
Radek Krejci | 010475d | 2018-03-08 13:14:19 +0100 | [diff] [blame] | 418 | elif mods[key]['type'] == 'reorder': |
| 419 | # postpone reorders |
| 420 | reorders.extend(mods[key]['transactions']) |
| 421 | continue |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 422 | |
| 423 | # create node |
Radek Krejci | 7a244f0 | 2018-02-21 09:46:18 +0100 | [diff] [blame] | 424 | # print("creating " + path) |
| 425 | # print("value " + str(value)) |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 426 | if root: |
| 427 | root.new_path(ctx, path, value, 0, 0) |
| 428 | else: |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 429 | root = yang.Data_Node(ctx, path, value, 0, 0) |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 430 | node = root.find_path(path).data()[0]; |
| 431 | |
| 432 | # set operation attribute and add additional data if any |
| 433 | if mods[key]['type'] == 'change': |
| 434 | node.insert_attr(None, 'ietf-netconf:operation', 'merge') |
| 435 | elif mods[key]['type'] == 'delete': |
| 436 | node.insert_attr(None, 'ietf-netconf:operation', 'delete') |
| 437 | elif mods[key]['type'] == 'create': |
| 438 | node.insert_attr(None, 'ietf-netconf:operation', 'create') |
| 439 | elif mods[key]['type'] == 'replace': |
| 440 | node.insert_attr(None, 'ietf-netconf:operation', 'replace') |
| 441 | else: |
| 442 | return(json.dumps({'success': False, 'error-msg': 'Invalid modification ' + key})) |
| 443 | |
| 444 | if recursion and 'children' in mods[key]['data']: |
| 445 | for child in mods[key]['data']['children']: |
Radek Krejci | f73403b | 2018-02-08 14:57:25 +0100 | [diff] [blame] | 446 | if 'key' in child['info'] and child['info']['key']: |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 447 | continue |
| 448 | _create_child(ctx, node, child) |
| 449 | |
Radek Krejci | 010475d | 2018-03-08 13:14:19 +0100 | [diff] [blame] | 450 | # finally process reorders which must be last since they may refer newly created nodes |
| 451 | # and they do not reflect removed nodes |
| 452 | for move in reorders: |
| 453 | try: |
| 454 | node = root.find_path(move['node']).data()[0]; |
| 455 | parent = node.parent() |
| 456 | node.unlink() |
| 457 | if parent: |
| 458 | parent.insert(node) |
| 459 | else: |
| 460 | root.insert_sibling(node) |
| 461 | except: |
| 462 | if root: |
| 463 | root.new_path(ctx, move['node'], None, 0, 0) |
| 464 | else: |
| 465 | root = yang.Data_Node(ctx, move['node'], None, 0, 0) |
| 466 | node = root.find_path(move['node']).data()[0]; |
| 467 | node.insert_attr(None, 'yang:insert', move['insert']) |
| 468 | if move['insert'] == 'after' or move['insert'] == 'before': |
| 469 | if 'key' in move: |
| 470 | node.insert_attr(None, 'yang:key', move['key']) |
| 471 | elif 'value' in move: |
| 472 | node.insert_attr(None, 'yang:value', move['value']) |
| 473 | |
Radek Krejci | 0f793fe | 2018-02-14 08:33:45 +0100 | [diff] [blame] | 474 | # print(root.print_mem(yang.LYD_XML, yang.LYP_FORMAT)) |
Radek Krejci | f73403b | 2018-02-08 14:57:25 +0100 | [diff] [blame] | 475 | try: |
| 476 | sessions[user.username][req['key']]['session'].rpcEditConfig(nc.DATASTORE_RUNNING, root) |
| 477 | except nc.ReplyError as e: |
| 478 | reply = {'success': False, 'error': []} |
| 479 | for err in e.args[0]: |
| 480 | reply['error'].append(json.loads(str(err))) |
| 481 | return(json.dumps(reply)) |
| 482 | |
Radek Krejci | f041d65 | 2018-02-06 10:21:25 +0100 | [diff] [blame] | 483 | return(json.dumps({'success': True})) |
| 484 | |
| 485 | |
Radek Krejci | 2e57856 | 2017-10-17 11:11:13 +0200 | [diff] [blame] | 486 | @auth.required() |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 487 | def session_close(): |
Jakub Man | 47834ec | 2018-06-21 15:04:35 +0200 | [diff] [blame] | 488 | session = auth.lookup(request.headers.get('lgui-Authorization', None)) |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 489 | user = session['user'] |
| 490 | req = request.args.to_dict() |
| 491 | |
| 492 | if not 'key' in req: |
| 493 | return(json.dumps({'success': False, 'error-msg': 'Missing session key.'})) |
| 494 | |
| 495 | if not user.username in sessions: |
| 496 | sessions[user.username] = {} |
| 497 | |
| 498 | key = req['key'] |
| 499 | if not key in sessions[user.username]: |
| 500 | return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'})) |
| 501 | |
| 502 | del sessions[user.username][key] |
| 503 | return(json.dumps({'success': True})) |
| 504 | |
| 505 | @auth.required() |
| 506 | def session_alive(): |
Jakub Man | 47834ec | 2018-06-21 15:04:35 +0200 | [diff] [blame] | 507 | session = auth.lookup(request.headers.get('lgui-Authorization', None)) |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 508 | user = session['user'] |
| 509 | req = request.args.to_dict() |
| 510 | |
| 511 | if not 'key' in req: |
| 512 | return(json.dumps({'success': False, 'error-msg': 'Missing session key.'})) |
| 513 | |
| 514 | if not user.username in sessions: |
| 515 | sessions[user.username] = {} |
Jakub Man | 47834ec | 2018-06-21 15:04:35 +0200 | [diff] [blame] | 516 | |
Radek Krejci | cbbb197 | 2017-09-21 13:25:19 +0200 | [diff] [blame] | 517 | key = req['key'] |
| 518 | if not key in sessions[user.username]: |
| 519 | return(json.dumps({'success': False, 'error-msg': 'Invalid session key.'})) |
| 520 | |
| 521 | return(json.dumps({'success': True})) |