[53e7d45] | 1 | #!/usr/bin/env python3 |
---|
[11f7a07] | 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. |
---|
[ed55bec] | 29 | """ |
---|
[08ecf23] | 30 | @author: Ramón M. Gómez, ramongomez at us dot es |
---|
[ed55bec] | 31 | """ |
---|
[53e7d45] | 32 | |
---|
[683d8d4] | 33 | import base64 |
---|
[11f7a07] | 34 | import os |
---|
[5d68449] | 35 | import random |
---|
[bedce23] | 36 | import shutil |
---|
[5d68449] | 37 | import string |
---|
[a850bd1] | 38 | import threading |
---|
[0440c7c] | 39 | import time |
---|
[53e7d45] | 40 | import urllib.error |
---|
| 41 | import urllib.parse |
---|
| 42 | import urllib.request |
---|
[11f7a07] | 43 | |
---|
[68c4c91] | 44 | from configparser import NoOptionError |
---|
[53e7d45] | 45 | from opengnsys import REST, operations, VERSION |
---|
[90e5c2d] | 46 | from opengnsys.log import logger |
---|
[8c6a652] | 47 | from opengnsys.jobmgr import JobMgr |
---|
[53e7d45] | 48 | from opengnsys.workers import ServerWorker |
---|
[90e5c2d] | 49 | |
---|
[03a1cb2] | 50 | |
---|
[ed55bec] | 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: |
---|
[de4289a] | 58 | this, path, get_params, post_params, server = args |
---|
[3a3b642] | 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) |
---|
[ed55bec] | 64 | else: |
---|
| 65 | raise Exception('Unauthorized operation') |
---|
| 66 | except Exception as e: |
---|
[1fdeb2a] | 67 | logger.debug (str(e)) |
---|
[ed55bec] | 68 | raise Exception(e) |
---|
| 69 | |
---|
| 70 | return wrapper |
---|
| 71 | |
---|
| 72 | |
---|
[0440c7c] | 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: |
---|
[1fdeb2a] | 85 | logger.debug (str(e)) |
---|
[0440c7c] | 86 | raise Exception(e) |
---|
| 87 | |
---|
| 88 | return wrapper |
---|
| 89 | |
---|
| 90 | return check_permitted |
---|
| 91 | |
---|
| 92 | |
---|
[11f7a07] | 93 | class OpenGnSysWorker(ServerWorker): |
---|
[4aa86de] | 94 | name = 'opengnsys' # Module name |
---|
[03a1cb2] | 95 | interface = None # Bound interface for OpenGnsys |
---|
[ed55bec] | 96 | REST = None # REST object |
---|
[e298c49] | 97 | user = [] # User sessions |
---|
[be263c6] | 98 | session_type = '' # User session type |
---|
[dab4e35] | 99 | random = None # Random string for secure connections |
---|
| 100 | length = 32 # Random string length |
---|
[0440c7c] | 101 | exec_level = None # Execution level (permitted operations) |
---|
[8c6a652] | 102 | jobmgr = JobMgr() |
---|
[90e5c2d] | 103 | |
---|
[16554e5] | 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 | |
---|
[11f7a07] | 118 | def onActivation(self): |
---|
[bedce23] | 119 | """ |
---|
[44e1e4c] | 120 | Sends OGAgent activation notification to OpenGnsys server |
---|
[bedce23] | 121 | """ |
---|
[a5d0da2] | 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 | |
---|
[68c4c91] | 127 | e = None # Error info |
---|
| 128 | t = 0 # Count of time |
---|
[feb481a] | 129 | # Generate random secret to send on activation |
---|
| 130 | self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(self.length)) |
---|
[11f7a07] | 131 | # Ensure cfg has required configuration variables or an exception will be thrown |
---|
[68c4c91] | 132 | try: |
---|
[16d24f3] | 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') |
---|
[68c4c91] | 137 | except NoOptionError as e: |
---|
| 138 | logger.error("Configuration error: {}".format(e)) |
---|
| 139 | raise e |
---|
[16d24f3] | 140 | self.REST = REST (url, ca_file=ca_file, crt_file=crt_file, key_file=key_file) |
---|
[0440c7c] | 141 | # Execution level ('full' by default) |
---|
| 142 | try: |
---|
[a5d0da2] | 143 | self.exec_level = self.service.config.get(self.name, 'level') |
---|
[68c4c91] | 144 | except NoOptionError: |
---|
[0440c7c] | 145 | self.exec_level = 'full' |
---|
[2e072d2] | 146 | # Get network interfaces until they are active or timeout (5 minutes) |
---|
| 147 | for t in range(0, 300): |
---|
[804c389] | 148 | try: |
---|
[68c4c91] | 149 | # Get the first network interface |
---|
| 150 | self.interface = list(operations.getNetworkInfo())[0] |
---|
[804c389] | 151 | except Exception as e: |
---|
| 152 | # Wait 1 sec. and retry |
---|
[b84ab33] | 153 | logger.warn (e) |
---|
[02399e9] | 154 | time.sleep(1) |
---|
[804c389] | 155 | finally: |
---|
| 156 | # Exit loop if interface is active |
---|
| 157 | if self.interface: |
---|
| 158 | if t > 0: |
---|
| 159 | logger.debug("Fetch connection data after {} tries".format(t)) |
---|
| 160 | break |
---|
| 161 | # Raise error after timeout |
---|
| 162 | if not self.interface: |
---|
[f69d3ab] | 163 | ## UnboundLocalError: cannot access local variable 'e' where it is not associated with a value |
---|
[804c389] | 164 | raise e |
---|
[6a01818] | 165 | |
---|
[feb481a] | 166 | # Loop to send initialization message |
---|
[6a01818] | 167 | init_retries = 100 |
---|
| 168 | for t in range(0, init_retries): |
---|
[feb481a] | 169 | try: |
---|
| 170 | try: |
---|
| 171 | self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip, |
---|
[ed55bec] | 172 | 'secret': self.random, 'ostype': operations.os_type, |
---|
[a850bd1] | 173 | 'osversion': operations.os_version, |
---|
| 174 | 'agent_version': VERSION}) |
---|
[feb481a] | 175 | break |
---|
[6a01818] | 176 | except Exception as e: |
---|
| 177 | logger.warn (str (e)) |
---|
[feb481a] | 178 | # Trying to initialize on alternative server, if defined |
---|
| 179 | # (used in "exam mode" from the University of Seville) |
---|
[16d24f3] | 180 | self.REST = REST(self.service.config.get(self.name, 'altremote'), ca_file=ca_file, crt_file=crt_file, key_file=key_file) |
---|
[feb481a] | 181 | self.REST.sendMessage('ogagent/started', {'mac': self.interface.mac, 'ip': self.interface.ip, |
---|
[ed55bec] | 182 | 'secret': self.random, 'ostype': operations.os_type, |
---|
[a850bd1] | 183 | 'osversion': operations.os_version, 'alt_url': True, |
---|
| 184 | 'agent_version': VERSION}) |
---|
[feb481a] | 185 | break |
---|
[6a01818] | 186 | except Exception as e: |
---|
| 187 | logger.warn (str (e)) |
---|
[feb481a] | 188 | time.sleep(3) |
---|
[ed55bec] | 189 | # Raise error after timeout |
---|
[6a01818] | 190 | if t < init_retries-1: |
---|
[ed55bec] | 191 | logger.debug('Successful connection after {} tries'.format(t)) |
---|
[6a01818] | 192 | elif t == init_retries-1: |
---|
[feb481a] | 193 | raise Exception('Initialization error: Cannot connect to remote server') |
---|
[6a01818] | 194 | |
---|
[6c8f1c2] | 195 | # Delete marking files |
---|
| 196 | for f in ['ogboot.me', 'ogboot.firstboot', 'ogboot.secondboot']: |
---|
| 197 | try: |
---|
| 198 | os.remove(os.sep + f) |
---|
| 199 | except OSError: |
---|
| 200 | pass |
---|
[bedce23] | 201 | # Copy file "HostsFile.FirstOctetOfIPAddress" to "HostsFile", if it exists |
---|
[03a1cb2] | 202 | # (used in "exam mode" from the University of Seville) |
---|
[ed55bec] | 203 | hosts_file = os.path.join(operations.get_etc_path(), 'hosts') |
---|
| 204 | new_hosts_file = hosts_file + '.' + self.interface.ip.split('.')[0] |
---|
| 205 | if os.path.isfile(new_hosts_file): |
---|
| 206 | shutil.copyfile(new_hosts_file, hosts_file) |
---|
[90e5c2d] | 207 | |
---|
[16554e5] | 208 | threading.Thread (name='monitoring_thread', target=self.mon, daemon=True).start() |
---|
| 209 | |
---|
[bf09933] | 210 | logger.debug ('onActivation ok') |
---|
[a67669b] | 211 | |
---|
[11f7a07] | 212 | def onDeactivation(self): |
---|
[bedce23] | 213 | """ |
---|
[44e1e4c] | 214 | Sends OGAgent stopping notification to OpenGnsys server |
---|
[bedce23] | 215 | """ |
---|
[11f7a07] | 216 | logger.debug('onDeactivation') |
---|
[03a1cb2] | 217 | self.REST.sendMessage('ogagent/stopped', {'mac': self.interface.mac, 'ip': self.interface.ip, |
---|
[ed55bec] | 218 | 'ostype': operations.os_type, 'osversion': operations.os_version}) |
---|
[90e5c2d] | 219 | |
---|
[bedce23] | 220 | def onLogin(self, data): |
---|
| 221 | """ |
---|
[44e1e4c] | 222 | Sends session login notification to OpenGnsys server |
---|
[bedce23] | 223 | """ |
---|
[be263c6] | 224 | user, language, self.session_type = tuple(data.split(',')) |
---|
| 225 | logger.debug('Received login for {0} using {2} with language {1}'.format(user, language, self.session_type)) |
---|
[e298c49] | 226 | self.user.append(user) |
---|
[03a1cb2] | 227 | self.REST.sendMessage('ogagent/loggedin', {'ip': self.interface.ip, 'user': user, 'language': language, |
---|
[be263c6] | 228 | 'session': self.session_type, |
---|
[ed55bec] | 229 | 'ostype': operations.os_type, 'osversion': operations.os_version}) |
---|
[5d68449] | 230 | |
---|
[11f7a07] | 231 | def onLogout(self, user): |
---|
[bedce23] | 232 | """ |
---|
[44e1e4c] | 233 | Sends session logout notification to OpenGnsys server |
---|
[bedce23] | 234 | """ |
---|
[11f7a07] | 235 | logger.debug('Received logout for {}'.format(user)) |
---|
[e298c49] | 236 | try: |
---|
| 237 | self.user.pop() |
---|
| 238 | except IndexError: |
---|
| 239 | pass |
---|
[44e1e4c] | 240 | self.REST.sendMessage('ogagent/loggedout', {'ip': self.interface.ip, 'user': user}) |
---|
[11f7a07] | 241 | |
---|
[ed55bec] | 242 | def process_ogclient(self, path, get_params, post_params, server): |
---|
[bedce23] | 243 | """ |
---|
| 244 | This method can be overridden to provide your own message processor, or better you can |
---|
| 245 | implement a method that is called exactly as "process_" + path[0] (module name has been removed from path |
---|
| 246 | array) and this default processMessage will invoke it |
---|
[11f7a07] | 247 | * Example: |
---|
| 248 | Imagine this invocation url (no matter if GET or POST): http://example.com:9999/Sample/mazinger/Z |
---|
| 249 | The HTTP Server will remove "Sample" from path, parse arguments and invoke this method as this: |
---|
[ed55bec] | 250 | module.processMessage(["mazinger","Z"], get_params, post_params) |
---|
[90e5c2d] | 251 | |
---|
[bedce23] | 252 | This method will process "mazinger", and look for a "self" method that is called "process_mazinger", |
---|
| 253 | and invoke it this way: |
---|
[ed55bec] | 254 | return self.process_mazinger(["Z"], get_params, post_params) |
---|
[90e5c2d] | 255 | |
---|
[bedce23] | 256 | In the case path is empty (that is, the path is composed only by the module name, like in |
---|
| 257 | "http://example.com/Sample", the "process" method will be invoked directly |
---|
[90e5c2d] | 258 | |
---|
[bedce23] | 259 | The methods must return data that can be serialized to json (i.e. Objects are not serializable to json, |
---|
| 260 | basic type are) |
---|
| 261 | """ |
---|
[90e5c2d] | 262 | if not path: |
---|
[11f7a07] | 263 | return "ok" |
---|
| 264 | try: |
---|
| 265 | operation = getattr(self, 'ogclient_' + path[0]) |
---|
| 266 | except Exception: |
---|
| 267 | raise Exception('Message processor for "{}" not found'.format(path[0])) |
---|
[ed55bec] | 268 | return operation(path[1:], get_params, post_params) |
---|
[90e5c2d] | 269 | |
---|
[de4289a] | 270 | # Warning: the order of the decorators matters |
---|
[0440c7c] | 271 | @execution_level('status') |
---|
[de4289a] | 272 | @check_secret |
---|
[ed55bec] | 273 | def process_status(self, path, get_params, post_params, server): |
---|
[bedce23] | 274 | """ |
---|
[ed55bec] | 275 | Returns client status (OS type or execution status) and login status |
---|
| 276 | :param path: |
---|
[3a3b642] | 277 | :param get_params: optional parameter "detail" to show extended status |
---|
[ed55bec] | 278 | :param post_params: |
---|
| 279 | :param server: |
---|
[3a3b642] | 280 | :return: JSON object {"status": "status_code", "loggedin": boolean, ...} |
---|
[bedce23] | 281 | """ |
---|
[4aa86de] | 282 | st = {'linux': 'LNX', 'macos': 'OSX', 'windows': 'WIN'} |
---|
| 283 | try: |
---|
[3a3b642] | 284 | # Standard status |
---|
[be263c6] | 285 | res = {'status': st[operations.os_type.lower()], 'loggedin': len(self.user) > 0, |
---|
| 286 | 'session': self.session_type} |
---|
[3a3b642] | 287 | # Detailed status |
---|
| 288 | if get_params.get('detail', 'false') == 'true': |
---|
| 289 | res.update({'agent_version': VERSION, 'os_version': operations.os_version, 'sys_load': os.getloadavg()}) |
---|
| 290 | if res['loggedin']: |
---|
| 291 | res.update({'sessions': len(self.user), 'current_user': self.user[-1]}) |
---|
[4aa86de] | 292 | except KeyError: |
---|
[e298c49] | 293 | # Unknown operating system |
---|
| 294 | res = {'status': 'UNK'} |
---|
[11f7a07] | 295 | return res |
---|
[90e5c2d] | 296 | |
---|
[0440c7c] | 297 | @execution_level('halt') |
---|
[de4289a] | 298 | @check_secret |
---|
[ed55bec] | 299 | def process_reboot(self, path, get_params, post_params, server): |
---|
[bedce23] | 300 | """ |
---|
[ed55bec] | 301 | Launches a system reboot operation |
---|
| 302 | :param path: |
---|
| 303 | :param get_params: |
---|
| 304 | :param post_params: |
---|
| 305 | :param server: authorization header |
---|
| 306 | :return: JSON object {"op": "launched"} |
---|
[bedce23] | 307 | """ |
---|
[11f7a07] | 308 | logger.debug('Received reboot operation') |
---|
[03a1cb2] | 309 | |
---|
[ed55bec] | 310 | # Rebooting thread |
---|
[11f7a07] | 311 | def rebt(): |
---|
| 312 | operations.reboot() |
---|
| 313 | threading.Thread(target=rebt).start() |
---|
| 314 | return {'op': 'launched'} |
---|
| 315 | |
---|
[0440c7c] | 316 | @execution_level('halt') |
---|
[de4289a] | 317 | @check_secret |
---|
[ed55bec] | 318 | def process_poweroff(self, path, get_params, post_params, server): |
---|
[bedce23] | 319 | """ |
---|
[ed55bec] | 320 | Launches a system power off operation |
---|
| 321 | :param path: |
---|
| 322 | :param get_params: |
---|
| 323 | :param post_params: |
---|
| 324 | :param server: authorization header |
---|
| 325 | :return: JSON object {"op": "launched"} |
---|
[bedce23] | 326 | """ |
---|
[11f7a07] | 327 | logger.debug('Received poweroff operation') |
---|
[03a1cb2] | 328 | |
---|
[ed55bec] | 329 | # Powering off thread |
---|
[11f7a07] | 330 | def pwoff(): |
---|
| 331 | time.sleep(2) |
---|
| 332 | operations.poweroff() |
---|
| 333 | threading.Thread(target=pwoff).start() |
---|
| 334 | return {'op': 'launched'} |
---|
| 335 | |
---|
[0440c7c] | 336 | @execution_level('full') |
---|
[de4289a] | 337 | @check_secret |
---|
[ed55bec] | 338 | def process_script(self, path, get_params, post_params, server): |
---|
[bedce23] | 339 | """ |
---|
[937c21f] | 340 | Processes an script execution (script should be encoded in base64) |
---|
[ed55bec] | 341 | :param path: |
---|
| 342 | :param get_params: |
---|
| 343 | :param post_params: JSON object {"script": "commands"} |
---|
| 344 | :param server: authorization header |
---|
| 345 | :return: JSON object {"op": "launched"} |
---|
[bedce23] | 346 | """ |
---|
[937c21f] | 347 | logger.debug('Processing script request') |
---|
[f69d3ab] | 348 | # Decoding script |
---|
[683d8d4] | 349 | script = urllib.parse.unquote(base64.b64decode(post_params.get('script')).decode('utf-8')) |
---|
[8c6a652] | 350 | logger.debug('received script "{}"'.format(script)) |
---|
| 351 | |
---|
[ed55bec] | 352 | if post_params.get('client', 'false') == 'false': |
---|
[8c6a652] | 353 | jobid = self.jobmgr.launch_job (script, False) |
---|
| 354 | return {'op': 'launched', 'jobid': jobid} |
---|
| 355 | |
---|
| 356 | else: ## post_params.get('client') is not 'false' |
---|
| 357 | ## send script as-is |
---|
[08ecf23] | 358 | self.sendClientMessage('script', {'code': script}) |
---|
[8c6a652] | 359 | #return {'op': 'launched', 'jobid': jobid} ## TODO obtain jobid generated at the client (can it be done?) |
---|
| 360 | return {'op': 'launched'} |
---|
| 361 | |
---|
| 362 | @execution_level('full') |
---|
| 363 | @check_secret |
---|
[d4e21da] | 364 | def process_terminatescript(self, path, get_params, post_params, server): |
---|
| 365 | jobid = post_params.get('jobid', None) |
---|
| 366 | logger.debug('Processing terminate_script request, jobid "{}"'.format (jobid)) |
---|
| 367 | if jobid is None: |
---|
| 368 | return {} |
---|
| 369 | self.sendClientMessage('terminatescript', {'jobid': jobid}) |
---|
| 370 | self.jobmgr.terminate_job (jobid) |
---|
| 371 | return {} |
---|
| 372 | |
---|
| 373 | @execution_level('full') |
---|
| 374 | @check_secret |
---|
[8c6a652] | 375 | def process_preparescripts(self, path, get_params, post_params, server): |
---|
| 376 | logger.debug('Processing preparescripts request') |
---|
| 377 | self.st = self.jobmgr.prepare_jobs() |
---|
| 378 | logger.debug('Sending preparescripts to client') |
---|
| 379 | self.sendClientMessage('preparescripts', None) |
---|
| 380 | return {} |
---|
| 381 | |
---|
| 382 | def process_client_preparescripts(self, params): |
---|
| 383 | logger.debug('Processing preparescripts message from client') |
---|
| 384 | for p in params: |
---|
| 385 | #logger.debug ('p "{}"'.format(p)) |
---|
[d4e21da] | 386 | self.st.append (p) |
---|
[8c6a652] | 387 | |
---|
| 388 | @execution_level('full') |
---|
| 389 | @check_secret |
---|
| 390 | def process_getscripts(self, path, get_params, post_params, server): |
---|
| 391 | logger.debug('Processing getscripts request') |
---|
| 392 | return self.st |
---|
[90e5c2d] | 393 | |
---|
[0440c7c] | 394 | @execution_level('full') |
---|
[de4289a] | 395 | @check_secret |
---|
[ed55bec] | 396 | def process_logoff(self, path, get_params, post_params, server): |
---|
[bedce23] | 397 | """ |
---|
[ed55bec] | 398 | Closes user session |
---|
[bedce23] | 399 | """ |
---|
[11f7a07] | 400 | logger.debug('Received logoff operation') |
---|
[ed55bec] | 401 | # Sending log off message to OGAgent client |
---|
[11f7a07] | 402 | self.sendClientMessage('logoff', {}) |
---|
[bedce23] | 403 | return {'op': 'sent to client'} |
---|
[11f7a07] | 404 | |
---|
[0440c7c] | 405 | @execution_level('full') |
---|
[de4289a] | 406 | @check_secret |
---|
[ed55bec] | 407 | def process_popup(self, path, get_params, post_params, server): |
---|
[bedce23] | 408 | """ |
---|
[ed55bec] | 409 | Shows a message popup on the user's session |
---|
[bedce23] | 410 | """ |
---|
[1deb0d1] | 411 | logger.debug('Received message operation') |
---|
[ed55bec] | 412 | # Sending popup message to OGAgent client |
---|
| 413 | self.sendClientMessage('popup', post_params) |
---|
[1deb0d1] | 414 | return {'op': 'launched'} |
---|
| 415 | |
---|
[90e5c2d] | 416 | def process_client_popup(self, params): |
---|
[1deb0d1] | 417 | self.REST.sendMessage('popup_done', params) |
---|