def testApprovalExpiry(self): """Tests that approvals expire after the correct time.""" client_id = "C.%016X" % 0 urn = rdfvalue.ClientURN(client_id).Add("/fs/os/c") token = access_control.ACLToken(username="******", reason="For testing") self.assertRaises(access_control.UnauthorizedAccess, aff4.FACTORY.Open, urn, None, "rw", token) with test_lib.FakeTime(100.0, increment=1e-3): self.GrantClientApproval(client_id, token) # This should work now. aff4.FACTORY.Open(urn, mode="rw", token=token) # 3 weeks later. with test_lib.FakeTime(100.0 + 3 * 7 * 24 * 60 * 60): # This should still work. aff4.FACTORY.Open(urn, mode="rw", token=token) # Getting close. with test_lib.FakeTime(100.0 + 4 * 7 * 24 * 60 * 60 - 100.0): # This should still work. aff4.FACTORY.Open(urn, mode="rw", token=token) # Over 4 weeks now. with test_lib.FakeTime(100.0 + 4 * 7 * 24 * 60 * 60 + 100.0): self.assertRaises(access_control.UnauthorizedAccess, aff4.FACTORY.Open, urn, None, "rw", token)
def _RunRegistryFinder(self, paths=None): client_mock = action_mocks.ActionMock("Find", "TransferBuffer", "HashBuffer", "FingerprintFile", "FingerprintFile", "Grep", "StatFile") output_path = "analysis/file_finder" client_id = rdfvalue.ClientURN("C.0000000000000001") aff4.FACTORY.Delete(client_id.Add(output_path), token=self.token) for _ in test_lib.TestFlowHelper("RegistryFinder", client_mock, client_id=client_id, keys_paths=paths, conditions=[], token=self.token, output=output_path): pass try: return list( aff4.FACTORY.Open(client_id.Add(output_path), aff4_type="RDFValueCollection", token=self.token)) except aff4.InstantiationError: return []
def testAuditEntryIsCreatedForEveryClient(self): client_ids = self.SetupClients(3) flow.GRRFlow.StartFlow(flow_name="ApplyLabelsToClientsFlow", clients=client_ids, labels=["drei", "ein", "zwei"], token=self.token) mock_worker = test_lib.MockWorker(token=self.token) mock_worker.Simulate() fd = aff4.FACTORY.Open("aff4:/audit/log", token=self.token) for client_id in client_ids: found_event = None for event in fd: if (event.action == rdfvalue.AuditEvent.Action.CLIENT_ADD_LABEL and event.client == rdfvalue.ClientURN(client_id)): found_event = event break self.assertFalse(found_event is None) self.assertEqual(found_event.flow_name, "ApplyLabelsToClientsFlow") self.assertEqual(found_event.user, self.token.username) self.assertEqual(found_event.description, "test.drei,test.ein,test.zwei")
def DeQueueClientRequest(self, client_id, task_id): """Remove the message from the client queue that this request forms.""" # Check this request was actually bound for a client. if client_id: client_id = rdfvalue.ClientURN(client_id) self.client_messages_to_delete.setdefault(client_id, []).append(task_id)
def ApprovalRevokeRaw(aff4_path, token, remove_from_cache=False): """Revokes an approval for a given token. This method requires raw datastore access to manipulate approvals directly. Args: aff4_path: The aff4_path or client id the approval should be created for. token: The token that should be revoked. remove_from_cache: If True, also remove the approval from the security_manager cache. """ try: urn = rdfvalue.ClientURN(aff4_path) except type_info.TypeValueError: urn = rdfvalue.RDFURN(aff4_path) approval_urn = aff4.ROOT_URN.Add("ACL").Add(urn.Path()).Add( token.username).Add(utils.EncodeReasonString(token.reason)) super_token = access_control.ACLToken(username="******") super_token.supervisor = True approval_request = aff4.FACTORY.Open(approval_urn, mode="rw", token=super_token) approval_request.DeleteAttribute(approval_request.Schema.APPROVER) approval_request.Close() if remove_from_cache: data_store.DB.security_manager.acl_cache.ExpireObject( utils.SmartUnicode(approval_urn))
def testSimpleAccess(self): """Tests that simple access requires a token.""" client_urn = rdfvalue.ClientURN("C.%016X" % 0) # These should raise for a lack of token for urn, mode in [("aff4:/ACL", "r"), ("aff4:/config/drivers", "r"), ("aff4:/", "rw"), (client_urn, "r")]: self.assertRaises(access_control.UnauthorizedAccess, aff4.FACTORY.Open, urn, mode=mode) # These should raise for trying to get write access. for urn, mode in [("aff4:/ACL", "rw"), (client_urn, "rw")]: fd = aff4.FACTORY.Open(urn, mode=mode, token=self.token) # Force cache flush. fd._dirty = True self.assertRaises(access_control.UnauthorizedAccess, fd.Close) # These should raise for access without a token: for urn, mode in [(client_urn.Add("flows").Add("W:1234"), "r"), (client_urn.Add("/fs"), "r")]: self.assertRaises(access_control.UnauthorizedAccess, aff4.FACTORY.Open, urn, mode=mode) # Even if a token is provided - it is not authorized. self.assertRaises(access_control.UnauthorizedAccess, aff4.FACTORY.Open, urn, mode=mode, token=self.token)
def ApprovalCreateRaw(client_id, token, approval_type="ClientApproval"): """Creates an approval for a given token. This method doesn't work through the Gatekeeper for obvious reasons. To use it, the console has to use raw datastore access. Args: client_id: The client id the approval should be created for. token: The token that will be used later for access. approval_type: The type of the approval to create. Raises: RuntimeError: On bad token. """ client_id = rdfvalue.ClientURN(client_id) if not token.reason: raise RuntimeError("Cannot create approval with empty reason") if not token.username: token.username = getpass.getuser() approval_urn = flow.GRRFlow.RequestApprovalWithReasonFlow.ApprovalUrnBuilder( client_id.Path(), token.username, token.reason) super_token = access_control.ACLToken(username="******") super_token.supervisor = True approval_request = aff4.FACTORY.Create(approval_urn, approval_type, mode="rw", token=super_token) # Add approvals indicating they were approved by fake "raw" mode users. approval_request.AddAttribute( approval_request.Schema.APPROVER("%s1-raw" % token.username)) approval_request.AddAttribute( approval_request.Schema.APPROVER("%s-raw2" % token.username)) approval_request.Close(sync=True)
def StoreMRUs(self, responses): """Store the MRU data for each user in a special structure.""" for response in responses: urn = aff4.AFF4Object.VFSGRRClient.PathspecToURN( response.pathspec, self.client_id) if stat.S_ISDIR(response.st_mode): obj_type = "VFSDirectory" else: obj_type = "VFSFile" fd = aff4.FACTORY.Create(urn, obj_type, mode="w", token=self.token) fd.Set(fd.Schema.STAT(response)) fd.Close(sync=False) username = responses.request_data["username"] m = re.search("/([^/]+)/\\d+$", unicode(urn)) if m: extension = m.group(1) fd = aff4.FACTORY.Create( rdfvalue.ClientURN(self.client_id) .Add("analysis/MRU/Explorer") .Add(extension) .Add(username), "MRUCollection", token=self.token, mode="rw") # TODO(user): Implement the actual parsing of the MRU. mrus = fd.Get(fd.Schema.LAST_USED_FOLDER) mrus.Append(filename="Foo") fd.Set(mrus) fd.Close()
def testApprovalExpiry(self): """Tests that approvals expire after the correct time.""" client_id = "C.%016X" % 0 urn = rdfvalue.ClientURN(client_id).Add("/fs/os/c") token = access_control.ACLToken(username="******", reason="For testing") self.assertRaises(access_control.UnauthorizedAccess, aff4.FACTORY.Open, urn, None, "rw", token) with test_lib.FakeTime(100.0, increment=1e-3): self.GrantClientApproval(client_id, token) # This should work now. aff4.FACTORY.Open(urn, mode="rw", token=token) token_expiry = config_lib.CONFIG["ACL.token_expiry"] # This is close to expiry but should still work. with test_lib.FakeTime(100.0 + token_expiry - 100.0): aff4.FACTORY.Open(urn, mode="rw", token=token) # Past expiry, should fail. with test_lib.FakeTime(100.0 + token_expiry + 100.0): self.assertRaises(access_control.UnauthorizedAccess, aff4.FACTORY.Open, urn, None, "rw", token)
def BuildTable(self, start_row, end_row, request): client_id = rdfvalue.ClientURN(request.REQ.get("client_id")) now = rdfvalue.RDFDatetime().Now() # Make a local QueueManager. manager = queue_manager.QueueManager(token=request.token) for i, task in enumerate(manager.Query(client_id, limit=end_row)): if i < start_row: continue difference = now - task.eta if difference > 0: self.AddCell( i, "Status", dict(icon="stock_yes", description="Available for Lease")) else: self.AddCell( i, "Status", dict(icon="clock", description="Leased for %s Seconds" % (difference / 1e6))) self.AddCell(i, "ID", task.task_id) self.AddCell(i, "Flow", task.session_id) self.AddCell(i, "Due", rdfvalue.RDFDatetime(task.eta)) self.AddCell(i, "Client Action", task.name)
def ApprovalRevokeRaw(client_id, token, remove_from_cache=False): """Revokes an approval for a given token. This method doesn't work through the Gatekeeper for obvious reasons. To use it, the console has to use raw datastore access. Args: client_id: The client id the approval should be revoked for. token: The token that should be revoked. remove_from_cache: If True, also remove the approval from the security_manager cache. """ client_id = rdfvalue.ClientURN(client_id) approval_urn = aff4.ROOT_URN.Add("ACL").Add(client_id.Path()).Add( token.username).Add(utils.EncodeReasonString(token.reason)) super_token = access_control.ACLToken(username="******") super_token.supervisor = True approval_request = aff4.FACTORY.Open(approval_urn, mode="rw", token=super_token) approval_request.DeleteAttribute(approval_request.Schema.APPROVER) approval_request.Close() if remove_from_cache: data_store.DB.security_manager.acl_cache.ExpireObject( utils.SmartUnicode(approval_urn))
def testClientApproval(self): """Tests that we can create an approval object to access clients.""" client_id = "C.%016X" % 0 urn = rdfvalue.ClientURN(client_id).Add("/fs") token = access_control.ACLToken(username="******", reason="For testing") self.assertRaises(access_control.UnauthorizedAccess, aff4.FACTORY.Open, urn, None, "rw", token=token) self.GrantClientApproval(client_id, token) fd = aff4.FACTORY.Open(urn, None, "rw", token=token) fd.Close() self.RevokeClientApproval(client_id, token) self.assertRaises(access_control.UnauthorizedAccess, aff4.FACTORY.Open, urn, None, "rw", token=token)
def Start(self): """Sign the CSR from the client.""" client = aff4.FACTORY.Create(self.client_id, "VFSGRRClient", token=self.token) if self.args.csr.type != rdfvalue.Certificate.Type.CSR: raise IOError("Must be called with CSR") req = X509.load_request_string(self.args.csr.pem) # Verify the CSR. This is not strictly necessary but doesn't harm either. if req.verify(req.get_pubkey()) != 1: raise flow.FlowError("CSR for client %s did not verify: %s" % (self.client_id, req.as_pem())) # Verify that the CN is of the correct form. The common name should refer # to a client URN. public_key = req.get_pubkey().get_rsa().pub()[1] self.cn = rdfvalue.ClientURN.FromPublicKey(public_key) if self.cn != rdfvalue.ClientURN(req.get_subject().CN): raise IOError("CSR CN %s does not match public key %s." % (rdfvalue.ClientURN(req.get_subject().CN), self.cn)) logging.info("Will sign CSR for: %s", self.cn) cert = self.MakeCert(self.cn, req) # This check is important to ensure that the client id reported in the # source of the enrollment request is the same as the one in the # certificate. We use the ClientURN to ensure this is also of the correct # form for a client name. if self.cn != self.client_id: raise flow.FlowError("Certificate name %s mismatch for client %s", self.cn, self.client_id) # Set and write the certificate to the client record. certificate_attribute = rdfvalue.RDFX509Cert(cert.as_pem()) client.Set(client.Schema.CERT, certificate_attribute) client.Set(client.Schema.FIRST_SEEN, rdfvalue.RDFDatetime().Now()) client.Close(sync=True) # Publish the client enrollment message. self.Publish("ClientEnrollment", certificate_attribute.common_name) self.Log("Enrolled %s successfully", self.client_id)
def GetClientURNFromPath(path): """Extracts the Client id from the path, if it is present.""" # Make sure that the first component of the path looks like a client. try: return rdfvalue.ClientURN(path.split("/")[1]) except (type_info.TypeValueError, IndexError): return None
def Layout(self, request, response): """Produce a summary of the client information.""" client_id = request.REQ.get("hunt_client") if client_id: super(HuntHostInformationRenderer, self).Layout( request, response, client_id=client_id, aff4_path=rdfvalue.ClientURN(client_id), age=aff4.ALL_TIMES)
def testHandleClientMessageRetransmission(self): """Check that requests get retransmitted but only if there is no status.""" # Make a new fake client client_id = self.SetupClients(1)[0] # Test the standard behavior. base_time = 1000 msgs_recvd = [] default_ttl = rdfvalue.GrrMessage().task_ttl with test_lib.FakeTime(base_time): flow.GRRFlow.StartFlow(client_id=client_id, flow_name="SendingFlow", message_count=1, token=self.token) for i in range(default_ttl): with test_lib.FakeTime(base_time + i * (self.message_expiry_time + 1)): tasks = self.server.DrainTaskSchedulerQueueForClient( client_id, 100000, rdfvalue.MessageList()) msgs_recvd.append(tasks) # Should return a client message (ttl-1) times and nothing afterwards. self.assertEqual(map(bool, msgs_recvd), [True] * (rdfvalue.GrrMessage().task_ttl - 1) + [False]) # Now we simulate that the workers are overloaded - the client messages # arrive but do not get processed in time. if default_ttl <= 3: self.fail("TTL too low for this test.") msgs_recvd = [] with test_lib.FakeTime(base_time): flow_id = flow.GRRFlow.StartFlow( client_id=client_id, flow_name="SendingFlow", message_count=1, token=self.token) for i in range(default_ttl): if i == 2: self._ScheduleResponseAndStatus(client_id, flow_id) with test_lib.FakeTime(base_time + i * (self.message_expiry_time + 1)): tasks = self.server.DrainTaskSchedulerQueueForClient( client_id, 100000, rdfvalue.MessageList()) msgs_recvd.append(tasks) if not tasks: # Even if the request has not been leased ttl times yet, # it should be dequeued by now. new_tasks = queue_manager.QueueManager(token=self.token).Query( queue=rdfvalue.ClientURN(client_id).Queue(), limit=1000) self.assertEqual(len(new_tasks), 0) # Should return a client message twice and nothing afterwards. self.assertEqual( map(bool, msgs_recvd), [True] * 2 + [False] * (rdfvalue.GrrMessage().task_ttl - 2))
def RenderAjax(self, request, response): """Run the flow for granting access.""" approval_urn = rdfvalue.RDFURN(request.REQ.get("acl", "/")) _, namespace, _ = approval_urn.Split(3) if namespace == "hunts": try: _, _, hunt_id, user, reason = approval_urn.Split() self.subject = rdfvalue.RDFURN(namespace).Add(hunt_id) self.user = user self.reason = utils.DecodeReasonString(reason) except (ValueError, TypeError): raise access_control.UnauthorizedAccess( "Approval object is not well formed.") flow.GRRFlow.StartFlow(flow_name="GrantHuntApprovalFlow", subject_urn=self.subject, reason=self.reason, delegate=self.user, token=request.token) elif namespace == "cron": try: _, _, cron_job_name, user, reason = approval_urn.Split() self.subject = rdfvalue.RDFURN(namespace).Add(cron_job_name) self.user = user self.reason = utils.DecodeReasonString(reason) except (ValueError, TypeError): raise access_control.UnauthorizedAccess( "Approval object is not well formed.") flow.GRRFlow.StartFlow(flow_name="GrantCronJobApprovalFlow", subject_urn=self.subject, reason=self.reason, delegate=self.user, token=request.token) elif aff4.AFF4Object.VFSGRRClient.CLIENT_ID_RE.match(namespace): try: _, client_id, user, reason = approval_urn.Split() self.subject = client_id self.user = user self.reason = utils.DecodeReasonString(reason) except (ValueError, TypeError): raise access_control.UnauthorizedAccess( "Approval object is not well formed.") flow.GRRFlow.StartFlow(client_id=client_id, flow_name="GrantClientApprovalFlow", reason=self.reason, delegate=self.user, subject_urn=rdfvalue.ClientURN( self.subject), token=request.token) else: raise access_control.UnauthorizedAccess( "Approval object is not well formed.") return renderers.TemplateRenderer.Layout( self, request, response, apply_template=self.ajax_template)
def GetAllClients(token=None): """Return a list of all client urns.""" results = [] for urn in aff4.FACTORY.Open(aff4.ROOT_URN, token=token).ListChildren(): try: results.append(rdfvalue.ClientURN(urn)) except type_info.TypeValueError: pass return results
def testSupervisorToken(self): """Tests that the supervisor token overrides the approvals.""" urn = rdfvalue.ClientURN("C.%016X" % 0).Add("/fs/os/c") self.assertRaises(access_control.UnauthorizedAccess, aff4.FACTORY.Open, urn) super_token = access_control.ACLToken(username="******") super_token.supervisor = True aff4.FACTORY.Open(urn, mode="rw", token=super_token)
def CreateLeasedClientRequest( client_id=rdfvalue.ClientURN("C.0000000000000001"), token=None): flow.GRRFlow.StartFlow(client_id=client_id, flow_name="ListProcesses", token=token) with queue_manager.QueueManager(token=token) as manager: manager.QueryAndOwn(client_id.Queue(), limit=1, lease_seconds=10000)
def Layout(self, request, response): acl = request.REQ.get("acl", "") _, client_id, _ = rdfvalue.RDFURN(acl).Split(3) # We skip the direct super class to avoid the access control check. super(fileview.HostInformation, self).Layout(request, response, client_id=client_id, aff4_path=rdfvalue.ClientURN(client_id))
def StoreResults(self, responses): """Stores the responses.""" client_id = responses.request.client_id if responses.success: self.LogResult(client_id, "Got process listing.", rdfvalue.ClientURN(client_id).Add("processes")) else: self.LogClientError(client_id, log_message=responses.status) self.MarkClientDone(client_id)
def ApprovalCreateRaw(aff4_path, reason="", expire_in=60 * 60 * 24 * 7, token=None, approval_type="ClientApproval"): """Creates an approval with raw access. This method requires raw datastore access to manipulate approvals directly. This currently doesn't work for hunt or cron approvals, because they check that each approver has the admin label. Since the fake users don't exist the check fails. Args: aff4_path: The aff4_path or client id the approval should be created for. reason: The reason to put in the token. expire_in: Expiry in seconds to use in the token. token: The token that will be used. If this is specified reason and expiry are ignored. approval_type: The type of the approval to create. Returns: The token. Raises: RuntimeError: On bad token. """ if approval_type == "ClientApproval": urn = rdfvalue.ClientURN(aff4_path) else: urn = rdfvalue.RDFURN(aff4_path) if not token: expiry = time.time() + expire_in token = rdfvalue.ACLToken(reason=reason, expiry=expiry) if not token.reason: raise RuntimeError("Cannot create approval with empty reason") if not token.username: token.username = getpass.getuser() approval_urn = flow.GRRFlow.RequestApprovalWithReasonFlow.ApprovalUrnBuilder( urn.Path(), token.username, token.reason) super_token = access_control.ACLToken(username="******") super_token.supervisor = True approval_request = aff4.FACTORY.Create(approval_urn, approval_type, mode="rw", token=super_token) # Add approvals indicating they were approved by fake "raw" mode users. approval_request.AddAttribute( approval_request.Schema.APPROVER("%s1-raw" % token.username)) approval_request.AddAttribute( approval_request.Schema.APPROVER("%s-raw2" % token.username)) approval_request.Close(sync=True)
def testNoClientActionIsDisplayedWhenFlowIsStarted(self): with self.ACLChecksDisabled(): self.GrantClientApproval("C.0000000000000001") self.Open("/#c=C.0000000000000001&main=ClientLoadView") self.WaitUntil(self.IsTextPresent, "No actions currently in progress.") flow.GRRFlow.StartFlow( client_id=rdfvalue.ClientURN("C.0000000000000001"), flow_name="ListProcesses", token=self.token)
def _ParseRSAKey(self, rsa): """Use the RSA private key to initialize our parameters. We set our client name as the hash of the RSA private key. Args: rsa: An RSA key pair. """ # Our CN will be the first 64 bits of the hash of the public key. public_key = rsa.pub()[1] self.common_name = rdfvalue.ClientURN( "C.%s" % (hashlib.sha256(public_key).digest()[:8].encode("hex")))
def StoreResults(self, responses): """Stores the responses.""" client_id = responses.request.client_id if responses.success: self.LogResult( client_id, "Downloaded RunKeys", rdfvalue.ClientURN(client_id).Add("analysis/RunKeys")) else: self.LogClientError(client_id, log_message=utils.SmartStr(responses.status)) self.MarkClientDone(client_id)
def UserHasClientApproval(self, subject, token): """Checks if read access for this client is allowed using the given token. Args: subject: Subject below the client level which triggered the check. token: The token to check with. Returns: True if the access is allowed. Raises: UnauthorizedAccess: if the access is rejected. """ client_id, _ = rdfvalue.RDFURN(subject).Split(2) client_urn = rdfvalue.ClientURN(client_id) logging.debug("Checking client approval for %s, %s", client_urn, token) if not token.reason: raise access_control.UnauthorizedAccess( "Must specify a reason for access.", subject=client_urn) # Build the approval URN. approval_urn = aff4.ROOT_URN.Add("ACL").Add(client_urn.Path()).Add( token.username).Add(utils.EncodeReasonString(token.reason)) try: token.is_emergency = self.acl_cache.Get(approval_urn) return True except KeyError: try: # Retrieve the approval object with superuser privileges so we can check # it. approval_request = aff4.FACTORY.Open(approval_urn, aff4_type="Approval", mode="r", token=self.super_token, age=aff4.ALL_TIMES) if approval_request.CheckAccess(token): # Cache this approval for fast path checking. self.acl_cache.Put(approval_urn, token.is_emergency) return True raise access_control.UnauthorizedAccess( "Approval %s was rejected." % approval_urn, subject=client_urn) except IOError: # No Approval found, reject this request. raise access_control.UnauthorizedAccess( "No approval found for client %s." % client_urn, subject=client_urn)
def __init__(self, client_id=None, platform=None, local_worker=False, token=None, local_client=True): # If we get passed a string, turn it into a urn. self.client_id = rdfvalue.ClientURN(client_id) self.platform = platform self.token = token self.local_worker = local_worker self.local_client = local_client super(ClientTestBase, self).__init__(methodName="runTest")
def testUpdateButton(self): self.Open("/") self.Type("client_query", "0001") self.Click("client_query_submit") self.WaitUntilEqual(u"C.0000000000000001", self.GetText, "css=span[type=subject]") # Choose client 1 self.Click("css=td:contains('0001')") # Go to Browse VFS self.Click("css=a:contains('Browse Virtual Filesystem')") self.Click("css=#_fs ins.jstree-icon") self.Click("css=#_fs-os ins.jstree-icon") self.Click("link=c") # Ensure that refresh button is enabled self.WaitUntilNot(self.IsElementPresent, "css=button[id^=refresh][disabled]") # Grab the root directory again - should produce an Interrogate flow. self.Click("css=button[id^=refresh]") # Check that the button got disabled self.WaitUntil(self.IsElementPresent, "css=button[id^=refresh][disabled]") # Get the flows that should have been started and finish them. with self.ACLChecksDisabled(): client_id = rdfvalue.ClientURN("C.0000000000000001") fd = aff4.FACTORY.Open(client_id.Add("flows"), token=self.token) flows = list(fd.ListChildren()) client_mock = action_mocks.ActionMock() for flow_urn in flows: for _ in test_lib.TestFlowHelper(flow_urn, client_mock, client_id=client_id, token=self.token, check_flow_errors=False): pass # Ensure that refresh button is enabled again. # # TODO(user): ideally, we should also check that something got # updated, not only that button got enabled back. self.WaitUntilNot(self.IsElementPresent, "css=button[id^=refresh][disabled]")
def testGrrMessageConverterWithOneMissingClient(self): msg1 = rdfvalue.GrrMessage(payload=DummyRDFValue4("some")) msg1.source = rdfvalue.ClientURN("C.0000000000000000") test_lib.ClientFixture(msg1.source, token=self.token) msg2 = rdfvalue.GrrMessage(payload=DummyRDFValue4("some2")) msg2.source = rdfvalue.ClientURN("C.0000000000000001") metadata1 = rdfvalue.ExportedMetadata( timestamp=rdfvalue.RDFDatetime().FromSecondsFromEpoch(1), source_urn=rdfvalue.RDFURN("aff4:/hunts/W:000000/Results")) metadata2 = rdfvalue.ExportedMetadata( timestamp=rdfvalue.RDFDatetime().FromSecondsFromEpoch(2), source_urn=rdfvalue.RDFURN("aff4:/hunts/W:000001/Results")) converter = export.GrrMessageConverter() results = list(converter.BatchConvert( [(metadata1, msg1), (metadata2, msg2)], token=self.token)) self.assertEqual(len(results), 1) self.assertEqual(results[0].timestamp, rdfvalue.RDFDatetime().FromSecondsFromEpoch(1)) self.assertEqual(results[0].source_urn, "aff4:/hunts/W:000000/Results")