def _assemble_json(responses, resource=None, url=None, extension=None): #NOTE(jbjohnso) I'm considering giving up on yielding bit by bit #in json case over http. Notably, duplicate key values from plugin #overwrite, but we'd want to preserve them into an array instead. #the downside is that http would just always blurt it ll out at #once and hold on to all the data in memory links = {} if resource is not None: links['self'] = {"href": resource + extension} if url == '/': pass elif resource[-1] == '/': links['collection'] = {"href": "../" + extension} else: links['collection'] = {"href": "./" + extension} rspdata = {} for rsp in responses: if isinstance(rsp, confluent.messages.LinkRelation): haldata = rsp.raw() for hk in haldata: if 'href' in haldata[hk]: if isinstance(haldata[hk]['href'], int): haldata[hk]['href'] = str(haldata[hk]['href']) haldata[hk]['href'] += extension if hk in links: if isinstance(links[hk], list): links[hk].append(haldata[hk]) else: links[hk] = [links[hk], haldata[hk]] elif hk == 'item': links[hk] = [ haldata[hk], ] else: links[hk] = haldata[hk] else: rsp = rsp.raw() for dk in rsp: if dk in rspdata: if isinstance(rspdata[dk], list): if isinstance(rsp[dk], list): rspdata[dk].extend(rsp[dk]) else: rspdata[dk].append(rsp[dk]) else: rspdata[dk] = [rspdata[dk], rsp[dk]] else: if dk == 'databynode' or dk == 'asyncresponse': # a quirk, databynode suggests noderange # multi response. This should *always* be a list, # even if it will be length 1 rspdata[dk] = [rsp[dk]] else: rspdata[dk] = rsp[dk] rspdata["_links"] = links tlvdata.unicode_dictvalues(rspdata) yield util.stringify( json.dumps(rspdata, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8'))
def _assemble_json(responses, resource=None, url=None, extension=None): #NOTE(jbjohnso) I'm considering giving up on yielding bit by bit #in json case over http. Notably, duplicate key values from plugin #overwrite, but we'd want to preserve them into an array instead. #the downside is that http would just always blurt it ll out at #once and hold on to all the data in memory links = {} if resource is not None: links['self'] = {"href": resource + extension} if url == '/': pass elif resource[-1] == '/': links['collection'] = {"href": "../" + extension} else: links['collection'] = {"href": "./" + extension} rspdata = {} for rsp in responses: if isinstance(rsp, confluent.messages.LinkRelation): haldata = rsp.raw() for hk in haldata.iterkeys(): if 'href' in haldata[hk]: if isinstance(haldata[hk]['href'], int): haldata[hk]['href'] = str(haldata[hk]['href']) haldata[hk]['href'] += extension if hk in links: if isinstance(links[hk], list): links[hk].append(haldata[hk]) else: links[hk] = [links[hk], haldata[hk]] elif hk == 'item': links[hk] = [haldata[hk],] else: links[hk] = haldata[hk] else: rsp = rsp.raw() for dk in rsp.iterkeys(): if dk in rspdata: if isinstance(rspdata[dk], list): rspdata[dk].append(rsp[dk]) else: rspdata[dk] = [rspdata[dk], rsp[dk]] else: if dk == 'databynode' or dk == 'asyncresponse': # a quirk, databynode suggests noderange # multi response. This should *always* be a list, # even if it will be length 1 rspdata[dk] = [rsp[dk]] else: rspdata[dk] = rsp[dk] rspdata["_links"] = links tlvdata.unicode_dictvalues(rspdata) yield json.dumps( rspdata, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8')
def process_request(connection, request, cfm, authdata, authname, skipauth): if isinstance(request, tlvdata.ClientFile): cfm.add_client_file(request) return if not isinstance(request, dict): raise exc.InvalidArgumentException operation = request['operation'] path = request['path'] params = request.get('parameters', {}) hdlr = None auditmsg = { 'operation': operation, 'target': path, } if not skipauth: authdata = auth.authorize(authdata[2], path, authdata[3], operation) if not authdata: auditmsg['allowed'] = False auditlog.log(auditmsg) raise exc.ForbiddenRequest() auditmsg['user'] = authdata[2] if authdata[3] is not None: auditmsg['tenant'] = authdata[3] auditmsg['allowed'] = True if _should_authlog(path, operation): tlvdata.unicode_dictvalues(auditmsg) auditlog.log(auditmsg) try: if operation == 'start': return start_term(authname, cfm, connection, params, path, authdata, skipauth) elif operation == 'shutdown' and skipauth: configmanager.ConfigManager.shutdown() else: hdlr = pluginapi.handle_path(path, operation, cfm, params) except exc.NotFoundException as e: send_data(connection, { "errorcode": 404, "error": "Target not found - " + str(e) }) send_data(connection, {"_requestdone": 1}) except exc.InvalidArgumentException as e: send_data(connection, { "errorcode": 400, "error": "Bad Request - " + str(e) }) send_data(connection, {"_requestdone": 1}) send_response(hdlr, connection) return
def resourcehandler_backend(env, start_response): """Function to handle new wsgi requests """ mimetype, extension = _pick_mimetype(env) headers = [('Content-Type', mimetype), ('Cache-Control', 'no-store'), ('Pragma', 'no-cache'), ('X-Content-Type-Options', 'nosniff'), ('Content-Security-Policy', "default-src 'self'"), ('X-XSS-Protection', '1; mode=block'), ('X-Frame-Options', 'deny'), ('Strict-Transport-Security', 'max-age=86400'), ('X-Permitted-Cross-Domain-Policies', 'none')] reqbody = None reqtype = None if 'CONTENT_LENGTH' in env and int(env['CONTENT_LENGTH']) > 0: reqbody = env['wsgi.input'].read(int(env['CONTENT_LENGTH'])) reqtype = env['CONTENT_TYPE'] operation = opmap[env['REQUEST_METHOD']] querydict = _get_query_dict(env, reqbody, reqtype) if 'restexplorerop' in querydict: operation = querydict['restexplorerop'] del querydict['restexplorerop'] authorized = _authorize_request(env, operation) if 'logout' in authorized: start_response('200 Successful logout', headers) yield ('{"result": "200 - Successful logout"}') return if 'HTTP_SUPPRESSAUTHHEADER' in env or 'HTTP_CONFLUENTAUTHTOKEN' in env: badauth = [('Content-type', 'text/plain')] else: badauth = [('Content-type', 'text/plain'), ('WWW-Authenticate', 'Basic realm="confluent"')] if authorized['code'] == 401: start_response('401 Authentication Required', badauth) yield 'authentication required' return if authorized['code'] == 403: start_response('403 Forbidden', badauth) yield 'Forbidden' return if authorized['code'] != 200: raise Exception("Unrecognized code from auth engine") headers.extend(("Set-Cookie", m.OutputString()) for m in authorized['cookie'].values()) cfgmgr = authorized['cfgmgr'] if (operation == 'create') and env['PATH_INFO'] == '/sessions/current/async': pagecontent = "" try: for rsp in _assemble_json( confluent.asynchttp.handle_async( env, querydict, httpsessions[authorized['sessionid']]['inflight'])): pagecontent += rsp start_response("200 OK", headers) if not isinstance(pagecontent, bytes): pagecontent = pagecontent.encode('utf-8') yield pagecontent return except exc.ConfluentException as e: if e.apierrorcode == 500: # raise generics to trigger the tracelog raise start_response('{0} {1}'.format(e.apierrorcode, e.apierrorstr), headers) yield e.get_error_body() elif (env['PATH_INFO'].endswith('/forward/web') and env['PATH_INFO'].startswith('/nodes/')): prefix, _, _ = env['PATH_INFO'].partition('/forward/web') _, _, nodename = prefix.rpartition('/') hm = cfgmgr.get_node_attributes(nodename, 'hardwaremanagement.manager') targip = hm.get(nodename, {}).get('hardwaremanagement.manager', {}).get('value', None) if not targip: start_response('404 Not Found', headers) yield 'No hardwaremanagemnet.manager defined for node' return funport = forwarder.get_port(targip, env['HTTP_X_FORWARDED_FOR'], authorized['sessionid']) host = env['HTTP_X_FORWARDED_HOST'] if ']' in host: host = host.split(']')[0] + ']' elif ':' in host: host = host.rsplit(':', 1)[0] url = 'https://{0}:{1}/'.format(host, funport) start_response('302', [('Location', url)]) yield 'Our princess is in another castle!' return elif (operation == 'create' and ('/console/session' in env['PATH_INFO'] or '/shell/sessions/' in env['PATH_INFO'])): #hard bake JSON into this path, do not support other incarnations if '/console/session' in env['PATH_INFO']: prefix, _, _ = env['PATH_INFO'].partition('/console/session') shellsession = False elif '/shell/sessions/' in env['PATH_INFO']: prefix, _, _ = env['PATH_INFO'].partition('/shell/sessions') shellsession = True _, _, nodename = prefix.rpartition('/') if 'session' not in querydict.keys() or not querydict['session']: auditmsg = { 'operation': 'start', 'target': env['PATH_INFO'], 'user': util.stringify(authorized['username']), } if 'tenant' in authorized: auditmsg['tenant'] = authorized['tenant'] auditlog.log(auditmsg) # Request for new session skipreplay = False if 'skipreplay' in querydict and querydict['skipreplay']: skipreplay = True width = querydict.get('width', 80) height = querydict.get('height', 24) datacallback = None async = None if 'HTTP_CONFLUENTASYNCID' in env: async = confluent.asynchttp.get_async(env, querydict) termrel = async .set_term_relation(env) datacallback = termrel.got_data try: if shellsession: consession = shellserver.ShellSession( node=nodename, configmanager=cfgmgr, username=authorized['username'], skipreplay=skipreplay, datacallback=datacallback, width=width, height=height) else: consession = consoleserver.ConsoleSession( node=nodename, configmanager=cfgmgr, username=authorized['username'], skipreplay=skipreplay, datacallback=datacallback, width=width, height=height) except exc.NotFoundException: start_response("404 Not found", headers) yield "404 - Request Path not recognized" return if not consession: start_response("500 Internal Server Error", headers) return sessid = _assign_consessionid(consession) if async: async .add_console_session(sessid) start_response('200 OK', headers) yield '{"session":"%s","data":""}' % sessid return elif 'bytes' in querydict.keys(): # not keycodes... myinput = querydict['bytes'] sessid = querydict['session'] if sessid not in consolesessions: start_response('400 Expired Session', headers) return consolesessions[sessid]['expiry'] = time.time() + 90 consolesessions[sessid]['session'].write(myinput) start_response('200 OK', headers) yield json.dumps({'session': querydict['session']}) return # client has requests to send or receive, not both... elif 'closesession' in querydict: consolesessions[querydict['session']]['session'].destroy() del consolesessions[querydict['session']] start_response('200 OK', headers) yield '{"sessionclosed": true}' return elif 'action' in querydict: if querydict['action'] == 'break': consolesessions[querydict['session']]['session'].send_break() elif querydict['action'] == 'resize': consolesessions[querydict['session']]['session'].resize( width=querydict['width'], height=querydict['height']) elif querydict['action'] == 'reopen': consolesessions[querydict['session']]['session'].reopen() else: start_response('400 Bad Request') yield 'Unrecognized action ' + querydict['action'] return start_response('200 OK', headers) yield json.dumps({'session': querydict['session']}) else: # no keys, but a session, means it's hooking to receive data sessid = querydict['session'] if sessid not in consolesessions: start_response('400 Expired Session', headers) yield '' return consolesessions[sessid]['expiry'] = time.time() + 90 # add our thread to the 'inflight' to have a hook to terminate # a long polling request loggedout = None mythreadid = greenlet.getcurrent() httpsessions[authorized['sessionid']]['inflight'].add(mythreadid) try: outdata = consolesessions[sessid]['session'].get_next_output( timeout=25) except greenlet.GreenletExit as ge: loggedout = ge httpsessions[authorized['sessionid']]['inflight'].discard( mythreadid) if sessid not in consolesessions: start_response('400 Expired Session', headers) yield '' return if loggedout is not None: consolesessions[sessid]['session'].destroy() start_response('401 Logged out', headers) yield '{"loggedout": 1}' return bufferage = False if 'stampsent' not in consolesessions[sessid]: consolesessions[sessid]['stampsent'] = True bufferage = consolesessions[sessid]['session'].get_buffer_age() if isinstance(outdata, dict): rspdata = outdata rspdata['session'] = querydict['session'] else: rspdata = {'session': querydict['session'], 'data': outdata} if bufferage is not False: rspdata['bufferage'] = bufferage try: rsp = json.dumps(rspdata) except UnicodeDecodeError: try: rsp = json.dumps(rspdata, encoding='cp437') except UnicodeDecodeError: rsp = json.dumps({ 'session': querydict['session'], 'data': 'DECODEERROR' }) start_response('200 OK', headers) yield rsp return else: # normal request url = env['PATH_INFO'] url = url.replace('.json', '') url = url.replace('.html', '') if url == '/sessions/current/info': start_response('200 OK', headers) sessinfo = {'username': authorized['username']} if 'authtoken' in authorized: sessinfo['authtoken'] = authorized['authtoken'] tlvdata.unicode_dictvalues(sessinfo) yield json.dumps(sessinfo) return resource = '.' + url[url.rindex('/'):] lquerydict = copy.deepcopy(querydict) try: hdlr = pluginapi.handle_path(url, operation, cfgmgr, querydict) if 'HTTP_CONFLUENTASYNCID' in env: confluent.asynchttp.run_handler(hdlr, env) start_response('202 Accepted', headers) yield 'Request queued' return pagecontent = "" if mimetype == 'text/html': for datum in _assemble_html(hdlr, resource, lquerydict, url, extension): pagecontent += datum else: for datum in _assemble_json(hdlr, resource, url, extension): pagecontent += datum start_response('200 OK', headers) if not isinstance(pagecontent, bytes): pagecontent = pagecontent.encode('utf-8') yield pagecontent except exc.ConfluentException as e: if ((not isinstance(e, exc.LockedCredentials)) and e.apierrorcode == 500): # raise generics to trigger the tracelog raise start_response('{0} {1}'.format(e.apierrorcode, e.apierrorstr), headers) yield e.get_error_body()