| 1 | #!/usr/bin/env python3 |
|---|
| 2 | # -*- coding: utf-8 -*- |
|---|
| 3 | # |
|---|
| 4 | # Copyright (c) 2014 Virtual Cable S.L. |
|---|
| 5 | # All rights reserved. |
|---|
| 6 | # |
|---|
| 7 | # Redistribution and use in source and binary forms, with or without modification, |
|---|
| 8 | # are permitted provided that the following conditions are met: |
|---|
| 9 | # |
|---|
| 10 | # * Redistributions of source code must retain the above copyright notice, |
|---|
| 11 | # this list of conditions and the following disclaimer. |
|---|
| 12 | # * Redistributions in binary form must reproduce the above copyright notice, |
|---|
| 13 | # this list of conditions and the following disclaimer in the documentation |
|---|
| 14 | # and/or other materials provided with the distribution. |
|---|
| 15 | # * Neither the name of Virtual Cable S.L. nor the names of its contributors |
|---|
| 16 | # may be used to endorse or promote products derived from this software |
|---|
| 17 | # without specific prior written permission. |
|---|
| 18 | # |
|---|
| 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|---|
| 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|---|
| 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|---|
| 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
|---|
| 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|---|
| 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
|---|
| 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|---|
| 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
|---|
| 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|---|
| 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|---|
| 29 | """ |
|---|
| 30 | @author: Ramón M. Gómez, ramongomez at us dot es |
|---|
| 31 | """ |
|---|
| 32 | |
|---|
| 33 | import base64 |
|---|
| 34 | import os |
|---|
| 35 | import random |
|---|
| 36 | import shutil |
|---|
| 37 | import string |
|---|
| 38 | import threading |
|---|
| 39 | import time |
|---|
| 40 | import urllib.error |
|---|
| 41 | import urllib.parse |
|---|
| 42 | import urllib.request |
|---|
| 43 | |
|---|
| 44 | from configparser import NoOptionError |
|---|
| 45 | from opengnsys import REST, operations, VERSION |
|---|
| 46 | from opengnsys.log import logger |
|---|
| 47 | from opengnsys.jobmgr import JobMgr |
|---|
| 48 | from opengnsys.workers import ServerWorker |
|---|
| 49 | |
|---|
| 50 | |
|---|
| 51 | # Check authorization header decorator |
|---|
| 52 | def check_secret(fnc): |
|---|
| 53 | """ |
|---|
| 54 | Decorator to check for received secret key and raise exception if it isn't valid. |
|---|
| 55 | """ |
|---|
| 56 | def wrapper(*args, **kwargs): |
|---|
| 57 | try: |
|---|
| 58 | this, path, get_params, post_params, server = args |
|---|
| 59 | # Accept "status" operation with no arguments or any function with Authorization header |
|---|
| 60 | if fnc.__name__ == 'process_status' and not get_params: |
|---|
| 61 | return fnc(*args, **kwargs) |
|---|
| 62 | elif this.random == server.headers['Authorization']: |
|---|
| 63 | return fnc(*args, **kwargs) |
|---|
| 64 | else: |
|---|
| 65 | raise Exception('Unauthorized operation') |
|---|
| 66 | except Exception as e: |
|---|
| 67 | logger.debug (str(e)) |
|---|
| 68 | raise Exception(e) |
|---|
| 69 | |
|---|
| 70 | return wrapper |
|---|
| 71 | |
|---|
| 72 | |
|---|
| 73 | # Check if operation is permitted |
|---|
| 74 | def execution_level(level): |
|---|
| 75 | def check_permitted(fnc): |
|---|
| 76 | def wrapper(*args, **kwargs): |
|---|
| 77 | levels = ['status', 'halt', 'full'] |
|---|
| 78 | this = args[0] |
|---|
| 79 | try: |
|---|
| 80 | if levels.index(level) <= levels.index(this.exec_level): |
|---|
| 81 | return fnc(*args, **kwargs) |
|---|
| 82 | else: |
|---|
| 83 | raise Exception('Unauthorized operation') |
|---|
| 84 | except Exception as e: |
|---|
| 85 | logger.debug (str(e)) |
|---|
| 86 | raise Exception(e) |
|---|
| 87 | |
|---|
| 88 | return wrapper |
|---|
| 89 | |
|---|
| 90 | return check_permitted |
|---|
| 91 | |
|---|
| 92 | |
|---|
| 93 | class OpenGnSysWorker(ServerWorker): |
|---|
| 94 | name = 'opengnsys' # Module name |
|---|
| 95 | interface = None # Bound interface for OpenGnsys |
|---|
| 96 | REST = None # REST object |
|---|
| 97 | user = [] # User sessions |
|---|
| 98 | session_type = '' # User session type |
|---|
| 99 | random = None # Random string for secure connections |
|---|
| 100 | length = 32 # Random string length |
|---|
| 101 | exec_level = None # Execution level (permitted operations) |
|---|
| 102 | jobmgr = JobMgr() |
|---|
| 103 | |
|---|
| 104 | ## pings ogcore |
|---|
| 105 | def mon (self): |
|---|
| 106 | n = 0 |
|---|
| 107 | while True: |
|---|
| 108 | time.sleep (1) |
|---|
| 109 | n += 1 |
|---|
| 110 | if not n % 10: |
|---|
| 111 | body = { |
|---|
| 112 | "iph": self.interface.ip, |
|---|
| 113 | "timestamp": int (time.time()), |
|---|
| 114 | } |
|---|
| 115 | #logger.debug (f'about to send ping ({body})') |
|---|
| 116 | self.REST.sendMessage ('clients/status/webhook', body) |
|---|
| 117 | |
|---|
| 118 | def onActivation(self): |
|---|
| 119 | """ |
|---|
| 120 | Sends OGAgent activation notification to OpenGnsys server |
|---|
| 121 | """ |
|---|
| 122 | if os.path.exists ('/scripts/oginit'): |
|---|
| 123 | ## estamos en oglive, este modulo no debe cargarse |
|---|
| 124 | ## esta lógica la saco de src/opengnsys/linux/operations.py, donde hay un if similar |
|---|
| 125 | raise Exception ('Refusing to load within an ogLive image') |
|---|
| 126 | |
|---|
| 127 | e = None # Error info |
|---|
| 128 | t = 0 # Count of time |
|---|
| 129 | # Generate random secret to send on activation |
|---|
| 130 | self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length)) |
|---|
| 131 | # Ensure cfg has required configuration variables or an exception will be thrown |
|---|
| 132 | try: |
|---|
| 133 | url = self.service.config.get(self.name, 'remote') |
|---|
| 134 | ca_file = self.service.config.get(self.name, 'ca') |
|---|
| 135 | crt_file = self.service.config.get(self.name, 'crt') |
|---|
| 136 | key_file = self.service.config.get(self.name, 'key') |
|---|
| 137 | except NoOptionError as e: |
|---|
| 138 | logger.error("Configuration error: {}".format(e)) |
|---|
| 139 | raise e |
|---|
| 140 | self.REST = REST (url, ca_file=ca_file, crt_file=crt_file, key_file=key_file) |
|---|
| 141 | # Execution level ('full' by default) |
|---|
| 142 | try: |
|---|
| 143 | self.exec_level = self.service.config.get(self.name, 'level') |
|---|
| 144 | except NoOptionError: |
|---|
| 145 | self.exec_level = 'full' |
|---|
| 146 | # Get network interfaces until they are active or timeout (5 minutes) |
|---|
| 147 | for t in range(0, 300): |
|---|
| 148 | try: |
|---|
| 149 | # Get the first network interface |
|---|
| 150 | nets = list (operations.getNetworkInfo()) |
|---|
| 151 | if 0 == len (nets): |
|---|
| 152 | logger.error ('No network interfaces found') |
|---|
| 153 | raise Exception ('No network interfaces found') |
|---|
| 154 | self.interface = nets[0] |
|---|
| 155 | except Exception as e: |
|---|
| 156 | # Wait 1 sec. and retry |
|---|
| 157 | logger.warn (e) |
|---|
| 158 | time.sleep(1) |
|---|
| 159 | finally: |
|---|
| 160 | # Exit loop if interface is active |
|---|
| 161 | if self.interface: |
|---|
| 162 | if t > 0: |
|---|
| 163 | logger.debug("Fetch connection data after {} tries".format(t)) |
|---|
| 164 | break |
|---|
| 165 | # Raise error after timeout |
|---|
| 166 | if not self.interface: |
|---|
| 167 | ## UnboundLocalError: cannot access local variable 'e' where it is not associated with a value |
|---|
| 168 | raise e |
|---|
| 169 | |
|---|
| 170 | # Loop to send initialization message |
|---|
| 171 | init_retries = 100 |
|---|
| 172 | for t in range(0, init_retries): |
|---|
| 173 | try: |
|---|
| 174 | try: |
|---|
| 175 | self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip, |
|---|
| 176 | 'secret': self.random, 'ostype': operations.os_type, |
|---|
| 177 | 'osversion': operations.os_version, |
|---|
| 178 | 'agent_version': VERSION}) |
|---|
| 179 | break |
|---|
| 180 | except Exception as e: |
|---|
| 181 | logger.warn (str (e)) |
|---|
| 182 | # Trying to initialize on alternative server, if defined |
|---|
| 183 | # (used in "exam mode" from the University of Seville) |
|---|
| 184 | self.REST = REST(self.service.config.get(self.name, 'altremote'), ca_file=ca_file, crt_file=crt_file, key_file=key_file) |
|---|
| 185 | self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip, |
|---|
| 186 | 'secret': self.random, 'ostype': operations.os_type, |
|---|
| 187 | 'osversion': operations.os_version, 'alt_url': True, |
|---|
| 188 | 'agent_version': VERSION}) |
|---|
| 189 | break |
|---|
| 190 | except Exception as e: |
|---|
| 191 | logger.warn (str (e)) |
|---|
| 192 | time.sleep(3) |
|---|
| 193 | # Raise error after timeout |
|---|
| 194 | if t < init_retries-1: |
|---|
| 195 | logger.debug('Successful connection after {} tries'.format(t)) |
|---|
| 196 | elif t == init_retries-1: |
|---|
| 197 | raise Exception('Initialization error: Cannot connect to remote server') |
|---|
| 198 | |
|---|
| 199 | # Delete marking files |
|---|
| 200 | for f in ['ogboot.me', 'ogboot.firstboot', 'ogboot.secondboot']: |
|---|
| 201 | try: |
|---|
| 202 | os.remove(os.sep + f) |
|---|
| 203 | except OSError: |
|---|
| 204 | pass |
|---|
| 205 | # Copy file "HostsFile.FirstOctetOfIPAddress" to "HostsFile", if it exists |
|---|
| 206 | # (used in "exam mode" from the University of Seville) |
|---|
| 207 | hosts_file = os.path.join(operations.get_etc_path(), 'hosts') |
|---|
| 208 | new_hosts_file = hosts_file + '.' + self.interface.ip.split('.')[0] |
|---|
| 209 | if os.path.isfile(new_hosts_file): |
|---|
| 210 | shutil.copyfile(new_hosts_file, hosts_file) |
|---|
| 211 | |
|---|
| 212 | threading.Thread (name='monitoring_thread', target=self.mon, daemon=True).start() |
|---|
| 213 | |
|---|
| 214 | logger.debug ('onActivation ok') |
|---|
| 215 | |
|---|
| 216 | def onDeactivation(self): |
|---|
| 217 | """ |
|---|
| 218 | Sends OGAgent stopping notification to OpenGnsys server |
|---|
| 219 | """ |
|---|
| 220 | now = time.time() |
|---|
| 221 | for elem in self.user: |
|---|
| 222 | sess_len = now - elem['login_ts'] |
|---|
| 223 | logger.debug ('Session of logged in user {} took {} seconds'.format (elem['username'], int (sess_len))) |
|---|
| 224 | logger.debug('onDeactivation') |
|---|
| 225 | self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip, |
|---|
| 226 | 'ostype': operations.os_type, 'osversion': operations.os_version}) |
|---|
| 227 | |
|---|
| 228 | def onLogin(self, data): |
|---|
| 229 | """ |
|---|
| 230 | Sends session login notification to OpenGnsys server |
|---|
| 231 | """ |
|---|
| 232 | user, language, self.session_type = tuple(data.split(',')) |
|---|
| 233 | logger.debug('Received login for {0} using {2} with language {1}'.format(user, language, self.session_type)) |
|---|
| 234 | self.user.append ({'username': user, 'login_ts': time.time() }) |
|---|
| 235 | self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language, |
|---|
| 236 | 'session': self.session_type, |
|---|
| 237 | 'ostype': operations.os_type, 'osversion': operations.os_version}) |
|---|
| 238 | |
|---|
| 239 | def onLogout(self, user): |
|---|
| 240 | """ |
|---|
| 241 | Sends session logout notification to OpenGnsys server |
|---|
| 242 | """ |
|---|
| 243 | sess_len = 0 |
|---|
| 244 | for elem in self.user: |
|---|
| 245 | if user != elem['username']: continue |
|---|
| 246 | sess_len = time.time() - elem['login_ts'] |
|---|
| 247 | logger.debug ('Received logout for {}, session length {} seconds'.format (user, int (sess_len))) |
|---|
| 248 | try: |
|---|
| 249 | self.user.pop() |
|---|
| 250 | except IndexError: |
|---|
| 251 | pass |
|---|
| 252 | self.REST.sendMessage('ogagent/loggedout', {'ip': self.interface.ip, 'user': user}) |
|---|
| 253 | |
|---|
| 254 | def process_ogclient(self, path, get_params, post_params, server): |
|---|
| 255 | """ |
|---|
| 256 | This method can be overridden to provide your own message processor, or better you can |
|---|
| 257 | implement a method that is called exactly as "process_" + path[0] (module name has been removed from path |
|---|
| 258 | array) and this default processMessage will invoke it |
|---|
| 259 | * Example: |
|---|
| 260 | Imagine this invocation url (no matter if GET or POST): http://example.com:9999/Sample/mazinger/Z |
|---|
| 261 | The HTTP Server will remove "Sample" from path, parse arguments and invoke this method as this: |
|---|
| 262 | module.processMessage(["mazinger","Z"], get_params, post_params) |
|---|
| 263 | |
|---|
| 264 | This method will process "mazinger", and look for a "self" method that is called "process_mazinger", |
|---|
| 265 | and invoke it this way: |
|---|
| 266 | return self.process_mazinger(["Z"], get_params, post_params) |
|---|
| 267 | |
|---|
| 268 | In the case path is empty (that is, the path is composed only by the module name, like in |
|---|
| 269 | "http://example.com/Sample", the "process" method will be invoked directly |
|---|
| 270 | |
|---|
| 271 | The methods must return data that can be serialized to json (i.e. Objects are not serializable to json, |
|---|
| 272 | basic type are) |
|---|
| 273 | """ |
|---|
| 274 | if not path: |
|---|
| 275 | return "ok" |
|---|
| 276 | try: |
|---|
| 277 | operation = getattr(self, 'ogclient_' + path[0]) |
|---|
| 278 | except Exception: |
|---|
| 279 | raise Exception('Message processor for "{}" not found'.format(path[0])) |
|---|
| 280 | return operation(path[1:], get_params, post_params) |
|---|
| 281 | |
|---|
| 282 | # Warning: the order of the decorators matters |
|---|
| 283 | @execution_level('status') |
|---|
| 284 | @check_secret |
|---|
| 285 | def process_status(self, path, get_params, post_params, server): |
|---|
| 286 | """ |
|---|
| 287 | Returns client status (OS type or execution status) and login status |
|---|
| 288 | :param path: |
|---|
| 289 | :param get_params: optional parameter "detail" to show extended status |
|---|
| 290 | :param post_params: |
|---|
| 291 | :param server: |
|---|
| 292 | :return: JSON object {"status": "status_code", "loggedin": boolean, ...} |
|---|
| 293 | """ |
|---|
| 294 | st = {'linux': 'LNX', 'macos': 'OSX', 'windows': 'WIN'} |
|---|
| 295 | try: |
|---|
| 296 | # Standard status |
|---|
| 297 | res = {'status': st[operations.os_type.lower()], 'loggedin': len(self.user) > 0, |
|---|
| 298 | 'session': self.session_type} |
|---|
| 299 | # Detailed status |
|---|
| 300 | if get_params.get('detail', 'false') == 'true': |
|---|
| 301 | res.update({'agent_version': VERSION, 'os_version': operations.os_version, 'sys_load': os.getloadavg()}) |
|---|
| 302 | if res['loggedin']: |
|---|
| 303 | res.update({'sessions': len(self.user), 'current_user': self.user[-1]['username']}) |
|---|
| 304 | except KeyError: |
|---|
| 305 | # Unknown operating system |
|---|
| 306 | res = {'status': 'UNK'} |
|---|
| 307 | return res |
|---|
| 308 | |
|---|
| 309 | @execution_level('halt') |
|---|
| 310 | @check_secret |
|---|
| 311 | def process_Reiniciar(self, path, get_params, post_params, server): |
|---|
| 312 | """ |
|---|
| 313 | Launches a system reboot operation |
|---|
| 314 | :param path: |
|---|
| 315 | :param get_params: |
|---|
| 316 | :param post_params: |
|---|
| 317 | :param server: authorization header |
|---|
| 318 | :return: JSON object {"op": "launched"} |
|---|
| 319 | """ |
|---|
| 320 | logger.debug('Received reboot operation') |
|---|
| 321 | |
|---|
| 322 | # Rebooting thread |
|---|
| 323 | def rebt(): |
|---|
| 324 | operations.reboot() |
|---|
| 325 | threading.Thread(target=rebt).start() |
|---|
| 326 | return {'op': 'launched'} |
|---|
| 327 | |
|---|
| 328 | @execution_level('halt') |
|---|
| 329 | @check_secret |
|---|
| 330 | def process_Apagar(self, path, get_params, post_params, server): |
|---|
| 331 | """ |
|---|
| 332 | Launches a system power off operation |
|---|
| 333 | :param path: |
|---|
| 334 | :param get_params: |
|---|
| 335 | :param post_params: |
|---|
| 336 | :param server: authorization header |
|---|
| 337 | :return: JSON object {"op": "launched"} |
|---|
| 338 | """ |
|---|
| 339 | logger.debug('Received poweroff operation') |
|---|
| 340 | |
|---|
| 341 | # Powering off thread |
|---|
| 342 | def pwoff(): |
|---|
| 343 | time.sleep(2) |
|---|
| 344 | operations.poweroff() |
|---|
| 345 | threading.Thread(target=pwoff).start() |
|---|
| 346 | return {'op': 'launched'} |
|---|
| 347 | |
|---|
| 348 | @execution_level('full') |
|---|
| 349 | @check_secret |
|---|
| 350 | def process_EjecutarScript(self, path, get_params, post_params, server): |
|---|
| 351 | """ |
|---|
| 352 | Processes an script execution (script should be encoded in base64) |
|---|
| 353 | :param path: |
|---|
| 354 | :param get_params: |
|---|
| 355 | :param post_params: JSON object {"script": "commands"} |
|---|
| 356 | :param server: authorization header |
|---|
| 357 | :return: JSON object {"op": "launched"} |
|---|
| 358 | """ |
|---|
| 359 | logger.debug('Processing script request') |
|---|
| 360 | # Decoding script |
|---|
| 361 | param_script = post_params.get('script') |
|---|
| 362 | if not param_script: |
|---|
| 363 | return {'op': 'error', 'err': 'Required parameter "script" is missing or empty'} |
|---|
| 364 | try: |
|---|
| 365 | b64decoded = base64.b64decode (param_script) |
|---|
| 366 | except Exception as e: |
|---|
| 367 | return {'op': 'error', 'err': f'Failed to decode base64: {e}'} |
|---|
| 368 | script = urllib.parse.unquote (b64decoded.decode ('utf-8')) |
|---|
| 369 | logger.debug('received script "{}"'.format(script)) |
|---|
| 370 | |
|---|
| 371 | if post_params.get('client', 'false') == 'false': |
|---|
| 372 | job_id = self.jobmgr.launch_job (script, False) |
|---|
| 373 | return {'op': 'launched', 'job_id': job_id} |
|---|
| 374 | |
|---|
| 375 | else: ## post_params.get('client') is not 'false' |
|---|
| 376 | ## send script as-is |
|---|
| 377 | self.sendClientMessage('script', {'code': script}) |
|---|
| 378 | |
|---|
| 379 | ## wait for job_id generated at the client |
|---|
| 380 | job_id = None |
|---|
| 381 | iters = 0 |
|---|
| 382 | while True: |
|---|
| 383 | time.sleep (0.2) |
|---|
| 384 | if os.path.exists ('/tmp/EjecutarScript-jobid'): |
|---|
| 385 | with open ('/tmp/EjecutarScript-jobid', 'r') as fd: |
|---|
| 386 | job_id = fd.read() |
|---|
| 387 | break |
|---|
| 388 | iters += 1 |
|---|
| 389 | if iters >= 10: break |
|---|
| 390 | |
|---|
| 391 | try: os.unlink ('/tmp/EjecutarScript-jobid') |
|---|
| 392 | except: pass |
|---|
| 393 | |
|---|
| 394 | if job_id is None: return {'op': 'launched'} |
|---|
| 395 | else: return {'op': 'launched', 'job_id': job_id} |
|---|
| 396 | |
|---|
| 397 | def process_client_script_launched(self, data): |
|---|
| 398 | fd = open ('/tmp/EjecutarScript-jobid', 'w') |
|---|
| 399 | fd.write (data['job_id']) |
|---|
| 400 | fd.close() |
|---|
| 401 | return True |
|---|
| 402 | |
|---|
| 403 | @execution_level('full') |
|---|
| 404 | @check_secret |
|---|
| 405 | def process_terminatescript(self, path, get_params, post_params, server): |
|---|
| 406 | job_id = post_params.get('job_id', None) |
|---|
| 407 | logger.debug('Processing terminate_script request, job_id "{}"'.format (job_id)) |
|---|
| 408 | if job_id is None: |
|---|
| 409 | return {} |
|---|
| 410 | self.sendClientMessage('terminatescript', {'job_id': job_id}) |
|---|
| 411 | self.jobmgr.terminate_job (job_id) |
|---|
| 412 | return {} |
|---|
| 413 | |
|---|
| 414 | @execution_level('full') |
|---|
| 415 | @check_secret |
|---|
| 416 | def process_preparescripts(self, path, get_params, post_params, server): |
|---|
| 417 | logger.debug('Processing preparescripts request') |
|---|
| 418 | self.st = self.jobmgr.prepare_jobs() |
|---|
| 419 | logger.debug('Sending preparescripts to client') |
|---|
| 420 | self.sendClientMessage('preparescripts', None) |
|---|
| 421 | return {} |
|---|
| 422 | |
|---|
| 423 | def process_client_preparescripts(self, params): |
|---|
| 424 | logger.debug('Processing preparescripts message from client') |
|---|
| 425 | for p in params: |
|---|
| 426 | #logger.debug ('p "{}"'.format(p)) |
|---|
| 427 | self.st.append (p) |
|---|
| 428 | |
|---|
| 429 | @execution_level('full') |
|---|
| 430 | @check_secret |
|---|
| 431 | def process_getscripts(self, path, get_params, post_params, server): |
|---|
| 432 | logger.debug('Processing getscripts request') |
|---|
| 433 | return self.st |
|---|
| 434 | |
|---|
| 435 | @execution_level('full') |
|---|
| 436 | @check_secret |
|---|
| 437 | def process_logoff(self, path, get_params, post_params, server): |
|---|
| 438 | """ |
|---|
| 439 | Closes user session |
|---|
| 440 | """ |
|---|
| 441 | logger.debug('Received logoff operation') |
|---|
| 442 | # Sending log off message to OGAgent client |
|---|
| 443 | self.sendClientMessage('logoff', {}) |
|---|
| 444 | return {'op': 'sent to client'} |
|---|
| 445 | |
|---|
| 446 | @execution_level('full') |
|---|
| 447 | @check_secret |
|---|
| 448 | def process_popup(self, path, get_params, post_params, server): |
|---|
| 449 | """ |
|---|
| 450 | Shows a message popup on the user's session |
|---|
| 451 | """ |
|---|
| 452 | logger.debug('Received message operation') |
|---|
| 453 | # Sending popup message to OGAgent client |
|---|
| 454 | self.sendClientMessage('popup', post_params) |
|---|
| 455 | return {'op': 'launched'} |
|---|
| 456 | |
|---|
| 457 | def process_client_popup(self, params): |
|---|
| 458 | self.REST.sendMessage('popup_done', params) |
|---|