approaches<\/a>.<\/li>\n<\/ol>\nNote:<\/strong> We chose to use Compose<\/strong> actions instead of variables as there is less of a performance hit and these are values that we will not need to further manipulate.<\/p>\n\n- Our next step is to retrieve an auth token that we can use to retrieve event details from the O365 Security and Compliance Center. We will use the values that we captured in our Compose<\/strong> actions and construct a URI<\/strong> that includes our Tenant ID<\/strong>. Our Header<\/strong> will include a Content-Type<\/strong> of application\/x-www-form-urlencoded.<\/strong> Lastly, we need to provide key\/value pairs that include our Client ID<\/strong>, Client Secret<\/strong>, Resource<\/strong> and Grant Type<\/strong>.<\/li>\n<\/ol>\n
<\/p>\n
\u00a0<\/p>\n
\n- We need to use the token that is returned in downstream actions so we will add a Parse JSON<\/strong> action that can will use this HTTP response as an input. The following Schema<\/strong> can be used to give our response a message shape.<\/li>\n<\/ol>\n
\n{\n \"type\": \"object\",\n \"properties\": {\n \"token_type\": {\n \"type\": \"string\"\n },\n \"expires_in\": {\n \"type\": \"string\"\n },\n \"ext_expires_in\": {\n \"type\": \"string\"\n },\n \"expires_on\": {\n \"type\": \"string\"\n },\n \"not_before\": {\n \"type\": \"string\"\n },\n \"resource\": {\n \"type\": \"string\"\n },\n \"access_token\": {\n \"type\": \"string\"\n }\n }\n}\n<\/pre>\n\u00a0<\/p>\n
<\/p>\n
\n- Our HTTP Trigger will only provide us with a message that describes the event that occurred inside the Office 365 Security and Compliance Center. It won\u2019t provide us with actual details about the event. To get the actual details about the event we need to make a subsequent call to the Office 365 Management API to get the details. We will accomplish this by using the HTTP Action and performing a GET request to the URI that was provided as part of the inbound message. The expression that we can use to retrieve this value is triggerBody()[0]?[‘contentUri’]<\/strong>. We also need to provide an Authorization<\/strong> Header that includes a Bearer<\/strong> token that is retrieved from our previous Parse Token Response<\/strong> action. In addition, we need to specify a Content-Type<\/strong> of application\https://www.microsoft.com/json.<\/strong><\/li>\n<\/ol>\n
<\/strong><\/strong><\/p>\n\u00a0<\/p>\n
\n- We now need to parse our response from the Office 365 Management API so we can explore the results. Once again we will use the Parse JSON action and this time we will provide the following schema:<\/li>\n<\/ol>\n
\n{\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"CreationTime\": {\n \"type\": \"string\"\n },\n \"Id\": {\n \"type\": \"string\"\n },\n \"Operation\": {\n \"type\": \"string\"\n },\n \"OrganizationId\": {\n \"type\": \"string\"\n },\n \"RecordType\": {\n \"type\": \"integer\"\n },\n \"ResultStatus\": {\n \"type\": \"string\"\n },\n \"UserKey\": {\n \"type\": \"string\"\n },\n \"UserType\": {\n \"type\": \"integer\"\n },\n \"Version\": {\n \"type\": \"integer\"\n },\n \"Workload\": {\n \"type\": \"string\"\n },\n \"ObjectId\": {\n \"type\": \"string\"\n },\n \"UserId\": {\n \"type\": \"string\"\n },\n \"FlowConnectorNames\": {\n \"type\": \"string\"\n },\n \"FlowDetailsUrl\": {\n \"type\": \"string\"\n },\n \"LicenseDisplayName\": {\n \"type\": \"string\"\n },\n \"RecipientUPN\": {\n \"type\": \"string\"\n },\n \"SharingPermission\": {\n \"type\": \"integer\"\n },\n \"UserTypeInitiated\": {\n \"type\": \"integer\"\n },\n \"UserUPN\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"CreationTime\",\n \"Id\",\n \"Operation\",\n \"OrganizationId\",\n \"RecordType\",\n \"ResultStatus\",\n \"UserKey\",\n \"UserType\",\n \"Version\",\n \"Workload\",\n \"ObjectId\",\n \"UserId\",\n \"FlowConnectorNames\",\n \"FlowDetailsUrl\",\n \"LicenseDisplayName\",\n \"RecipientUPN\",\n \"SharingPermission\",\n \"UserTypeInitiated\",\n \"UserUPN\"\n ]\n }\n}\n\n<\/pre>\n\u00a0<\/p>\n
<\/p>\n
\n- The Parse Log Event<\/strong> can retrieve multiple events from Office 365. As a result, we need to loop through the Body<\/strong> that is returned from the Parse Log Event<\/strong>. This loop will get added as soon as we use a data element from the Parse Log Event output.<\/li>\n
- Since Microsoft Flow events are captured within Audit.General<\/strong> Content Type inside of Office 365 Security and Compliance Center, will now want to perform some logic that will focus on Microsoft Flow CreateFlow <\/strong>and EditFlow <\/strong>events. To accomplish this, we will add an advanced condition that includes an or <\/strong>statement that looks for either CreateFlow<\/strong> or EditFlow<\/strong> events.<\/li>\n<\/ol>\n
@or(equals(items(‘Apply_to_each_2’)[‘Operation’], ‘CreateFlow’),equals(items(‘Apply_to_each_2’)[‘Operation’], ‘EditFlow’))<\/p>\n
<\/p>\n
\n- Next, we want to see if the Office 365 Outlook Connector is being used within this Flow that created the audit event. We can achieve this by seeing if the FlowConnectorNames<\/strong> attribute (within the Parse Log Event) contains Office 365 Outlook.<\/strong><\/li>\n<\/ol>\n
\u200b<\/strong><\/p>\n\n- If the list of connectors does include the Office 365 Outlook connector then we want to further explore whether the Forward Email<\/strong> action is being used since that is the action that we want to prevent our users from using. In order to determine if a Flow Definition<\/strong> does contain the ForwardEmail <\/strong>action we need to capture the Environment ID<\/strong> and Flow ID<\/strong>. To get the Environment ID<\/strong> we will use a Compose Action<\/strong> and use an expression to parse it from the FlowDetailsUrl<\/strong> attribute that can be found within Parse Log Event \u2013 Body <\/strong>array. The expression we want to use is:<\/li>\n<\/ol>\n
substring(replace(item()?[‘FlowDetailsUrl’],’https:\/\/admin.flow.microsoft.com\/environments\/’,”),0,indexOf(replace(item()?[‘FlowDetailsUrl’],’https:\/\/admin.flow.microsoft.com\/environments\/’,”),’\/’))<\/p>\n
<\/p>\n
\n- We will use a similar approach to retrieve the Flow ID<\/strong>, but our expression will be:<\/li>\n<\/ol>\n
replace(substring(item()?[‘FlowDetailsUrl’],lastIndexOf(item()?[‘FlowDetailsUrl’],’\/’),sub(length(item()?[‘FlowDetailsUrl’]),lastIndexOf(item()?[‘FlowDetailsUrl’],’\/’))),’\/’,”)<\/p>\n
<\/p>\n
\n- In an upcoming step, we want to add our Principle Id<\/strong> as an owner of this flow that we want to inspect so that we can retrieve the flow definition. To obtain our Principle ID<\/strong> we can use the Office 365 Users<\/strong> connector and the Get my profile (V2) <\/strong>action to provide this attribute.<\/li>\n
- We can use the Id <\/strong>returned from the Get my profile (V2) <\/strong>action with our outputs from the Get Environment<\/strong> and Get Flow ID<\/strong> compose actions to add our account as an owner of this flow.<\/li>\n<\/ol>\n
\u00a0<\/p>\n
<\/p>\n
\n- Being an owner of the flow is important so that we can retrieve the flow definition to determine whether or not the Forward Email action is being used. We can retrieve the flow definition by using the Flow Management connector and using the Get Flow<\/strong> action. Once again we need to use the outputs from the Get Environment<\/strong> and Get Flow ID<\/strong> compose actions as inputs to this action.<\/li>\n<\/ol>\n
<\/p>\n
\n- We are going to inspect the flow definition for a swaggerOperationId<\/strong> that is equal to ForwardEmail<\/strong> but before we do that we need to cast the json flow definition to a string. We can do this by using the following expression: string(body(‘Get_Flow’)[‘properties’][‘definition’]).<\/strong> Once we have it cast, we can see if it contains<\/strong> “swaggerOperationId”:”ForwardEmail”<\/strong>.<\/li>\n<\/ol>\n
<\/p>\n
\n- If the flow definition does include the ForwardEmail<\/strong> action then we want to perform some additional steps in the If yes <\/strong>branch.<\/li>\n
- As you have seen, the Environment ID<\/strong> is an attribute that we have used within this flow. But, we have not used the Environment Name<\/strong>, since it isn\u2019t a data attribute that is available to us at this point. However, we can access this attribute by using the List My Environments<\/strong> action that is part of the Flow Management<\/strong> connector.<\/li>\n<\/ol>\n
<\/p>\n
\n- By calling the List My Environments <\/strong>action, all of the environments that our user has access to will be returned. Since we cannot filter using the existing connector, we can add a Filter array<\/strong> action and filter on the Environment Name <\/strong>attribute by comparing it to the Environment ID<\/strong> that we have previously captured.<\/li>\n<\/ol>\n
<\/p>\n
\n- Since the Filter array<\/strong> action will return a list of items that match our criteria, we will want to access the first instance using an expression body(‘Filter_array’)[0]?[‘properties’]?[‘displayName’]<\/strong> which will take the first index of our array. Since Environment IDs<\/strong> are unique, this approach is safe.<\/li>\n
- With our Environment Display Name<\/strong> now available, we can pass this attribute and others into an approval that we will use to determine whether or not any corrective action is required. In addition, we will include the Flow Display Name, Environment ID, User UPN<\/strong> (from Parse Log Event) and Connectors Used <\/strong>(from Parse Log Event).<\/li>\n<\/ol>\n
<\/p>\n
\n- Next, we will wait for an approval by adding a condition to our flow. Provided the Response<\/strong> is equal to Approve<\/strong> we will use the Stop Flow <\/strong>action that is part of the Flow Management<\/strong> connector to stop that flow.<\/li>\n<\/ol>\n
<\/p>\n
To view the entire flow, please click on the following link<\/a>.<\/p>\n\u00a0<\/p>\n
Creating Office 365 Management API Webhook<\/strong><\/p>\nWith our flow now complete, there is something that we need to do before we create our Webhook subscription. We need the URL that is part of our HTTP Request Trigger <\/strong>which we can copy by clicking on the following icon.<\/p>\n<\/p>\n
To complete the next couple steps we are going to need to call the Office 365 Management APIs and as a result will benefit from a tool called Postman<\/strong><\/a>.<\/strong><\/p>\nWe need to generate an access token that we can use to create our Webhook subscription. To do this, we need access to our Code<\/strong> that is returned from our consent call.<\/p>\n\n- To obtain this code populate client_id <\/strong>and redirect_uri <\/strong>with your values and enter this into a web browser.<\/li>\n<\/ol>\n
https:\/\/login.windows.net\/common\/oauth2\/authorize?response_type=code&resource=https%3A%2F%2Fmanage.office.com&client_id={your_client_id}<\/strong>&redirect_uri={your_redirect_url}<\/strong><\/a><\/p>\n\n- When the webpage resolves, there will be a query parameter called code\u00a0<\/b>returned in the URL. Copy this value for use in the next step.<\/li>\n<\/ol>\n
<\/p>\n
Note: <\/strong>At the end of the URL returned from the web browser, there may be a session_state<\/strong> query parameter also returned. This value is not<\/strong> required and should not <\/strong>be included in the next step.<\/p>\n