def resync(environ, start_response): """ Handle Re-sync requests """ try: # Validate caller address if environ['REMOTE_ADDR'] not in settings['SYNC_POOL']: logger.error('Operation not permitted from IP %(REMOTE_ADDR)s', environ) raise YKSyncError( 'OPERATION_NOT_ALLOWED', 'Remote IP %(REMOTE_ADDR)s it not in sync pool' % environ) # Parse query and check values resync_params = parse_querystring(environ['QUERY_STRING']) synclib = Sync() output = synclib.resync_local(resync_params) status_code = 200 logger.info('Re-sync request by %s for keys: %s', environ['REMOTE_ADDR'], resync_params['yk']) except YKSyncError as err: output = str(err) status_code = 401 except Exception as err: logger.exception('ERROR: %s', err) output = '' status_code = 500 finally: start_response(HTTP_STATUS_CODES[status_code], [('Content-Type', 'text/plain')]) return [output.encode()]
def sync(environ, start_response): """ Handle Sync requests """ local_params = None try: # Validate caller address logger.debug('Received request from %(REMOTE_ADDR)s', environ) if environ['REMOTE_ADDR'] not in settings['SYNC_POOL']: logger.info('Operation not permitted from IP %(REMOTE_ADDR)s', environ) logger.debug('Remote IP %s is not in allowed sync pool: %s', environ['REMOTE_ADDR'], settings['SYNC_POOL']) raise YKSyncError('ERROR Authorization failed for %(REMOTE_ADDR)s)' % environ) sync_params = parse_querystring(environ['QUERY_STRING']) logger.info('Received: %s', sync_params) synclib = Sync() local_params = synclib.sync_local(sync_params) output = 'OK' except YKSyncError as err: output = str(err) except Exception as err: logger.exception('ERROR: %s', err) output = 'BACKEND_ERROR' finally: return wsgi_response(output, start_response, apikey=''.encode(), extra=local_params)
def verify(environ, start_response): """ Handle OTP Validation """ apikey = ''.encode() try: params = parse_querystring(environ['QUERY_STRING']) public_id = params.get('otp', '?' * 12)[:12] logger.debug('%s: PROCESSED QUERYSTRING: %s', public_id, params) validator = Validator() apikey = validator.get_client_apikey(params.get('id')) client_signature = params.pop('h') server_signature = sign(params, apikey) if client_signature != server_signature: logger.error('[%s] Client hmac=%s != Server hmac=%s', public_id, client_signature, server_signature) raise YKValError('BAD_SIGNATURE') for old_key, new_key in PARAM_MAP.items(): if old_key in params: params[new_key] = params[old_key] params.pop(old_key) extra = validator.verify(**params) output = 'OK' logger.info('[%s] OTP Verified', public_id) except YKValError as err: output = '%s' % err except Exception as err: logger.exception('%s: Backend error: %s', public_id, err) output = 'BACKEND_ERROR' finally: return wsgi_response(output, start_response, apikey=apikey, extra=None)
def resync(environ, start_response): """ Handle Re-sync requests """ try: # Validate caller address if environ['REMOTE_ADDR'] not in settings['SYNC_POOL']: logger.error('Operation not permitted from IP %(REMOTE_ADDR)s', environ) raise YKSyncError('OPERATION_NOT_ALLOWED', 'Remote IP %(REMOTE_ADDR)s it not in sync pool' % environ) # Parse query and check values resync_params = parse_querystring(environ['QUERY_STRING']) synclib = Sync() output = synclib.resync_local(resync_params) status_code = 200 logger.info('Re-sync request by %s for keys: %s', environ['REMOTE_ADDR'], resync_params['yk']) except YKSyncError as err: output = str(err) status_code = 401 except Exception as err: logger.exception('ERROR: %s', err) output = '' status_code = 500 finally: start_response(HTTP_STATUS_CODES[status_code], [('Content-Type', 'text/plain')]) return [output.encode()]
def decrypt(environ, start_response): """ Handle OTP decryptions """ _format = 'text' try: params = parse_querystring(environ['QUERY_STRING']) if params.get('format') == 'json' or environ[ 'HTTP_ACCEPT'] == 'application/json': _format = 'json' logger.debug('PROCESSED QUERYSTRING: %s', params) decryptor = Decryptor() output = decryptor.decrypt(params.get('otp')) if _format != 'json': output = 'OK counter=%(counter)s low=%(low)s high=%(high)s use=%(use)s\n' % output status_code = 200 except YKKSMError as err: logger.exception('Decryption error: %s', err.error_code) output = '%s' % str(err) status_code = 400 except Exception as err: logger.exception('Backend error: %s', err) output = 'Backend failure\n' status_code = 500 finally: content_type = 'application/json' if _format == 'json' else 'text/plain' if _format == 'json': if isinstance(output, str): output = {'error': output} output = json.dumps(output) elif status_code != 200: output = 'ERR %s\n' % output start_response(HTTP_STATUS_CODES[status_code], [('Content-Type', content_type)]) return [output.encode()]
def sync(environ, start_response): """ Handle Sync requests """ local_params = None try: # Validate caller address if environ['REMOTE_ADDR'] not in settings['SYNC_POOL']: logger.error('Operation not permitted from IP %(REMOTE_ADDR)s', environ) raise YKSyncError('OPERATION_NOT_ALLOWED', 'Remote IP %(REMOTE_ADDR)s it not in sync pool' % environ) sync_params = parse_querystring(environ['QUERY_STRING']) logger.info('[%s] Received sync request from %s (counter: %s, use: %s, nonce: %s)', sync_params.get('yk_publicname'), environ['REMOTE_ADDR'], sync_params.get('yk_counter'), sync_params.get('yk_use'), sync_params.get('nonce')) synclib = Sync() local_params = synclib.sync_local(sync_params) output = 'OK' status_code = 200 except YKSyncError as err: output = str(err) status_code = 401 except Exception as err: logger.exception('ERROR: %s', err) output = 'BACKEND_ERROR' status_code = 500 finally: return wsgi_response(output, start_response, apikey=''.encode(), extra=local_params, status=status_code)
def decrypt(environ, start_response): """ Handle OTP decryptions """ _format = 'text' try: params = parse_querystring(environ['QUERY_STRING']) if params.get('format') == 'json' or environ['HTTP_ACCEPT'] == 'application/json': _format = 'json' logger.debug('PROCESSED QUERYSTRING: %s', params) decryptor = Decryptor() output = decryptor.decrypt(params.get('otp')) if _format != 'json': output = 'OK counter=%(counter)s low=%(low)s high=%(high)s use=%(use)s\n' % output status_code = 200 except YKKSMError as err: logger.exception('Decryption error: %s', err.error_code) output = '%s' % str(err) status_code = 400 except Exception as err: logger.exception('Backend error: %s', err) output = 'Backend failure\n' status_code = 500 finally: content_type = 'application/json' if _format == 'json' else 'text/plain' if _format == 'json': if isinstance(output, str): output = {'error': output} output = json.dumps(output) elif status_code != 200: output = 'ERR %s\n' % output start_response(HTTP_STATUS_CODES[status_code], [('Content-Type', content_type)]) return [output.encode()]
def decrypt(environ, start_response): """ Handle OTP decryptions """ try: params = parse_querystring(environ['QUERY_STRING']) logger.debug('PROCESSED QUERYSTRING: %s', params) decryptor = Decryptor() output = decryptor.decrypt(params.get('otp')) output = 'OK counter=%(counter)s low=%(low)s high=%(high)s use=%(use)s\n' % output except YKKSMError as err: logger.exception('Decryption error: %s', err) output = '%s\n' % err except Exception as err: logger.exception('Backend error: %s', err) output = 'ERR Backend failure\n' finally: start_response('200 OK', [('Content-Type', 'text/plain')]) return [output.encode()]
def resync(environ, start_response): """ Handle Re-sync requests """ try: # Validate caller address if environ['REMOTE_ADDR'] not in settings['SYNC_POOL']: logger.info('Operation not permitted from IP %(REMOTE_ADDR)s', environ) raise YKSyncError('ERROR Authorization failed for %(REMOTE_ADDR)s)' % environ) # Parse query and check values resync_params = parse_querystring(environ['QUERY_STRING']) synclib = Sync() output = synclib.resync_local(resync_params) except YKSyncError as err: output = str(err) except Exception as err: logger.exception('ERROR: %s', err) output = '' finally: start_response('200 OK', [('Content-Type', 'text/plain')]) return [output.encode()]
def authenticate(environ, start_response): """ Handle authentications """ REQUIRED_PARAMS = ['username', 'password', 'otp'] try: # Parse POST request try: request_body_size = int(environ.get('CONTENT_LENGTH', 0)) except ValueError: request_body_size = 0 request_body = environ['wsgi.input'].read(request_body_size) params = parse_querystring(request_body.decode()) _params = params.copy() _params['password'] = '******' logger.debug('PROCESSED QUERYSTRING: %s', _params) for req_param in REQUIRED_PARAMS: if req_param not in params: raise YKAuthError("Missing parameter: '%s'" % req_param) client = Client() output = client.authenticate(params['username'], params['password'], params['otp']) except YKAuthError as err: logger.exception('Authentication error: %s', err) output = False except Exception as err: logger.exception('Backend error: %s', err) output = None raise finally: if output == True: resp = (200, 'true') elif output == False: resp = (400, 'false') else: resp = (500, 'false') start_response('%s OK' % resp[0], [('Content-Type', 'text/plain')]) return [resp[1].encode()]
def verify(environ, start_response): """ Handle OTP Validation """ apikey = ''.encode() try: params = parse_querystring(environ['QUERY_STRING']) logger.debug('PROCESSED QUERYSTRING: %s', params) verifyer = Verifyer() kwargs = params.copy() if 'id' in kwargs: kwargs.pop('id') if 'otp' in kwargs: kwargs.pop('otp') apikey = verifyer.get_client_apikey(params.get('id')) output = verifyer.verify(params.get('id'), params.get('otp'), **kwargs) except YKValError as err: logger.exception('Validation error: %s', err) output = '%s' % err except Exception as err: logger.exception('Backend error: %s', err) output = 'BACKEND_ERROR' finally: return wsgi_response(output, start_response, apikey=apikey, extra=None)
def sync(environ, start_response): """ Handle Sync requests """ local_params = None try: # Validate caller address if environ['REMOTE_ADDR'] not in settings['SYNC_POOL']: logger.error('Operation not permitted from IP %(REMOTE_ADDR)s', environ) raise YKSyncError( 'OPERATION_NOT_ALLOWED', 'Remote IP %(REMOTE_ADDR)s it not in sync pool' % environ) sync_params = parse_querystring(environ['QUERY_STRING']) logger.info( '[%s] Received sync request from %s (counter: %s, use: %s, nonce: %s)', sync_params.get('yk_publicname'), environ['REMOTE_ADDR'], sync_params.get('yk_counter'), sync_params.get('yk_use'), sync_params.get('nonce')) synclib = Sync() local_params = synclib.sync_local(sync_params) output = 'OK' status_code = 200 except YKSyncError as err: output = str(err) status_code = 401 except Exception as err: logger.exception('ERROR: %s', err) output = 'BACKEND_ERROR' status_code = 500 finally: return wsgi_response(output, start_response, apikey=''.encode(), extra=local_params, status=status_code)
def authenticate(environ, start_response): """ Handle authentications """ start_time = time.time() if 'client' not in PERSISTENT_OBJECTS: PERSISTENT_OBJECTS['client'] = Client() client = PERSISTENT_OBJECTS['client'] _format = 'json' if environ.get( 'HTTP_ACCEPT') == 'application/json' else 'text' params = {} try: # Parse POST request try: request_body_size = int(environ.get('CONTENT_LENGTH', 0)) except ValueError: request_body_size = 0 request_body = environ['wsgi.input'].read(request_body_size) params = parse_querystring(request_body.decode()) _params = params.copy() _params['password'] = '******' * 8 logger.debug('PROCESSED QUERYSTRING: %s', _params) # Checking parameters for req_param in REQUIRED_AUTH_PARAMS: if req_param not in params: raise YKAuthError('MISSING_PARAMETER') client.authenticate(params['username'], params['password'], params['otp']) status_code = 200 output = {'status': 'OK', 'message': 'Successful authentication'} except (YKAuthError, YKValError, YKSyncError, YKKSMError) as err: status_code = 400 output = {'status': err.error_code, 'message': str(err)} except Exception as err: status_code = 500 output = { 'status': 'BACKEND_ERROR', 'message': 'Backend error: %s' % err } logger.exception('Backend error: %s', err) finally: content_type = 'application/json' if _format == 'json' else 'text/plain' start_response(HTTP_STATUS_CODES[status_code], [('Content-Type', content_type)]) output['username'] = params.get('username', '') output['token_id'] = params.get('otp', '')[:-TOKEN_LEN] output['latency'] = round(time.time() - start_time, 3) output['src_ip'] = environ.get('REMOTE_ADDR') response = json.dumps(output if _format == 'json' else ( status_code == 200)) if status_code == 200: logger_sev = logging.INFO syslog_sev = syslog.LOG_INFO elif status_code == 400: logger_sev = logging.WARNING syslog_sev = syslog.LOG_WARNING else: logger_sev = logging.ERROR syslog_sev = syslog.LOG_ERR if settings['SYSLOG_WSGI_AUTH']: syslog.syslog( syslog_sev, response if _format == 'json' else json.dumps(output)) logger.log(logger_sev, '[%(username)s][%(token_id)s] %(status)s: %(message)s', output) return [response.encode()]
def authenticate(environ, start_response): """ Handle authentications """ start_time = time.time() if 'client' not in PERSISTENT_OBJECTS: PERSISTENT_OBJECTS['client'] = Client() client = PERSISTENT_OBJECTS['client'] _format = 'json' if environ.get('HTTP_ACCEPT') == 'application/json' else 'text' params = {} try: # Parse POST request try: request_body_size = int(environ.get('CONTENT_LENGTH', 0)) except ValueError: request_body_size = 0 request_body = environ['wsgi.input'].read(request_body_size) params = parse_querystring(request_body.decode()) _params = params.copy() _params['password'] = '******' * 8 logger.debug('PROCESSED QUERYSTRING: %s', _params) # Checking parameters for req_param in REQUIRED_AUTH_PARAMS: if req_param not in params: raise YKAuthError('MISSING_PARAMETER') client.authenticate(params['username'], params['password'], params['otp']) status_code = 200 output = {'status': 'OK', 'message': 'Successful authentication'} except (YKAuthError, YKValError, YKSyncError, YKKSMError) as err: status_code = 400 output = {'status': err.error_code, 'message': str(err)} except Exception as err: status_code = 500 output = {'status': 'BACKEND_ERROR', 'message': 'Backend error: %s' % err} logger.exception('Backend error: %s', err) finally: content_type = 'application/json' if _format == 'json' else 'text/plain' start_response(HTTP_STATUS_CODES[status_code], [('Content-Type', content_type)]) output['username'] = params.get('username', '') output['token_id'] = params.get('otp', '')[:-TOKEN_LEN] output['latency'] = round(time.time() - start_time, 3) output['src_ip'] = environ.get('REMOTE_ADDR') response = json.dumps(output if _format == 'json' else (status_code == 200)) if status_code == 200: logger_sev = logging.INFO syslog_sev = syslog.LOG_INFO elif status_code == 400: logger_sev = logging.WARNING syslog_sev = syslog.LOG_WARNING else: logger_sev = logging.ERROR syslog_sev = syslog.LOG_ERR if settings['SYSLOG_WSGI_AUTH']: syslog.syslog(syslog_sev, response if _format == 'json' else json.dumps(output)) logger.log(logger_sev, '[%(username)s][%(token_id)s] %(status)s: %(message)s', output) return [response.encode()]
def main(): """ Main program """ sync = Sync() queue_data = sync.db.read_queue() servers = set([x['server'] for x in queue_data]) for server in servers: logger.debug('Processing queue for server %s', server) items = [x for x in queue_data if x['server'] == server] try: for item in items[:20]: url = '%s?otp=%s&modified=%s&%s' % (item['server'], item['otp'], item['modified'], item['info'].split(',')[0]) resp = requests.get(url) if resp.status_code == 200: if resp.text.rstrip().endswith('status=OK'): # Read remote counters remote_params = parse_sync_response(resp.text) # Update local counters sync.db.update_db_counters(remote_params) # Get OTP counters otp_params = parse_querystring(item['info'].split(',')[0]) # Get validation counters before processing the OTP validation_params = parse_querystring(item['info'].split(',')[1]) validation_params = {'yk_counter': validation_params['local_counter'], 'yk_use': validation_params['local_use']} # Get current local counters local_params = sync.db.get_local_params(otp_params['yk_publicname']) if counters_gt(validation_params, remote_params): logger.info('[%s]: Remote server out of sync compared to counters ' 'at validation request time.', server) if counters_gt(remote_params, validation_params): if counters_eq(remote_params, otp_params): logger.info('[%s]: Remote server had received the current ' 'counter values already.', server) else: logger.info('Local server out of sync compared to counters ' 'at validation request time.') if counters_gt(local_params, remote_params): logger.info('Local server out of sync compared to current local ' 'counters. Local server updated.') if counters_gt(remote_params, otp_params): logger.info('[%s]: Remote server has higher counters than OTP. ' 'This response would have marked the OTP as invalid.', server) elif counters_eq(remote_params, otp_params) and \ remote_params['nonce'] != otp_params['nonce']: logger.info('[%s]: Remote server has equal counters as OTP ' 'and nonce differs. This response would have ' 'marked the OTP as invalid.', server) # Delete queue entry sync.db.remove_from_queue(server, item['modified'], item['server_nonce']) elif resp.text.rstrip().endswith('status=BAD_OTP'): logger.warning('[%s]: Remote server says BAD_OTP, pointless to try ' 'again, removing from queue.', server) # Delete queue entry sync.db.remove_from_queue(server, item['modified'], item['server_nonce']) else: logger.error('[%s]: Remote server refused our sync request. ' 'Check remote server logs.', server) else: logger.error('[%s]: Remote server refused our sync request. ' 'Check remote server logs.', server) except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as err: logger.error('Failed to connect to server %s: %s', server, err) continue