def post_hba_raw(conn, config, http_context): new_version = False # Push a notification. try: NotificationMgmt.push( config, Notification(username=http_context['username'], message="HBA file updated")) except NotificationError as e: logger.error(e.message) if 'content' not in http_context['post']: raise HTTPError(406, "Parameter 'content' not sent.") if http_context and 'new_version' in http_context['post']: # Check parameter 'version' validate_parameters(http_context['post'], [('new_version', T_NEW_VERSION, False)]) if http_context['post']['new_version'] is True: new_version = True hba_file = get_setting(conn, 'hba_file') return HBAManager.save_file_content(hba_file, http_context['post']['content'], new_version)
def post_pg_control(http_context, app): # Control instance validate_parameters(http_context['post'], [ ('action', T_CONTROL, False) ]) action = http_context['post']['action'] logger.info("PostgreSQL '%s' requested." % action) NotificationMgmt.push(app.config, Notification(username=http_context['username'], message="PostgreSQL %s" % action)) cmd = app.config.administration.pg_ctl % action cmd_args = oneline_cmd_to_array(cmd) (rcode, stdout, stderr) = exec_script(cmd_args) if rcode != 0: raise Exception(str(stderr)) # Let's check if PostgreSQL is up & running after having executed # 'start' or 'restart' action. if action in ['start', 'restart']: # When a start/restart operation is requested, after the # startup/pg_ctl script has been executed then we check that # postgres is up & running: # while the PG conn. is not working then, for 10 seconds (max) # we'll check (connect/SELECT 1/disconnect) the connection, every # 0.5 second. retry = True t_start = time.time() while retry: try: with app.postgres.connect() as conn: conn.execute('SELECT 1') logger.info("Done.") return dict(action=action, state='ok') except error: if (time.time() - t_start) > 10: logger.info("Failed.") return dict(action=action, state='ko') logger.info("Retrying...") time.sleep(0.5) elif action == 'stop': # Check the PG conn is not working anymore. try: retry = True t_start = time.time() while retry: with app.postgres.connect() as conn: conn.execute('SELECT 1') time.sleep(0.5) if (time.time() - t_start) > 10: retry = False logger.info("Failed.") return dict(action=action, state='ko') except error: logger.info("Done.") return dict(action=action, state='ok') elif action == 'reload': logger.info("Done.") return dict(action=action, state='ok')
def post_hba(conn, config, http_context): new_version = False set_logger_name("settings") logger = get_logger(config) # Push a notification. try: NotificationMgmt.push( config, Notification(username=http_context['username'], message="HBA file updated")) except NotificationError as e: logger.error(e.message) if 'entries' not in http_context['post']: raise HTTPError(406, "Parameter 'entries' not sent.") if http_context and 'new_version' in http_context['post']: # Check parameter 'version' validate_parameters(http_context['post'], [('new_version', T_NEW_VERSION, False)]) if http_context['post']['new_version'] is True: new_version = True hba_file = get_setting(conn, 'hba_file') hba_entries = [] logger.debug(http_context['post']['entries']) for entry in http_context['post']['entries']: if 'comment' in entry and len(entry['connection']) == 0: new_hba_entry = HBAComment() new_hba_entry.comment = entry['comment'] else: new_hba_entry = HBAEntry() try: new_hba_entry.connection = entry[ 'connection'] if 'connection' in entry else '' new_hba_entry.database = entry[ 'database'] if 'database' in entry else '' new_hba_entry.user = entry['user'] if 'user' in entry else '' new_hba_entry.address = entry[ 'address'] if 'address' in entry else '' new_hba_entry.auth_method = entry[ 'auth_method'] if 'auth_method' in entry else '' new_hba_entry.auth_options = entry[ 'auth_options'] if 'auth_options' in entry else '' except Exception as e: logger.error(e.message) raise HTTPError(406, "Invalid HBA entry.") new_hba_entry.lazy_check() hba_entries.append(new_hba_entry) return HBAManager.save_entries(hba_file, hba_entries, new_version)
def logout(http_context, app, sessions): headers = http_context['headers'] logger.info("Removing session: %s" % (headers['X-Session'])) try: NotificationMgmt.push(app.config, Notification(username=http_context['username'], message="Logout")) except NotificationError as e: logger.exception(e) try: sessions.delete(headers['X-Session'].encode('utf-8')) return {'logout': True} except (SharedItem_exists, SharedItem_no_free_slot_left) as e: logger.exception(e) raise HTTPError(500, "Internal error.")
def purge_expired(self, ttl, logger, config): """ Remove old Session when the Session last update time + TTL is prior to current timestamp. """ for i in range(0, self.size): if len(self.sessions[i].sessionid) == T_SESSIONID_SIZE and \ (self.sessions[i].time + ttl) < time.time(): logger.info("Session with sessionid=%s expired." % (self.sessions[i].sessionid)) try: NotificationMgmt.push(config, Notification( username = self.sessions[i].username, message = "Session expired")) except NotificationError as e: logger.error(e.message) self.sessions[i] = Session()
def post_activity_kill(conn, config, http_context): validate_parameters(http_context['post'], [('pids', T_PID, True)]) ret = {'backends': []} for pid in http_context['post']['pids']: conn.execute("SELECT pg_terminate_backend(%s) AS killed" % (pid)) # Push a notification. try: NotificationMgmt.push( config, Notification(username=http_context['username'], message="Backend %s terminated" % (pid))) except (NotificationError, Exception) as e: pass ret['backends'].append({ 'pid': pid, 'killed': list(conn.get_rows())[0]['killed'] }) return ret
def logout(http_context, config=None, sessions=None): headers = http_context['headers'] logger.info("Removing session: %s" % (headers['X-Session'])) try: username = check_sessionid(headers, sessions) except HTTPError as e: logger.exception(e.message) logger.info("Invalid session.") raise e try: NotificationMgmt.push( config, Notification(username=username, message="Logout")) except NotificationError as e: logger.exception(e.message) try: sessions.delete(headers['X-Session'].encode('utf-8')) except (SharedItem_exists, SharedItem_no_free_slot_left) as e: logger.exception(e.message) raise HTTPError(500, "Internal error.") return {'logout': True}
def delete_hba_version(conn, config, http_context): version = None if http_context and 'version' in http_context['query']: # Check parameter 'version' validate_parameters(http_context['query'], [('version', T_FILE_VERSION, True)]) version = http_context['query']['version'][0] if version is None: raise HTTPError(406, "HBA version number must be specified.") hba_file = get_setting(conn, 'hba_file') # Push a notification. try: NotificationMgmt.push( config, Notification(username=http_context['username'], message="HBA file version '%s' removed." % (version))) except NotificationError as e: logger.error(e.message) return HBAManager.remove_version(hba_file, version)
def login(http_context, app, sessions): post = http_context['post'] # Add an unconditional sleeping time to reduce brute-force risks time.sleep(1) logger.info("Authenticating user: %s" % (post['username'])) try: validate_parameters(post, [('username', T_USERNAME, False), ('password', T_PASSWORD, False)]) auth_user(app.config.temboard['users'], post['username'], post['password']) except HTTPError as e: logger.info("Authentication failed.") raise e try: session = sessions.get_by_username(post['username']) if not session: sessionid = gen_sessionid(post['username']) session = Session(sessionid.encode('utf-8'), time.time(), post['username'].encode('utf-8')) sessions.add(session) else: sessionid = session.sessionid session.time = time.time() sessions.update(session) try: NotificationMgmt.push(app.config, Notification(username=post['username'], message="Login")) except NotificationError as e: logger.exception(e) return {'session': sessionid} except (SharedItem_exists, SharedItem_no_free_slot_left) as e: logger.exception(e) raise HTTPError(500, "Internal error.")
def post_activity_kill(conn, config, http_context): """ Kill (using pg_terminate_backend()) processes based on a given backend PID list. """ validate_parameters(http_context['post'], [('pids', T_PID, True)]) ret = {'backends': []} for pid in http_context['post']['pids']: killed = conn.query_scalar( "SELECT pg_terminate_backend(%s) AS killed" % (pid)) # Push a notification. try: NotificationMgmt.push( config, Notification(username=http_context['username'], message="Backend %s terminated" % (pid))) except (NotificationError, Exception): pass ret['backends'].append({ 'pid': pid, 'killed': killed, }) return ret
def post_settings(conn, config, http_context): set_logger_name("settings") logger = get_logger(config) if http_context and 'filter' in http_context['query']: # Check 'filter' parameters. validate_parameters(http_context['query'], [('filter', T_PGSETTINGS_FILTER, True)]) pg_config_categories = get_settings(conn, config, None) if 'settings' not in http_context['post']: raise HTTPError(406, "Parameter 'settings' not sent.") settings = http_context['post']['settings'] ret = {'settings': []} do_not_check_names = ['unix_socket_permissions', 'log_file_mode'] logger.debug(settings) for setting in settings: if 'name' not in setting \ or 'setting' not in setting: raise HTTPError(406, "setting item malformed.") checked = False try: for pg_config_category in pg_config_categories: for pg_config_item in pg_config_category['rows']: if pg_config_item['name'] == setting['name']: if pg_config_item['name'] in do_not_check_names: checked = True raise Exception() if pg_config_item['vartype'] == u'integer': # Integers handling. if pg_config_item['min_val'] and pg_config_item['unit'] and \ (int(human_to_number(setting['setting'], pg_config_item['unit'])) < int(pg_config_item['min_val'])): raise HTTPError( 406, "%s: Invalid setting." % (pg_config_item['name'])) if pg_config_item['max_val'] and pg_config_item['unit'] and \ (int(human_to_number(setting['setting'], pg_config_item['unit'])) > int(pg_config_item['max_val'])): raise HTTPError( 406, "%s: Invalid setting." % (pg_config_item['name'])) setting['setting'] = pg_escape(setting['setting']) if ((setting['setting'].startswith("'") and setting['setting'].endswith("'")) or \ (setting['setting'].startswith('"') and setting['setting'].endswith('"'))): setting['setting'] = setting['setting'][1:-1] if setting['setting'] == '': setting['setting'] = None checked = True if pg_config_item['vartype'] == u'real': # Real handling. if pg_config_item['min_val'] and \ (float(setting['setting']) < float(pg_config_item['min_val'])): raise HTTPError( 406, "%s: Invalid setting." % (pg_config_item['name'])) if pg_config_item['max_val'] and \ (float(setting['setting']) > float(pg_config_item['max_val'])): raise HTTPError( 406, "%s: Invalid setting." % (pg_config_item['name'])) setting['setting'] = float(setting['setting']) checked = True if pg_config_item['vartype'] == u'bool': # Boolean handling. if setting['setting'].lower() not in [ u'on', u'off' ]: raise HTTPError( 406, 'Invalid setting: %s.' % (setting['setting'].lower())) checked = True if pg_config_item['vartype'] == u'enum': # Enum handling. if len(pg_config_item['enumvals']) > 0: enumvals = [ re.sub(r"^[\"\'](.+)[\"\ ']$", r"\1", enumval) for enumval in pg_config_item['enumvals'][1:-1].split(',') ] if ((setting['setting'].startswith("'") and setting['setting'].endswith("'")) or \ (setting['setting'].startswith('"') and setting['setting'].endswith('"'))): setting['setting'] = setting['setting'][ 1:-1] if setting['setting'] not in enumvals: raise HTTPError( 406, 'Invalid setting: %s.' % (setting['setting'])) checked = True if pg_config_item['vartype'] == u'string': # String handling. # setting must be escaped. setting['setting'] = pg_escape( str(setting['setting'])) if ((setting['setting'].startswith("'") and setting['setting'].endswith("'")) or \ (setting['setting'].startswith('"') and setting['setting'].endswith('"'))): setting['setting'] = setting['setting'][1:-1] if setting['setting'] == '': setting['setting'] = None checked = True raise Exception() except HTTPError as e: raise HTTPError(e.code, e.message['error']) except Exception as e: pass if not checked: raise HTTPError( 406, 'Parameter %s can\'t be checked.' % (setting['name'])) if 'force' not in setting: setting['force'] = 'false' if ((pg_config_item['vartype'] == u'integer' and setting['setting'] != pg_config_item['setting_raw']) or \ (pg_config_item['vartype'] == u'real' and float(setting['setting']) != float(pg_config_item['setting'])) or \ (pg_config_item['vartype'] not in [ u'integer', u'real' ] and setting['setting'] != pg_config_item['setting'])) or \ (setting['force'] == 'true'): # At this point, all incoming parameters have been checked. if setting['setting']: query = "ALTER SYSTEM SET %s TO '%s'" % (setting['name'], setting['setting']) else: query = "ALTER SYSTEM RESET %s;" % (setting['name']) logger.debug(query) # Push a notification on setting change. try: NotificationMgmt.push( config, Notification( username=http_context['username'], message="Setting '%s' changed: '%s' -> '%s'" % (pg_config_item['name'], pg_config_item['setting_raw'], setting['setting']))) except NotificationError as e: logger.error(e.message) try: conn.execute(query) except error as e: raise HTTPError(408, "%s: %s" % (setting['name'], e.message)) ret['settings'].append({ 'name': pg_config_item['name'], 'setting': setting['setting'], 'previous_setting': pg_config_item['setting_raw'], 'restart': True if pg_config_item['context'] in ['internal', 'postmaster'] else False }) # Reload PG configuration. conn.execute("SELECT pg_reload_conf()") # Push a notification. try: NotificationMgmt.push( config, Notification(username=http_context['username'], message="PostgreSQL reload")) except NotificationError as e: logger.error(e.message) return ret
def post_pg_control(http_context, queue_in = None, config = None, sessions = None, commands = None): # NOTE: in this case we don't want to use api functions wrapper, it leads # to "Broken pipe" error with debian init.d on start/restart. This is # probably due to getattr() call. set_logger_name("administration") # Get a new logger. logger = get_logger(config) check_sessionid(http_context['headers'], sessions) post = http_context['post'] # Check POST parameters. validate_parameters(post, [ ('action', T_CONTROL, False) ]) try: session = sessions.get_by_sessionid(http_context['headers']['X-Session'].encode('utf-8')) NotificationMgmt.push(config, Notification( username = session.username, message = "PostgreSQL %s" % (post['action']))) except NotificationError as e: logger.error(e.message) cmd_args = oneline_cmd_to_array(config.plugins['administration']['pg_ctl'] % (post['action'])) (rcode, stdout, stderr) = exec_script(cmd_args) if rcode != 0: raise HTTPError(408, str(stderr)) # Let's check if postgresql is up & running on 'start' or 'restart' action. if post['action'] in ['start', 'restart']: conn = connector( host = config.postgresql['host'], port = config.postgresql['port'], user = config.postgresql['user'], password = config.postgresql['password'], database = config.postgresql['dbname'] ) # When a start/restart operation is requested, after the startup/pg_ctl # script is executed we check that postgres is up & running: while the # PG connection is not working, during 10 seconds (max) we'll check # (connect/SELECT 1/disconnect) the connection, every 0.5 second. retry = True t_start = time.time() while retry: try: conn.connect() conn.execute('SELECT 1') conn.close() return {'action': post['action'], 'state': 'ok'} except error as e: if (time.time() - t_start) > 10: try: conn.close() except error as e: pass except Exception: pass return {'action': post['action'], 'state': 'ko'} time.sleep(0.5) elif post['action'] == 'stop': conn = connector( host = config.postgresql['host'], port = config.postgresql['port'], user = config.postgresql['user'], password = config.postgresql['password'], database = config.postgresql['dbname'] ) # Check the PG conn is not working anymore. try: retry = True t_start = time.time() while retry: conn.connect() conn.execute('SELECT 1') conn.close() time.sleep(0.5) if (time.time() - t_start) > 10: retry = False return {'action': post['action'], 'state': 'ko'} except error as e: return {'action': post['action'], 'state': 'ok'} return {'action': post['action'], 'state': 'ok'}
def post_pg_control(http_context, config=None, sessions=None): # NOTE: in this case we don't want to use api functions wrapper, it leads # to "Broken pipe" error with debian init.d script on start/restart. # This is probably due to getattr() call. post = http_context['post'] try: check_sessionid(http_context['headers'], sessions) # Check POST parameters. validate_parameters(post, [('action', T_CONTROL, False)]) session = sessions.get_by_sessionid( http_context['headers']['X-Session'].encode('utf-8')) except (Exception, HTTPError) as e: logger.exception(str(e)) logger.debug(http_context) if isinstance(e, HTTPError): raise e else: raise HTTPError(500, "Internal error.") try: NotificationMgmt.push( config, Notification(username=session.username, message="PostgreSQL %s" % post['action'])) except (NotificationError, Exception) as e: logger.exception(str(e)) try: logger.info("PostgreSQL '%s' requested." % (post['action'])) cmd_args = oneline_cmd_to_array( config.plugins['administration']['pg_ctl'] % (post['action'])) (rcode, stdout, stderr) = exec_script(cmd_args) if rcode != 0: raise Exception(str(stderr)) # Let's check if PostgreSQL is up & running after having executed # 'start' or 'restart' action. if post['action'] in ['start', 'restart']: conn = connector(host=config.postgresql['host'], port=config.postgresql['port'], user=config.postgresql['user'], password=config.postgresql['password'], database=config.postgresql['dbname']) # When a start/restart operation is requested, after the # startup/pg_ctl script has been executed then we check that # postgres is up & running: # while the PG conn. is not working then, for 10 seconds (max) # we'll check (connect/SELECT 1/disconnect) the connection, every # 0.5 second. retry = True t_start = time.time() while retry: try: conn.connect() conn.execute('SELECT 1') conn.close() logger.info("Done.") return {'action': post['action'], 'state': 'ok'} except error: if (time.time() - t_start) > 10: try: conn.close() except error: pass except Exception: pass logger.info("Failed.") return {'action': post['action'], 'state': 'ko'} time.sleep(0.5) elif post['action'] == 'stop': conn = connector(host=config.postgresql['host'], port=config.postgresql['port'], user=config.postgresql['user'], password=config.postgresql['password'], database=config.postgresql['dbname']) # Check the PG conn is not working anymore. try: retry = True t_start = time.time() while retry: conn.connect() conn.execute('SELECT 1') conn.close() time.sleep(0.5) if (time.time() - t_start) > 10: retry = False logger.info("Failed.") return {'action': post['action'], 'state': 'ko'} except error: logger.info("Done.") return {'action': post['action'], 'state': 'ok'} logger.info("Done.") return {'action': post['action'], 'state': 'ok'} except (Exception, error, HTTPError) as e: logger.exception(str(e)) logger.info("Failed") if isinstance(e, HTTPError): raise e else: raise HTTPError(500, "Internal error.")
def login(http_context, queue_in=None, config=None, sessions=None, commands=None): """ @api {get} /login User login @apiVersion 0.0.1 @apiName UserLogin @apiGroup User @apiParam {String} username Username. @apiParam {String} password Password. @apiSuccess {String} sessions Session ID. @apiExample {curl} Example usage: curl -k -X POST -H "Content-Type: application/json" -d '{"username": "******", "password": "******"}' \ https://localhost:2345/login @apiSuccessExample Success-Reponse: HTTP/1.0 200 OK Server: temboard-agent/0.0.1 Python/2.7.8 Date: Wed, 22 Apr 2015 12:19:48 GMT Content-type: application/json {"session": "fa452548403ac53f2158a65f5eb6db9723d2b07238dd83f5b6d9ca52ce817b63"} @apiError (500 error) error Internal error. @apiError (404 error) error Invalid username or password. @apiError (406 error) error Username or password malformed or missing. @apiErrorExample 404 error example HTTP/1.0 404 Not Found Server: temboard-agent/0.0.1 Python/2.7.8 Date: Wed, 22 Apr 2015 12:20:33 GMT Content-type: application/json {"error": "Invalid username/password."} @apiErrorExample 406 error example HTTP/1.0 406 Not Acceptable Server: temboard-agent/0.0.1 Python/2.7.8 Date: Wed, 22 Apr 2015 12:21:01 GMT Content-type: application/json {"error": "Parameter 'password' is malformed."} """ post = http_context['post'] set_logger_name("api") logger = get_logger(config) # Add an unconditional sleeping time to reduce brute-force risks time.sleep(1) logger.info("Authenticating user: %s" % (post['username'])) try: validate_parameters(post, [('username', T_USERNAME, False), ('password', T_PASSWORD, False)]) auth_user(config.temboard['users'], post['username'], post['password']) except HTTPError as e: logger.traceback(get_tb()) logger.error(e.message) logger.info("Authentication failed.") raise e try: session = sessions.get_by_username(post['username']) if not session: sessionid = gen_sessionid(post['username']) session = Session(sessionid.encode('utf-8'), time.time(), post['username'].encode('utf-8')) sessions.add(session) else: sessionid = session.sessionid session.time = time.time() sessions.update(session) try: NotificationMgmt.push( config, Notification(username=post['username'], message="Login")) except NotificationError as e: logger.traceback(get_tb()) logger.error(e.message) except (SharedItem_exists, SharedItem_no_free_slot_left) as e: logger.traceback(get_tb()) logger.error(e.message) raise HTTPError(500, "Internal error.") return {'session': sessionid}
def logout(http_context, queue_in=None, config=None, sessions=None, commands=None): """ @api {get} /logout User logout @apiVersion 0.0.1 @apiName UserLogout @apiGroup User @apiHeader {String} X-Session Session ID. @apiSuccess {Bool} logout True if logout succeeds. @apiExample {curl} Example usage: curl -k -H "X-Session: fa452548403ac53f2158a65f5eb6db9723d2b07238dd83f5b6d9ca52ce817b63" https://localhost:2345/logout @apiSuccessExample Success-Reponse: HTTP/1.0 200 OK Server: temboard-agent/0.0.1 Python/2.7.8 Date: Wed, 22 Apr 2015 12:33:19 GMT Content-type: application/json {"logout": true} @apiError (500 error) error Internal error. @apiError (401 error) error Invalid session ID. @apiError (406 error) error Session ID malformed. @apiErrorExample 401 error example HTTP/1.0 401 Unauthorized Server: temboard-agent/0.0.1 Python/2.7.8 Date: Wed, 22 Apr 2015 12:36:33 GMT Content-type: application/json {"error": "Invalid session."} @apiErrorExample 406 error example HTTP/1.0 406 Not Acceptable Server: temboard-agent/0.0.1 Python/2.7.8 Date: Wed, 22 Apr 2015 12:37:23 GMT Content-type: application/json {"error": "Parameter 'X-Session' is malformed."} """ headers = http_context['headers'] set_logger_name("api") logger = get_logger(config) logger.info("Removing session: %s" % (headers['X-Session'])) try: username = check_sessionid(headers, sessions) except HTTPError as e: logger.traceback(get_tb()) logger.error(e.message) logger.info("Invalid session.") raise e try: NotificationMgmt.push( config, Notification(username=username, message="Logout")) except NotificationError as e: logger.traceback(get_tb()) logger.error(e.message) try: sessions.delete(headers['X-Session'].encode('utf-8')) except (SharedItem_exists, SharedItem_no_free_slot_left) as e: logger.traceback(get_tb()) logger.error(e.message) raise HTTPError(500, "Internal error.") return {'logout': True}