def get_message_handler(service, taxii_message): """ Given a service and a TAXII Message, return the message handler class. """ # Convenience aliases st = service.service_type mt = taxii_message.message_type handler = None try: if st == SVC_INBOX and mt == MSG_INBOX_MESSAGE: handler = service.inbox_message_handler elif st == SVC_POLL and mt == MSG_POLL_REQUEST: handler = service.poll_request_handler elif st == SVC_POLL and mt == MSG_POLL_FULFILLMENT_REQUEST: handler = service.poll_fulfillment_handler elif st == SVC_DISCOVERY and mt == MSG_DISCOVERY_REQUEST: handler = service.discovery_handler elif st == SVC_COLLECTION_MANAGEMENT and mt in ( MSG_COLLECTION_INFORMATION_REQUEST, MSG_FEED_INFORMATION_REQUEST): handler = service.collection_information_handler elif st == SVC_COLLECTION_MANAGEMENT and mt in ( MSG_MANAGE_COLLECTION_SUBSCRIPTION_REQUEST, MSG_MANAGE_FEED_SUBSCRIPTION_REQUEST): handler = service.subscription_management_handler except models.MessageHandler.DoesNotExist: raise StatusMessageException( taxii_message.message_id, ST_FAILURE, "The MessageHandler lookup failed for service at %s" % service.path) if not handler: raise StatusMessageException( taxii_message.message_id, ST_FAILURE, "Message Type: %s is not supported by %s" % (mt, st)) module_name, class_name = handler.handler.rsplit('.', 1) try: module = import_module(module_name) handler_class = getattr(module, class_name) except Exception as e: type_, value, traceback = sys.exc_info() raise type_, ("Error importing handler: %s" % handler.handler, type, value), traceback return handler_class
def handle_message(poll_service, poll_fulfillment_request, django_request): """ Looks in the database for a matching result set part and return it. Workflow: 1. Look in models.ResultSetPart for a ResultSetPart that matches the criteria of the request 2. Update the ResultSetPart's parent (models.ResultSet) to store which ResultSetPart was most recently returned 3. Turn the ResultSetPart into a PollResponse, and return it """ try: rsp = models.ResultSetPart.objects.get( result_set__pk=poll_fulfillment_request.result_id, part_number=poll_fulfillment_request.result_part_number, result_set__data_collection__name=poll_fulfillment_request. collection_name) poll_response = rsp.to_poll_response_11( poll_fulfillment_request.message_id) rsp.result_set.last_part_returned = rsp rsp.save() return poll_response except models.ResultSetPart.DoesNotExist: raise StatusMessageException( poll_fulfillment_request.message_id, ST_NOT_FOUND, {SD_ITEM: str(poll_fulfillment_request.result_id)})
def filter_content(cls, prp, content_blocks): """ Turns the prp.query into an XPath, runs the XPath against each item in `content_blocks`, and returns the items in `content_blocks` that match the XPath. :param prp: A PollRequestParameters object representing the Poll Request :param content_blocks: A list of models.ContentBlock objects to filter :return: A list of models.ContentBlock objects matching the query """ if prp.query.targeting_expression_id not in cls.get_supported_tevs(): raise StatusMessageException( prp.message_id, ST_UNSUPPORTED_TARGETING_EXPRESSION_ID, status_detail={ SD_TARGETING_EXPRESSION_ID: cls.get_supported_tevs() }) result_list = [] for content_block in content_blocks: etree_content = parse(content_block.content) if cls.evaluate_criteria(prp, etree_content, prp.query.criteria): result_list.append(content_block) return result_list
def _wrapped_view(request, *args, **kwargs): if request.method != 'POST': raise StatusMessageException('0', ST_BAD_MESSAGE, 'Request method was not POST!') # If requested, attempt to validate # TODO: Validation has changed! if do_validate: try: validate(request) except Exception as e: raise StatusMessageException('0', ST_BAD_MESSAGE, e.message) # Attempt to deserialize try: message = deserialize(request) except Exception as e: raise StatusMessageException('0', ST_BAD_MESSAGE, e.message) try: if message_types is None: pass # No checking was requested elif isinstance(message_types, list): if message.__class__ not in message_types: raise ValueError( 'Message type not allowed. Must be one of: %s' % message_types) elif isinstance(message_types, object): if not isinstance(message, message_types): raise ValueError( 'Message type not allowed. Must be: %s' % message_types) elif message_types is not None: raise ValueError( 'Something strange happened with message_types! Was not a list or object!' ) except ValueError as e: msg = tm11.StatusMessage(generate_message_id(), message.message_id, status_type='FAILURE', message=e.message) return HttpResponseTaxii(msg.to_xml(), response_headers) kwargs['taxii_message'] = message return view_func(request, *args, **kwargs)
def handle_message(discovery_service, discovery_request, django_request): """ Passes the message off to either DiscoveryRequest10Handler or DiscoveryRequest11Handler """ if isinstance(discovery_request, tm10.DiscoveryRequest): return DiscoveryRequest10Handler.handle_message( discovery_service, discovery_request, django_request) elif isinstance(discovery_request, tm11.DiscoveryRequest): return DiscoveryRequest11Handler.handle_message( discovery_service, discovery_request, django_request) else: raise StatusMessageException( discovery_request.message_id, ST_FAILURE, "TAXII Message not supported by Message Handler.")
def handle_message(inbox_service, inbox_message, django_request): """ Passes the request to either InboxMessage10Handler or InboxMessage11Handler """ if isinstance(inbox_message, tm10.InboxMessage): return InboxMessage10Handler.handle_message( inbox_service, inbox_message, django_request) elif isinstance(inbox_message, tm11.InboxMessage): return InboxMessage11Handler.handle_message( inbox_service, inbox_message, django_request) else: raise StatusMessageException( inbox_message.message_id, ST_FAILURE, "TAXII Message not supported by Message Handler.")
def create_pending_response(cls, poll_service, prp, content): """ Arguments: poll_service (models.PollService) - The TAXII Poll Service being invoked prp (util.PollRequestProperties) - The Poll Request Properties of the Poll Request content - A list of content (nominally, models.ContentBlock objects). This method returns a StatusMessage with a Status Type of Pending OR raises a StatusMessageException based on the following table:: asynch | Delivery_Params | can_push || Response Type ---------------------------------------------------- True | - | - || Pending - Asynch False | Yes | Yes || Pending - Push False | Yes | No || StatusMessageException False | No | - || StatusMessageException """ # Identify the Exception conditions first (e.g., rows #3 and #4) if (prp.allow_asynch is False and (prp.delivery_parameters is None or prp.can_push is False)): raise StatusMessageException( prp.message_id, ST_FAILURE, "The content was not available now and \ the request had allow_asynch=False and no \ Delivery Parameters were specified.") # Rows #1 and #2 are both Status Messages with a type of Pending result_set = cls.create_result_set(content, prp, poll_service) sm = tm11.StatusMessage(message_id=generate_message_id(), in_response_to=prp.message_id, status_type=ST_PENDING) if prp.allow_asynch: sm.status_details = { SD_ESTIMATED_WAIT: 300, SD_RESULT_ID: result_set.pk, SD_WILL_PUSH: False } else: # TODO: Check and see if the requested delivery parameters are supported sm.status_details = { SD_ESTIMATED_WAIT: 300, SD_RESULT_ID: result_set.pk, SD_WILL_PUSH: True } # TODO: Need to try pushing or something. return sm
def validate_message_is_supported(cls, taxii_message): """ Checks whether the TAXII Message is supported by this Message Handler. Arguments: taxii_message - A libtaxii.messages_11 or libtaxii.messages_10 taxii message Returns: None if the message is supported, raises a StatusMessageException otherwise. """ if taxii_message.__class__ not in cls.get_supported_request_messages(): raise StatusMessageException(taxii_message.message_id, ST_FAILURE, "TAXII Message not supported by Message Handler.") return
def handle_message(poll_service, poll_request, django_request): """ Passes the request to either PollRequest10Handler or PollRequest11Handler """ if isinstance(poll_request, tm10.PollRequest): return PollRequest10Handler.handle_message(poll_service, poll_request, django_request) elif isinstance(poll_request, tm11.PollRequest): return PollRequest11Handler.handle_message(poll_service, poll_request, django_request) else: raise StatusMessageException( poll_request.message_id, ST_FAILURE, "TAXII Message not supported by Message Handler.")
def handle_message(collection_management_service, manage_collection_subscription_request, django_request): """ Passes the request to either SubscriptionRequest10Handler or SubscriptionRequest11Handler. """ # aliases because names are long cms = collection_management_service mcsr = manage_collection_subscription_request dr = django_request if isinstance(mcsr, tm10.ManageFeedSubscriptionRequest): return SubscriptionRequest10Handler.handle_message(cms, mcsr, dr) elif isinstance(mcsr, tm11.ManageCollectionSubscriptionRequest): return SubscriptionRequest11Handler.handle_message(cms, mcsr, dr) else: raise StatusMessageException( mcsr.message_id, ST_FAILURE, "TAXII Message not supported by Message Handler.")
def handle_message(cls, collection_management_service, manage_collection_subscription_request, django_request): """ Workflow: (Kinda big) #. Validate the Data Collection that the request identifies #. Validate a variety of aspects of the request message #. If there's a subscription_id in the request message, attempt to identify that \ subscription in the database #. If Action == Subscribe, call `subscribe(service, data_collection, request)` #. If Action == Unsubscribe, call `unsubscribe(service, request, subscription)` #. If Action == Pause, call `pause(service, request, subscription)` #. If Action == Resume, call `resume(service, request, subscription)` #. If Action == Status and there is a `subscription_id, call single_status(service, request, subscription)` #. If Action == Status and there is not a `subscription_id, call `multi_status(service, request)` #. Return a CollectionManageSubscriptionResponse """ # Create an alias because the name is long as fiddlesticks smr = manage_collection_subscription_request cms = collection_management_service # This code follows the guidance in the TAXII Services Spec 1.1 Section 4.4.6. # This code could probably be optimized, but it exists in part to be readable. # 1. This code doesn't do authentication, so this step is skipped # 2. If the Collection Name does not exist, respond with a Status Message data_collection = cms.validate_collection_name(smr.collection_name, smr.message_id) # The following code executes this truth table: # |Action | Subscription ID | Subscription ID | # | | in message? | DB match? | # |--------------------------------------------------| # |Subscribe | Prohibited | N/A | # |Unsubscribe | Required | Not Needed | # |Pause | Required | Needed | # |Resume | Required | Needed | # |Status | Optional | Yes, if specified | if smr.action not in ACT_TYPES_11: raise StatusMessageException( smr.message_id, ST_BAD_MESSAGE, message="The specified value of Action was invalid.") # "For messages where the Action field is UNSUBSCRIBE, PAUSE, or RESUME, [subscription id] MUST be present" if smr.action in (ACT_UNSUBSCRIBE, ACT_PAUSE, ACT_RESUME) and not smr.subscription_id: raise StatusMessageException( smr.message_id, ST_BAD_MESSAGE, message="The %s action requires a subscription id." % smr.action) # Attempt to identify a subscription in the database subscription = None if smr.subscription_id: try: subscription = models.Subscription.objects.get( subscription_id=smr.subscription_id) except models.Subscription.DoesNotExist: subscription = None # This is OK for certain circumstances # If subscription is None for Unsubscribe, that's OK, but it's not OK for Pause/Resume if subscription is None and smr.action in (ACT_PAUSE, ACT_RESUME): raise StatusMessageException( smr.message_id, ST_NOT_FOUND, status_detail={SD_ITEM: smr.subscription_id}) # Create a stub ManageCollectionSubscriptionResponse response = tm11.ManageCollectionSubscriptionResponse( message_id=generate_message_id(), in_response_to=smr.message_id, collection_name=data_collection.name) # This code can probably be optimized if smr.action == ACT_SUBSCRIBE: subs_instance = cls.subscribe(smr, data_collection) response.subscription_instances.append(subs_instance) elif smr.action == ACT_UNSUBSCRIBE: subs_instance = cls.unsubscribe(smr, subscription) response.subscription_instances.append(subs_instance) elif smr.action == ACT_PAUSE: subs_instance = cls.pause(smr, subscription) response.subscription_instances.append(subs_instance) elif smr.action == ACT_RESUME: subs_instance = cls.resume(smr, subscription) response.subscription_instances.append(subs_instance) elif smr.action == ACT_STATUS and subscription is not None: subs_instance = cls.single_status(smr, subscription) response.subscription_instances.append(subs_instance) elif smr.action == ACT_STATUS and subscription is None: subs_instances = cls.multi_status(smr) for subs_instance in subs_instances: response.subscription_instances.append(subs_instance) else: raise ValueError("Unknown Action!") # print response.to_xml(pretty_print=True) return response
def from_poll_request_10(poll_service, poll_request): prp = PollRequestProperties() prp.poll_request = poll_request prp.message_id = poll_request.message_id prp.collection = poll_service.validate_collection_name( poll_request.feed_name, poll_request.message_id) if poll_request.subscription_id: try: s = models.Subscription.objects.get( subscription_id=poll_request.subscription_id) prp.subscription = s except models.Subscription.DoesNotExist: raise StatusMessageException( poll_request.message_id, ST_NOT_FOUND, status_detail={SD_ITEM: poll_request.subscription_id}) prp.response_type = None prp.content_bindings = s.content_binding.all() prp.allow_asynch = None prp.delivery_parameters = s.delivery_parameters else: prp.response_type = None prp.content_bindings = prp.collection.get_binding_intersection_10( poll_request.content_bindings, prp.message_id) prp.delivery_parameters = None if prp.collection.type != CT_DATA_FEED: # Only Data Feeds existed in TAXII 1.0 raise StatusMessageException( poll_request.message_id, ST_NOT_FOUND, "The Named Data Collection is not a Data Feed, it is a Data Set. " "Only Data Feeds can be" "Polled in TAXII 1.0", {SD_ITEM: poll_request.feed_name}) current_datetime = datetime.datetime.now(tzutc()) # If the request specifies a timestamp label in an acceptable range, use it. # Otherwise, don't use a begin timestamp label if poll_request.exclusive_begin_timestamp_label: pr_ebtl = poll_request.exclusive_begin_timestamp_label if pr_ebtl < current_datetime: prp.exclusive_begin_timestamp_label = poll_request.exclusive_begin_timestamp_label # Use either the specified end timestamp label; # or the current time iff the specified end timestmap label is after the current time prp.inclusive_end_timestamp_label = current_datetime if poll_request.inclusive_end_timestamp_label: pr_ietl = poll_request.inclusive_end_timestamp_label if pr_ietl < current_datetime: prp.inclusive_end_timestamp_label = poll_request.inclusive_end_timestamp_label if ((prp.inclusive_end_timestamp_label is not None and prp.exclusive_begin_timestamp_label is not None) and prp.inclusive_end_timestamp_label < prp.exclusive_begin_timestamp_label): raise StatusMessageException( prp.message_id, ST_FAILURE, message="Invalid Timestamp Labels: End TS Label is earlier " "than Begin TS Label") return prp
def from_poll_request_11(poll_service, poll_request): prp = PollRequestProperties() prp.poll_request = poll_request prp.message_id = poll_request.message_id prp.collection = poll_service.validate_collection_name( poll_request.collection_name, poll_request.message_id) if poll_request.subscription_id: try: s = models.Subscription.objects.get( subscription_id=poll_request.subscription_id) prp.subscription = s except models.Subscription.DoesNotExist: raise StatusMessageException( poll_request.message_id, ST_NOT_FOUND, "The subscription was not found", {SD_ITEM: poll_request.subscription_id}) prp.response_type = s.response_type prp.content_bindings = s.content_binding.all() prp.allow_asynch = False prp.query = s.query if prp.query: prp.supported_query = poll_service.get_supported_query( prp.query, prp.message_id) else: prp.supported_query = None prp.delivery_parameters = s.delivery_parameters else: pp = poll_request.poll_parameters prp.response_type = pp.response_type prp.content_bindings = prp.collection.get_binding_intersection_11( pp.content_bindings, prp.message_id) prp.allow_asynch = pp.allow_asynch prp.query = pp.query if prp.query: prp.supported_query = poll_service.get_supported_query( prp.query, prp.message_id) else: prp.supported_query = None prp.delivery_parameters = pp.delivery_parameters if prp.collection.type == CT_DATA_FEED: # Only data feeds care about timestamp labels current_datetime = datetime.datetime.now(tzutc()) # If the request specifies a timestamp label in an acceptable range, use it. # Otherwise, don't use a begin timestamp label if poll_request.exclusive_begin_timestamp_label: pr_ebtl = poll_request.exclusive_begin_timestamp_label if pr_ebtl < current_datetime: prp.exclusive_begin_timestamp_label = poll_request.exclusive_begin_timestamp_label # Use either the specified end timestamp label; # or the current time iff the specified end timestmap label is after the current time prp.inclusive_end_timestamp_label = current_datetime if poll_request.inclusive_end_timestamp_label: pr_ietl = poll_request.inclusive_end_timestamp_label if pr_ietl < current_datetime: prp.inclusive_end_timestamp_label = poll_request.inclusive_end_timestamp_label if ((prp.inclusive_end_timestamp_label is not None and prp.exclusive_begin_timestamp_label is not None) and prp.inclusive_end_timestamp_label < prp.exclusive_begin_timestamp_label): raise StatusMessageException( prp.message_id, ST_FAILURE, message="Invalid Timestamp Labels: End TS Label is earlier " "than Begin TS Label") return prp
def service_router(request, path, do_validate=True): """ Takes in a request, path, and TAXII Message, and routes the taxii_message to the Service Handler. """ if request.method != 'POST': raise StatusMessageException('0', ST_BAD_MESSAGE, 'Request method was not POST!') xtct = request.META.get('HTTP_X_TAXII_CONTENT_TYPE', None) if not xtct: raise StatusMessageException( '0', ST_BAD_MESSAGE, 'The X-TAXII-Content-Type Header was not present.') parse_tuple = xtct_map.get(xtct) if not parse_tuple: raise StatusMessageException( '0', ST_BAD_MESSAGE, 'The X-TAXII-Content-Type Header is not supported.') if do_validate: msg = None # None means no error, a non-None value means an error happened try: result = parse_tuple.validator.validate_string(request.body) if not result.valid: if settings.DEBUG is True: msg = 'Request was not schema valid: %s' % [ err for err in result.error_log ] else: msg = PV_ERR except XMLSyntaxError as e: if settings.DEBUG is True: msg = 'Request was not well-formed XML: %s' % str(e) else: msg = PV_ERR if msg is not None: raise StatusMessageException('0', ST_BAD_MESSAGE, msg) try: taxii_message = parse_tuple.parser(request.body) except tm11.UnsupportedQueryException as e: # TODO: Is it possible to give the real message id? # TODO: Is it possible to indicate which query aspects are supported? # This might require a change in how libtaxii works raise StatusMessageException('0', ST_UNSUPPORTED_QUERY) service = handlers.get_service_from_path(request.path) handler = service.get_message_handler(taxii_message) module_name, class_name = handler.handler.rsplit('.', 1) try: module = import_module(module_name) handler_class = getattr(module, class_name) except Exception as e: type, value, tb = sys.exc_info() raise type, ("Error importing handler: %s" % handler.handler, type, value), tb handler_class.validate_headers(request, taxii_message.message_id) handler_class.validate_message_is_supported(taxii_message) try: response_message = handler_class.handle_message( service, taxii_message, request) except StatusMessageException: raise # The handler_class has intentionally raised this except Exception as e: # Something else happened msg = "There was a failure while executing the message handler" if settings.DEBUG: # Add the stacktrace msg += "\r\n" + traceback.format_exc() raise StatusMessageException(taxii_message.message_id, ST_FAILURE, msg) try: response_message.message_type except AttributeError as e: msg = "The message handler (%s) did not return a TAXII Message!" % handler_class if settings.DEBUG: msg += ("\r\n The returned value was: %s (class=%s)" % (response_message, response_message.__class__.__name__)) raise StatusMessageException(taxii_message.message_id, ST_FAILURE, msg) if response_message.__module__ == 'libtaxii.messages_11': vid = VID_TAXII_SERVICES_11 elif response_message.__module__ == 'libtaxii.messages_10': vid = VID_TAXII_SERVICES_10 else: raise ValueError("Unknown response message module") response_headers = handlers.get_headers(vid, request.is_secure()) return handlers.HttpResponseTaxii( response_message.to_xml(pretty_print=True), response_headers)
def validate_headers(cls, django_request, in_response_to='0'): """ Validates the headers of a django request based on the properties of this MessageHandler. Specifically, the supported_request_messages property is used to infer which version(s) of TAXII this message handler supports and from there infer which headers are valid/invalid. Arguments: django_request - The Django request to validate in_response_to - If a StatusMessageException is raised as a result \ of header validation (e.g., the headers are invalid), \ in_response_to will be used as the in_response_to \ field of the Status Message. Returns: None if all headers are valid. Raises a StatusMessageException otherwise. """ # First, make sure required headers exist svcs = django_request.META.get('HTTP_X_TAXII_SERVICES', None) if not svcs: msg = "The X-TAXII-Services header was not specified" if settings.DEBUG: msg += "\r\nHeaders: %s " % str(django_request.META) raise StatusMessageException(in_response_to, ST_FAILURE, msg) ct = django_request.META.get('CONTENT_TYPE', None) if not ct: raise StatusMessageException(in_response_to, ST_FAILURE, "Content-Type header was not specified") xtct = django_request.META.get('HTTP_X_TAXII_CONTENT_TYPE', None) if not xtct: raise StatusMessageException(in_response_to, ST_FAILURE, "The X-TAXII-Content-Type header was not specified") xtp = django_request.META.get('HTTP_X_TAXII_PROTOCOL', None) if not xtp: raise StatusMessageException(in_response_to, ST_FAILURE, "The X-TAXII-Protocol header was not specified") # These headers are optional accept = django_request.META.get('HTTP_ACCEPT', None) xta = django_request.META.get('HTTP_X_TAXII_ACCEPT', None) # for k, v in django_request.META.iteritems(): # print '%s: %s' % (k, v) # Identify which TAXII versions the message handler supports supports_taxii_11 = False supports_taxii_10 = False for message in cls.get_supported_request_messages(): if message.__module__ == 'libtaxii.messages_11': supports_taxii_11 = True elif message.__module__ == 'libtaxii.messages_10': supports_taxii_10 = True else: raise ValueError(("The variable \'supported_request_messages\' " "contained a non-libtaxii message module: %s") % message.__module__) # Next, determine whether the MessageHandler supports the headers # Validate the X-TAXII-Services header if svcs not in (VID_TAXII_SERVICES_11, VID_TAXII_SERVICES_10): raise StatusMessageException(in_response_to, ST_FAILURE, "The value of X-TAXII-Services was not recognized.") if ((svcs == VID_TAXII_SERVICES_11 and not supports_taxii_11) or (svcs == VID_TAXII_SERVICES_10 and not supports_taxii_10)): raise StatusMessageException(in_response_to, ST_FAILURE, ("The specified value of X-TAXII-Services (%s) " "is not supported by this TAXII Service.") % svcs) # Validate the Content-Type header if ct.lower() != 'application/xml': raise StatusMessageException(in_response_to, ST_FAILURE, "The specified value of Content-Type is not supported.") # Validate the X-TAXII-Content-Type header if xtct not in (VID_TAXII_XML_11, VID_TAXII_XML_10): raise StatusMessageException(in_response_to, ST_FAILURE, "The value of X-TAXII-Content-Type was not recognized.") if ((xtct == VID_TAXII_XML_11 and not supports_taxii_11) or (xtct == VID_TAXII_XML_10 and not supports_taxii_10)): raise StatusMessageException(in_response_to, ST_FAILURE, "The specified value of X-TAXII-Content-Type is not supported") # Validate the X-TAXII-Protocol header # TODO: Look into the service properties instead of assuming both are supported if xtp not in (VID_TAXII_HTTP_10, VID_TAXII_HTTPS_10): raise StatusMessageException(in_response_to, ST_FAILURE, "The specified value of X-TAXII-Protocol is not supported") # Validate the accept header if accept and accept.lower() != 'application/xml': raise StatusMessageException(in_response_to, ST_FAILURE, "The specified value of Accept is not supported") # Validate the X-TAXII-Accept header # TODO: Accept more "complex" accept headers (e.g., ones that specify more # than one value) if xta not in (VID_TAXII_XML_11, VID_TAXII_XML_10, None): raise StatusMessageException(in_response_to, ST_FAILURE, "The specified value of X-TAXII-Accept is not recognized") if not xta: # X-TAXII-Accept not specified, we can pick whatever we want xta = VID_TAXII_XML_11 if ((xta == VID_TAXII_XML_11 and not supports_taxii_11) or (xta == VID_TAXII_XML_10 and not supports_taxii_10)): raise StatusMessageException(in_response_to, ST_FAILURE, "The specified value of X-TAXII-Accept is not supported") # Headers are valid return