def Start(self): """Calls the Update() method of a given VFSFile/VFSDirectory object.""" self.Init() client_id = rdf_client.ClientURN(self.args.vfs_file_urn.Split()[0]) data_store.DB.security_manager.CheckClientAccess(self.token.RealUID(), client_id) fd = aff4.FACTORY.Open(self.args.vfs_file_urn, mode="rw", token=self.token) # Account for implicit directories. if fd.Get(fd.Schema.TYPE) is None: fd = fd.Upgrade("VFSDirectory") self.state.get_file_flow_urn = fd.Update( attribute=self.args.attribute, priority=rdf_flows.GrrMessage.Priority.HIGH_PRIORITY)
def testRecursiveRefreshButtonGetsReenabledWhenUpdateEnds(self): self.Open("/#/clients/C.0000000000000001/vfs/fs/os/c/") self.Click("css=button[name=RecursiveRefresh]:not([disabled])") self.Click("css=button[name=Proceed]") # The message should come and go (and the dialog should close itself). self.WaitUntil(self.IsTextPresent, "Refresh started successfully!") self.WaitUntilNot(self.IsTextPresent, "Refresh started successfully!") self.WaitUntil(self.IsElementPresent, "css=button[name=RecursiveRefresh][disabled]") client_id = rdf_client.ClientURN("C.0000000000000001") self._RunUpdateFlow(client_id) # Check that the button got enabled again. self.WaitUntil(self.IsElementPresent, "css=button[name=RecursiveRefresh]:not([disabled])")
def testTreeAndFileListRefreshedWhenRecursiveRefreshCompletes(self): self.Open("/#/clients/C.0000000000000001/vfs/fs/os/c/") self.Click("css=button[name=RecursiveRefresh]:not([disabled])") self.Click("css=button[name=Proceed]") # The message should come and go (and the dialog should close itself). self.WaitUntil(self.IsTextPresent, "Refresh started successfully!") self.WaitUntilNot(self.IsTextPresent, "Refresh started successfully!") client_id = rdf_client.ClientURN("C.0000000000000001") self._RunUpdateFlow(client_id) # The flow should be finished now, and file/tree lists update should # be triggered. # Ensure that the tree got updated as well as files list. self.WaitUntil(self.IsElementPresent, "css=tr:contains('TestFolder')") self.WaitUntil(self.IsElementPresent, "css=#_fs-os-c-TestFolder i.jstree-icon")
def testResultsAreDisplayedInResultsTab(self): client_id = rdf_client.ClientURN("C.0000000000000001") with self.ACLChecksDisabled(): for _ in test_lib.TestFlowHelper("FlowWithOneStatEntryResult", action_mocks.ActionMock(), client_id=client_id, token=self.token): pass self.GrantClientApproval(client_id) self.Open("/#c=C.0000000000000001") self.Click("css=a:contains('Manage launched flows')") self.Click("css=td:contains('FlowWithOneStatEntryResult')") self.Click("css=#Results") self.WaitUntil(self.IsTextPresent, "aff4:/some/unique/path")
def OnDelete(self, deletion_pool=None): super(GRRHunt, self).OnDelete(deletion_pool=deletion_pool) # Delete all the symlinks in the clients namespace that point to the flows # initiated by this hunt. children_urns = deletion_pool.ListChildren(self.urn) clients_ids = [] for urn in children_urns: try: clients_ids.append(rdf_client.ClientURN(urn.Basename())) except type_info.TypeValueError: # Ignore children that are not valid clients ids. continue symlinks_urns = [ self._ClientSymlinkUrn(client_id) for client_id in clients_ids ] deletion_pool.MultiMarkForDeletion(symlinks_urns)
def ReadAllAuditEvents(self, cursor=None): cursor.execute(""" SELECT username, urn, client_id, timestamp, details FROM audit_event ORDER BY timestamp """) result = [] for username, urn, client_id, timestamp, details in cursor.fetchall(): event = rdf_events.AuditEvent.FromSerializedString(details) event.user = username if urn: event.urn = rdfvalue.RDFURN(urn) if client_id is not None: event.client = rdf_client.ClientURN(_IntToClientID(client_id)) event.timestamp = _MysqlToRDFDatetime(timestamp) result.append(event) return result
def testTreeAndFileListRefreshedWhenRecursiveRefreshCompletes(self): self.Open("/#/clients/C.0000000000000001/vfs/fs/os/c/") self.Click("css=button[name=RecursiveRefresh]:not([disabled])") self.Click("css=button[name=Proceed]") # Wait until the dialog is automatically closed. self.WaitUntilNot( self.IsElementPresent, "css=.modal-header:contains('Recursive Directory Refresh')") client_id = rdf_client.ClientURN("C.0000000000000001") self._RunUpdateFlow(client_id) # The flow should be finished now, and file/tree lists update should # be triggered. # Ensure that the tree got updated as well as files list. self.WaitUntil(self.IsElementPresent, "css=tr:contains('TestFolder')") self.WaitUntil(self.IsElementPresent, "css=#_fs-os-c-TestFolder i.jstree-icon")
def __init__(self, client_id, client_mock, token=None): if not isinstance(client_id, rdf_client.ClientURN): client_id = rdf_client.ClientURN(client_id) if client_mock is None: client_mock = action_mocks.InvalidActionMock() self.status_message_enforced = getattr(client_mock, "STATUS_MESSAGE_ENFORCED", True) self._mock_task_queue = getattr(client_mock, "mock_task_queue", []) self.client_id = client_id self.client_mock = client_mock self.token = token # Well known flows are run on the front end. self.well_known_flows = flow.WellKnownFlow.GetAllWellKnownFlows(token=token) self.user_cpu_usage = [] self.system_cpu_usage = [] self.network_usage = []
def testRecursiveRefreshButtonGetsReenabledWhenUpdateEnds(self): self.Open("/#/clients/C.0000000000000001/vfs/fs/os/c/") self.Click("css=button[name=RecursiveRefresh]:not([disabled])") self.Click("css=button[name=Proceed]") # Wait until the dialog is automatically closed. self.WaitUntilNot( self.IsElementPresent, "css=.modal-header:contains('Recursive Directory Refresh')") self.WaitUntil(self.IsElementPresent, "css=button[name=RecursiveRefresh][disabled]") client_id = rdf_client.ClientURN("C.0000000000000001") self._RunUpdateFlow(client_id) # Check that the button got enabled again. self.WaitUntil(self.IsElementPresent, "css=button[name=RecursiveRefresh]:not([disabled])")
def SetUpCrashedFlowInHunt(self): client_ids = [rdf_client.ClientURN("C.%016X" % i) for i in range(0, 10)] client_mocks = dict([(client_id, test_lib.CrashClientMock( client_id, self.token)) for client_id in client_ids]) with hunts.GRRHunt.StartHunt(hunt_name="SampleHunt", regex_rules=[rdf_foreman.ForemanAttributeRegex( attribute_name="GRR client", attribute_regex="GRR")], client_rate=0, token=self.token) as hunt: hunt.Run() foreman = aff4.FACTORY.Open("aff4:/foreman", mode="rw", token=self.token) for client_id in client_ids: foreman.AssignTasksToClient(client_id) test_lib.TestHuntHelperWithMultipleMocks(client_mocks, False, self.token) return client_ids
def testClientApproval(self): """Tests that we can create an approval object to access clients.""" client_id = "C.%016X" % 0 urn = rdf_client.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 testTreeAndFileListRefreshedWhenUpdateCompletesWhenSelectionChanged(self): self.Open("/#/clients/C.0000000000000001/vfs/fs/os/c/") self.Click("css=button[id=refresh-dir]:not([disabled])") # Change the selection while the update is in progress. self.WaitUntil(self.IsElementPresent, "css=button[id=refresh-dir][disabled]") self.Click("css=#_fs-os-c-bin a") client_id = rdf_client.ClientURN("C.0000000000000001") self._RunUpdateFlow(client_id) # The flow should be finished now, and directory tree update should # be triggered, even though the selection has changed during the update. # # Ensure that the tree got updated as well as files list. self.WaitUntil(self.IsElementPresent, "css=#_fs-os-c-TestFolder i.jstree-icon")
def testRealPathspec(self): client_id = rdf_client.ClientURN("C.%016X" % 1234) for path in ["a/b", "a/b/c/d"]: d = aff4.FACTORY.Create( client_id.Add("fs/os").Add(path), aff4_type=aff4_standard.VFSDirectory, token=self.token) pathspec = rdf_paths.PathSpec( path=path, pathtype=rdf_paths.PathSpec.PathType.OS) d.Set(d.Schema.PATHSPEC, pathspec) d.Close() d = aff4.FACTORY.Create( client_id.Add("fs/os").Add("a/b/c"), aff4_type=aff4_standard.VFSDirectory, mode="rw", token=self.token) self.assertEqual(d.real_pathspec.CollapsePath(), "a/b/c")
def GetClientTestTargets(client_ids=None, hostnames=None, token=None, checkin_duration_threshold="20m"): """Get client urns for end-to-end tests. Args: client_ids: list of client id URN strings or rdf_client.ClientURNs hostnames: list of hostnames to search for token: access token checkin_duration_threshold: clients that haven't checked in for this long will be excluded Returns: client_id_set: set of rdf_client.ClientURNs available for end-to-end tests. """ if client_ids: client_ids = set(client_ids) else: client_ids = set(config_lib.CONFIG.Get("Test.end_to_end_client_ids")) if hostnames: hosts = set(hostnames) else: hosts = set(config_lib.CONFIG.Get("Test.end_to_end_client_hostnames")) if hosts: client_id_dict = client_index.GetClientURNsForHostnames(hosts, token=token) for client_list in client_id_dict.values(): client_ids.update(client_list) client_id_set = set([rdf_client.ClientURN(x) for x in client_ids]) duration_threshold = rdfvalue.Duration(checkin_duration_threshold) for client in aff4.FACTORY.MultiOpen(client_id_set, token=token): # Only test against client IDs that have checked in recently. Test machines # tend to have lots of old client IDs hanging around that will cause lots of # waiting for timeouts in the tests. if (rdfvalue.RDFDatetime().Now() - client.Get(client.Schema.LAST) > duration_threshold): client_id_set.remove(client.urn) return client_id_set
def BuildTable(self, start_row, end_row, request): """Builds the table.""" client_id = rdf_client.ClientURN(request.REQ.get("client_id")) task_id = "task:%s" % request.REQ.get("task_id", "") # Make a local QueueManager. manager = queue_manager.QueueManager(token=request.token) # This is the request. request_messages = manager.Query(client_id, task_id=task_id) if not request_messages: return request_message = request_messages[0] state_queue = request_message.session_id.Add( "state/request:%08X" % request_message.request_id) predicate_re = (manager.FLOW_RESPONSE_PREFIX % request_message.request_id) + ".*" # Get all the responses for this request. for i, (predicate, serialized_message, _) in enumerate( data_store.DB.ResolveRegex(state_queue, predicate_re, limit=end_row, token=request.token)): message = rdf_flows.GrrMessage(serialized_message) if i < start_row: continue if i > end_row: break # Tie up the request to each response to make it easier to render. rdf_response_message = rdf_flows.GrrMessage(message) rdf_response_message.request = request_message self.AddCell(i, "Task ID", predicate) self.AddCell(i, "Response", rdf_response_message)
def EnrolFleetspeakClient(self, client_id): """Enrols a Fleetspeak-enabled client for use with GRR.""" client_urn = rdf_client.ClientURN(client_id) # If already enrolled, return. if data_store.RelationalDBReadEnabled(): if data_store.REL_DB.ReadClientMetadata(client_id): return else: if aff4.FACTORY.ExistsWithType(client_urn, aff4_type=aff4_grr.VFSGRRClient, token=self.token): return logging.info("Enrolling a new Fleetspeak client: %r", client_id) if data_store.RelationalDBWriteEnabled(): data_store.REL_DB.WriteClientMetadata(client_id, fleetspeak_enabled=True) # TODO(fleetspeak-team,grr-team): If aff4 isn't reliable enough, we can # catch exceptions from it and forward them to Fleetspeak by failing its # gRPC call. Fleetspeak will then retry with a random, perhaps healthier, # instance of the GRR frontend. with aff4.FACTORY.Create(client_urn, aff4_type=aff4_grr.VFSGRRClient, mode="rw", token=self.token) as client: client.Set(client.Schema.FLEETSPEAK_ENABLED, rdfvalue.RDFBool(True)) index = client_index.CreateClientIndex(token=self.token) index.AddClient(client) if data_store.RelationalDBWriteEnabled(): index = client_index.ClientIndex() index.AddClient(data_migration.ConvertVFSGRRClient(client)) # Publish the client enrollment message. events.Events.PublishEvent("ClientEnrollment", client_urn, token=self.token)
def EnrolFleetspeakClient(self, client_id): """Enrols a Fleetspeak-enabled client for use with GRR.""" client_urn = rdf_client.ClientURN(client_id) # If already enrolled, return. if aff4.FACTORY.ExistsWithType(client_urn, aff4_type=aff4_grr.VFSGRRClient, token=self.token): return logging.info("Enrolling a new Fleetspeak client: %r", client_id) # TODO(fleetspeak-team,grr-team): If aff4 isn't reliable enough, we can # catch exceptions from it and forward them to Fleetspeak by failing its # gRPC call. Fleetspeak will then retry with a random, perhaps healthier, # instance of the GRR frontend. with aff4.FACTORY.Create(client_urn, aff4_type=aff4_grr.VFSGRRClient, mode="rw", token=self.token) as client: client.Set(client.Schema.FLEETSPEAK_ENABLED, rdfvalue.RDFBool(True)) index = client_index.CreateClientIndex(token=self.token) index.AddClient(client) enrollment_session_id = rdfvalue.SessionID(queue=queues.ENROLLMENT, flow_name="Enrol") publish_msg = rdf_flows.GrrMessage( payload=client_urn, session_id=enrollment_session_id, # Fleetspeak ensures authentication. auth_state=rdf_flows.GrrMessage.AuthorizationState.AUTHENTICATED, source=enrollment_session_id, priority=rdf_flows.GrrMessage.Priority.MEDIUM_PRIORITY) # Publish the client enrollment message. events.Events.PublishEvent("ClientEnrollment", publish_msg, token=self.token)
def testExpiredTokens(self): """Tests that expired tokens are rejected.""" urn = rdf_client.ClientURN("C.%016X" % 0).Add("/fs/os/c") self.assertRaises(access_control.UnauthorizedAccess, aff4.FACTORY.Open, urn) with test_lib.FakeTime(100): # Token expires in 5 seconds. super_token = access_control.ACLToken(username="******", expiry=105) super_token.supervisor = True # This should work since token is a super token. aff4.FACTORY.Open(urn, mode="rw", token=super_token) # Change the time to 200 with test_lib.FakeTime(200): # Should be expired now. with self.assertRaises(access_control.ExpiryError): aff4.FACTORY.Open(urn, token=super_token, mode="rw")
def __init__(self, client_id, token=None, fixture=None, age=None, **kwargs): """Constructor. Args: client_id: The unique id for the new client. token: An instance of access_control.ACLToken security token. fixture: An optional fixture to install. If not provided we use client_fixture.VFS. age: Create the fixture at this timestamp. If None we use FIXTURE_TIME. **kwargs: Any other parameters which need to be interpolated by the fixture. """ self.args = kwargs self.token = token self.age = age or FIXTURE_TIME.AsSecondsSinceEpoch() self.client_id = rdf_client.ClientURN(client_id) self.args["client_id"] = self.client_id.Basename() self.args["age"] = self.age self.CreateClientObject(fixture or client_fixture.VFS)
def testClickingOnTreeNodeArrowRefreshesChildrenFoldersList(self): self.Open("/#/clients/C.0000000000000001/vfs/fs/os/c/") self.WaitUntil(self.IsElementPresent, "link=Downloads") self.WaitUntilNot(self.IsElementPresent, "link=foo") gui_test_lib.CreateFolder(rdf_client.ClientURN("C.0000000000000001"), "fs/os/c/foo", timestamp=gui_test_lib.TIME_0, token=self.token) # Click on the arrow icon, it should close the tree branch. self.Click("css=#_fs-os-c i.jstree-icon") self.WaitUntilNot(self.IsElementPresent, "link=Downloads") self.WaitUntilNot(self.IsElementPresent, "link=foo") # Click on the arrow icon again, it should reopen the tree # branch. It should be updated. self.Click("css=#_fs-os-c i.jstree-icon") self.WaitUntil(self.IsElementPresent, "link=Downloads") self.WaitUntil(self.IsElementPresent, "link=foo")
def testLogsCanBeOpenedByClickingOnLogsTab(self): client_id = rdf_client.ClientURN("C.0000000000000001") # RecursiveTestFlow doesn't send any results back. with self.ACLChecksDisabled(): for _ in test_lib.TestFlowHelper("RecursiveTestFlow", action_mocks.ActionMock(), client_id=client_id, token=self.token): pass self.GrantClientApproval(client_id) self.Open("/#c=C.0000000000000001") self.Click("css=a:contains('Manage launched flows')") self.Click("css=td:contains('RecursiveTestFlow')") self.Click("css=li[renderer=FlowLogView]") self.WaitUntil(self.IsTextPresent, "Subflow call 1") self.WaitUntil(self.IsTextPresent, "Subflow call 0")
def testRemoveLabels(self): client = aff4.FACTORY.Create( CLIENT_ID, aff4_type=aff4_grr.VFSGRRClient, mode="rw", token=self.token) client.AddLabel("testlabel_1") client.AddLabel("testlabel_2") client.Flush() index = client_index.CreateClientIndex(token=self.token) index.AddClient(client) client_list = [rdf_client.ClientURN(CLIENT_ID)] self.assertEqual(index.LookupClients(["testlabel_1"]), client_list) self.assertEqual(index.LookupClients(["testlabel_2"]), client_list) # Now delete one label. index.RemoveClientLabels(client) client.RemoveLabel("testlabel_1") index.AddClient(client) self.assertEqual(index.LookupClients(["testlabel_1"]), []) self.assertEqual(index.LookupClients(["testlabel_2"]), client_list)
def FillClientStats( client_id=rdf_client.ClientURN("C.0000000000000001"), token=None): for minute in range(6): stats = rdf_client.ClientStats() for i in range(minute * 60, (minute + 1) * 60): sample = rdf_client.CpuSample(timestamp=int(i * 10 * 1e6), user_cpu_time=10 + i, system_cpu_time=20 + i, cpu_percent=10 + i) stats.cpu_samples.Append(sample) sample = rdf_client.IOSample(timestamp=int(i * 10 * 1e6), read_bytes=10 + i, write_bytes=10 + i * 2) stats.io_samples.Append(sample) message = rdf_flows.GrrMessage(source=client_id, args=stats.SerializeToString()) flow.WellKnownFlow.GetAllWellKnownFlows( token=token)["Stats"].ProcessMessage(message)
def testBreakGlass(self): """Test the breakglass mechanism.""" client_id = rdf_client.ClientURN("C.%016X" % 0) urn = client_id.Add("/fs/os/c") self.assertRaises(access_control.UnauthorizedAccess, aff4.FACTORY.Open, urn, token=self.token) # We expect to receive an email about this email = {} def SendEmail(to, from_user, subject, message, **_): email["to"] = to email["from_user"] = from_user email["subject"] = subject email["message"] = message with utils.Stubber(email_alerts.EMAIL_ALERTER, "SendEmail", SendEmail): flow.GRRFlow.StartFlow( client_id=client_id, flow_name="BreakGlassGrantClientApprovalFlow", token=self.token, reason=self.token.reason) # Reset the emergency state of the token. self.token.is_emergency = False # This access is using the emergency_access granted, so we expect the # token to be tagged as such. aff4.FACTORY.Open(urn, token=self.token) self.assertEqual( email["to"], config_lib.CONFIG["Monitoring.emergency_access_email"]) self.assertIn(self.token.username, email["message"]) self.assertEqual(email["from_user"], self.token.username) # Make sure the token is tagged as an emergency token: self.assertEqual(self.token.is_emergency, True)
def testOSXLaunchdPlistParser(self): parser = osx_file_parser.OSXLaunchdPlistParser() client = "C.1000000000000000" plists = ["com.google.code.grr.plist", "com.google.code.grr.bplist"] results = [] for plist in plists: path = os.path.join(self.base_path, plist) plist_file = open(path) stat = rdf_client.StatEntry( aff4path=rdf_client.ClientURN(client).Add("fs/os").Add(path), pathspec=rdf_paths.PathSpec(path=path, pathtype=rdf_paths.PathSpec.PathType.OS), st_mode=16877) results.extend(list(parser.Parse(stat, plist_file, None))) for result in results: self.assertEqual(result.Label, "com.google.code.grr") self.assertItemsEqual(result.ProgramArguments, ["/usr/lib/grr/grr_3.0.0.5_amd64/grr", "--config=/usr/lib/grr/grr_3.0.0.5_amd64/grr.yaml"] )
def CallClient(self, client_id, action_cls, request=None, response_session_id=None, **kwargs): """Calls a client action from a well known flow.""" if client_id is None: raise FlowError("CallClient needs a valid client_id.") client_id = rdf_client.ClientURN(client_id) if action_cls.in_rdfvalue is None: if request: raise RuntimeError("Client action %s does not expect args." % action_cls.__name__) else: if request is None: # Create a new rdf request. request = action_cls.in_rdfvalue(**kwargs) else: # Verify that the request type matches the client action requirements. if not isinstance(request, action_cls.in_rdfvalue): raise RuntimeError("Client action expected %s but got %s" % (action_cls.in_rdfvalue, type(request))) if response_session_id is None: cls = GRRFlow.classes["IgnoreResponses"] response_session_id = cls.well_known_session_id msg = rdf_flows.GrrMessage( session_id=utils.SmartUnicode(response_session_id), name=action_cls.__name__, request_id=0, queue=client_id.Queue(), payload=request, generate_task_id=True) queue_manager.QueueManager(token=self.token).Schedule(msg)
def testProcessMessagesWellKnown(self): worker_obj = worker.GRRWorker(token=self.token) # Send a message to a WellKnownFlow - ClientStatsAuto. session_id = administrative.GetClientStatsAuto.well_known_session_id client_id = rdf_client.ClientURN("C.1100110011001100") self.SendResponse(session_id, data=rdf_client.ClientStats(RSS_size=1234), client_id=client_id, well_known=True) # Process all messages worker_obj.RunOnce() worker_obj.thread_pool.Join() client = aff4.FACTORY.Open(client_id.Add("stats"), token=self.token) stats = client.Get(client.Schema.STATS) self.assertEqual(stats.RSS_size, 1234) # Make sure no notifications have been sent. user = aff4.FACTORY.Open("aff4:/users/%s" % self.token.username, token=self.token) notifications = user.Get(user.Schema.PENDING_NOTIFICATIONS) self.assertIsNone(notifications)
def testExportTabIsEnabledForStatEntryResults(self): client_id = rdf_client.ClientURN("C.0000000000000001") with self.ACLChecksDisabled(): for _ in test_lib.TestFlowHelper("FlowWithOneStatEntryResult", action_mocks.ActionMock(), client_id=client_id, token=self.token): pass self.GrantClientApproval(client_id) self.Open("/#c=C.0000000000000001") self.Click("css=a:contains('Manage launched flows')") self.Click("css=td:contains('FlowWithOneStatEntryResult')") self.Click("css=#Export") self.WaitUntil( self.IsTextPresent, "--username test --reason 'Running tests' collection_files " "--path aff4:/C.0000000000000001/analysis/FlowWithOneStatEntryResult" )
def Layout(self, request, response): """Render the toolbar.""" self.state["client_id"] = client_id = request.REQ.get("client_id") self.state["aff4_path"] = aff4_path = request.REQ.get( "aff4_path", client_id) client_urn = rdf_client.ClientURN(client_id) self.paths = [("/", client_urn, "_", 0)] for path in rdfvalue.RDFURN(aff4_path).Split()[1:]: previous = self.paths[-1] fullpath = previous[1].Add(path) self.paths.append((path, fullpath, renderers.DeriveIDFromPath( fullpath.RelativeName(client_urn)), previous[3] + 1)) response = super(Toolbar, self).Layout(request, response) return self.CallJavascript(response, "Toolbar.Layout", aff4_path=utils.SmartUnicode(aff4_path), paths=self.paths)
def testWriteAuditEventFieldSerialization(self): client_urn = rdf_client.ClientURN("C.4815162342000000") event = rdf_events.AuditEvent( action=rdf_events.AuditEvent.Action.RUN_FLOW, user="******", flow_name="foo", flow_args="bar", client=client_urn, urn=client_urn.Add("flows").Add("108"), description="lorem ipsum") self.db.WriteAuditEvent(event) log = self.db.ReadAllAuditEvents() self.assertEqual(len(log), 1) self.assertEqual(log[0].action, rdf_events.AuditEvent.Action.RUN_FLOW) self.assertEqual(log[0].user, "quux") self.assertEqual(log[0].flow_name, "foo") self.assertEqual(log[0].flow_args, "bar") self.assertEqual(log[0].client, client_urn) self.assertEqual(log[0].urn, client_urn.Add("flows").Add("108")) self.assertEqual(log[0].description, "lorem ipsum")