def _HandleServerRequestInner(handler, req_msg, reader): """Calls the handler function for the current request. """ handler_context = _HttpServerRequest(req_msg.start_line.method, req_msg.start_line.path, req_msg.headers, req_msg.body, reader.sock) logging.debug("Handling request %r", handler_context) try: try: # Authentication, etc. handler.PreHandleRequest(handler_context) # Call actual request handler result = handler.HandleRequest(handler_context) except (http.HttpException, errors.RapiTestResult, KeyboardInterrupt, SystemExit): raise except Exception as err: logging.exception("Caught exception") raise http.HttpInternalServerError(message=str(err)) except: logging.exception("Unknown exception") raise http.HttpInternalServerError(message="Unknown error") if not isinstance(result, (str, bytes)): raise http.HttpError("Handler function didn't return string type") return (http.HTTP_OK, handler_context.resp_headers, result) finally: # No reason to keep this any longer, even for exceptions handler_context.private = None
def Authenticate(cf, pam_handle, authtok=None): """Performs authentication via PAM. Perfroms two steps: - if authtok is provided then set it with pam_set_item - call pam_authenticate """ try: authtok_copy = None if authtok: authtok_copy = cf.strndup(authtok, len(authtok)) if not authtok_copy: raise http.HttpInternalServerError("Not enough memory for PAM") ret = cf.pam_set_item(c.pointer(pam_handle), PAM_AUTHTOK, authtok_copy) if ret != PAM_SUCCESS: raise http.HttpInternalServerError("pam_set_item failed [%d]" % ret) ret = cf.pam_authenticate(pam_handle, 0) if ret == PAM_ABORT: raise http.HttpInternalServerError( "pam_authenticate requested abort") if ret != PAM_SUCCESS: raise http.HttpUnauthorized("Authentication failed") except: cf.pam_end(pam_handle, ret) raise finally: if authtok_copy: cf.free(authtok_copy)
def SubmitJob(self, op, cl=None): """Generic wrapper for submit job, for better http compatibility. @type op: list @param op: the list of opcodes for the job @type cl: None or luxi.Client @param cl: optional luxi client to use @rtype: string @return: the job ID """ if cl is None: cl = self.GetClient() try: return cl.SubmitJob(op) except errors.JobQueueFull: raise http.HttpServiceUnavailable( "Job queue is full, needs archiving") except errors.JobQueueDrainError: raise http.HttpServiceUnavailable( "Job queue is drained, cannot submit") except rpcerr.NoMasterError as err: raise http.HttpBadGateway("Master seems to be unreachable: %s" % err) except rpcerr.PermissionError: raise http.HttpInternalServerError( "Internal error: no permission to" " connect to the master daemon") except rpcerr.TimeoutError as err: raise http.HttpGatewayTimeout("Timeout while talking to the master" " daemon: %s" % err)
def PutPamEnvVariable(cf, pam_handle, name, value): """Wrapper over pam_setenv. """ setenv = "%s=" % name if value: setenv += value ret = cf.pam_putenv(pam_handle, setenv) if ret != PAM_SUCCESS: raise http.HttpInternalServerError("pam_putenv call failed [%d]" % ret)
def GetClient(self): """Wrapper for L{luxi.Client} with HTTP-specific error handling. """ # Could be a function, pylint: disable=R0201 try: return self._client_cls() except rpcerr.NoMasterError as err: raise http.HttpBadGateway("Can't connect to master daemon: %s" % err) except rpcerr.PermissionError: raise http.HttpInternalServerError("Internal error: no permission to" " connect to the master daemon")
class ResourceBase(object): """Generic class for resources. """ # Default permission requirements GET_ACCESS = [] PUT_ACCESS = [rapi.RAPI_ACCESS_WRITE] POST_ACCESS = [rapi.RAPI_ACCESS_WRITE] DELETE_ACCESS = [rapi.RAPI_ACCESS_WRITE] def __init__(self, items, queryargs, req, _client_cls=None): """Generic resource constructor. @param items: a list with variables encoded in the URL @param queryargs: a dictionary with additional options from URL @param req: Request context @param _client_cls: L{luxi} client class (unittests only) """ assert isinstance(queryargs, dict) self.items = items self.queryargs = queryargs self._req = req if _client_cls is None: _client_cls = luxi.Client self._client_cls = _client_cls self.auth_user = "" def _GetRequestBody(self): """Returns the body data. """ return self._req.private.body_data request_body = property(fget=_GetRequestBody) def _checkIntVariable(self, name, default=0): """Return the parsed value of an int argument. """ val = self.queryargs.get(name, default) if isinstance(val, list): if val: val = val[0] else: val = default try: val = int(val) except (ValueError, TypeError): raise http.HttpBadRequest("Invalid value for the" " '%s' parameter" % (name, )) return val def _checkStringVariable(self, name, default=None): """Return the parsed value of a string argument. """ val = self.queryargs.get(name, default) if isinstance(val, list): if val: val = val[0] else: val = default return val def getBodyParameter(self, name, *args): """Check and return the value for a given parameter. If a second parameter is not given, an error will be returned, otherwise this parameter specifies the default value. @param name: the required parameter """ if args: return CheckParameter(self.request_body, name, default=args[0]) return CheckParameter(self.request_body, name) def useLocking(self): """Check if the request specifies locking. """ return bool(self._checkIntVariable("lock")) def useBulk(self): """Check if the request specifies bulk querying. """ return bool(self._checkIntVariable("bulk")) def useForce(self): """Check if the request specifies a forced operation. """ return bool(self._checkIntVariable("force")) def dryRun(self): """Check if the request specifies dry-run mode. """ return bool(self._checkIntVariable("dry-run")) def GetClient(self): """Wrapper for L{luxi.Client} with HTTP-specific error handling. """ # Could be a function, pylint: disable=R0201 try: return self._client_cls() except rpcerr.NoMasterError, err: raise http.HttpBadGateway("Can't connect to master daemon: %s" % err) except rpcerr.PermissionError: raise http.HttpInternalServerError( "Internal error: no permission to" " connect to the master daemon")
trail = getattr(opcode, constants.OPCODE_REASON, []) trail.append(self.GetAuthReason()) setattr(opcode, constants.OPCODE_REASON, trail) return cl.SubmitJob(op) except errors.JobQueueFull: raise http.HttpServiceUnavailable( "Job queue is full, needs archiving") except errors.JobQueueDrainError: raise http.HttpServiceUnavailable( "Job queue is drained, cannot submit") except rpcerr.NoMasterError, err: raise http.HttpBadGateway("Master seems to be unreachable: %s" % err) except rpcerr.PermissionError: raise http.HttpInternalServerError( "Internal error: no permission to" " connect to the master daemon") except rpcerr.TimeoutError, err: raise http.HttpGatewayTimeout("Timeout while talking to the master" " daemon: %s" % err) def GetResourceOpcodes(cls): """Returns all opcodes used by a resource. """ return frozenset( filter(None, (getattr(cls, method_attrs.opcode, None) for method_attrs in OPCODE_ATTRS)))
def ValidateRequest(cf, username, uri_access_rights, password=None, service=DEFAULT_SERVICE_NAME, authtok=None, uri=None, method=None, body=None): """Checks whether it's permitted to execute an rapi request. Calls pam_authenticate and then pam_acct_mgmt in order to check whether a request should be executed. @param cf: An instance of CFunctions class containing necessary imports @param username: username @param uri_access_rights: handler access rights @param password: password @param service: a service name that will be used for the interaction with PAM @param authtok: user's authentication token (e.g. some kind of signature) @param uri: an uri of a target resource obtained from an http header @param method: http method trying to access the uri @param body: a body of an RAPI request @return: On success - authenticated user name. Throws an exception otherwise. """ ValidateParams(username, uri_access_rights, password, service, authtok, uri, method, body) def ConversationFunction(num_msg, msg, resp, _app_data_ptr): """Conversation function that will be provided to PAM modules. The function replies with a password for each message with PAM_PROMPT_ECHO_OFF style and just ignores the others. """ if num_msg > MAX_MSG_COUNT: logging.warning("Too many messages passed to conv function: [%d]", num_msg) return PAM_BUF_ERR response = cf.calloc(num_msg, c.sizeof(PamResponse)) if not response: logging.warning("calloc failed in conv function") return PAM_BUF_ERR resp[0] = c.cast(response, c.POINTER(PamResponse)) for i in range(num_msg): if msg[i].contents.msg_style != PAM_PROMPT_ECHO_OFF: continue resp.contents[i].resp = cf.strndup(password, len(password)) if not resp.contents[i].resp: logging.warning("strndup failed in conv function") for j in range(i): cf.free(c.cast(resp.contents[j].resp, c.c_void_p)) cf.free(response) return PAM_BUF_ERR resp.contents[i].resp_retcode = 0 return PAM_SUCCESS pam_handle = PamHandleT() conv = PamConv(CONV_FUNC(ConversationFunction), 0) ret = cf.pam_start(service, username, c.pointer(conv), c.pointer(pam_handle)) if ret != PAM_SUCCESS: cf.pam_end(pam_handle, ret) raise http.HttpInternalServerError("pam_start call failed [%d]" % ret) Authenticate(cf, pam_handle, authtok) Authorize(cf, pam_handle, uri_access_rights, uri, method, body) # retrieve the authorized user name puser = c.c_void_p() ret = cf.pam_get_item(pam_handle, PAM_USER, c.pointer(puser)) if ret != PAM_SUCCESS or not puser: cf.pam_end(pam_handle, ret) raise http.HttpInternalServerError("pam_get_item call failed [%d]" % ret) user_c_string = c.cast(puser, c.c_char_p) cf.pam_end(pam_handle, PAM_SUCCESS) return user_c_string.value