DependencyTrack / dependency-track

Dependency-Track is an intelligent Component Analysis platform that allows organizations to identify and reduce risk in the software supply chain.
https://dependencytrack.org/
Apache License 2.0
2.69k stars 578 forks source link

MS Teams is retiring webhooks - Power Automate workflows is the new black #3953

Open black-snow opened 4 months ago

black-snow commented 4 months ago

Current Behavior

M$ is retiring the classic webhooks and you'll have to use Power Automate workflows instead.

The linked page doesn't have too many details about the transition so I thought it'd be a swap & replace. But apparently that's not true. With the existing template my workflow errs with:

... "Send_each_adaptive_card": The execution of template action 'Send_each_adaptive_card' failed: the result of the evaluation of 'foreach' expression '@triggerOutputs()?['body']?['attachments']' is of type 'Null'. The result must be a valid array.

Proposed Behavior

I guess it'd be a good idea to have a new template for notifications for workflows that works out of the box and has an adequate name.

Checklist

otbe commented 4 months ago

Quick headsup because I tried it this morning as well with dependency track.

Its not enough to wrap the existing message into the new payload structure that comes with the default template for sending messages to a teams channel (see)

They also only accept adaptive cards there, no more MessageCards or even the simpler text version of the old webhook. If you however put the example from the link above into a custom template in DTrack it works. So I guess we need a new adaptive card template.

black-snow commented 4 months ago

Thanks for looking into this @otbe. Did you just reuse org.dependencytrack.notification.publisher.MsTeamsPublisher for your custom template?

otbe commented 4 months ago

Thanks for looking into this @otbe. Did you just reuse org.dependencytrack.notification.publisher.MsTeamsPublisher for your custom template?

Yes exactly.

black-snow commented 4 months ago

I'll be giving this a shot:

{
       "type":"message",
       "attachments":[
          {
             "contentType":"application/vnd.microsoft.card.adaptive",
             "contentUrl":"{{ baseUrl }}/projects/{{ subject.project.uuid | escape(strategy='json') }}",
             "content":{
                "$schema":"http://adaptivecards.io/schemas/adaptive-card.json",
                "type":"AdaptiveCard",
                "version":"1.2",
                "body":[
                    {
                    "type": "TextBlock",
{% if notification.group == "POLICY_VIOLATION" %}
                    "text": "{{ subject.policyViolation.policyCondition.subject | escape(strategy="json") }} {{ subject.component.toString | escape(strategy="json") }} - {{ notification.content | escape(strategy="json") }}"
{% elseif notification.group == "BOM_PROCESSING_FAILED" %}
                    "text": "BOM processing failed for {{ subject.project.toString | escape(strategy="json") }} - {{ notification.content | escape(strategy="json") }}"
{% else  %}
                    "text": "{{ notification.content | escape(strategy="json") }}"
{% endif %}
                    }
                ]
             }
          }
       ]
    }

Should be easy to add a new template then. Perhaps I'll do it today or tomorrow.

hajohoetger commented 4 months ago

Hi, i'm suffering from the same problem. Unfortunately i'm not too deep into this subjects and i have to admit, that i have not understood everything from your post. I managed to create a workflow 'post to channel...'. When i post your templete to the workflow url, a message is displayed in the teams channel, but it's empty. No text at all. May i kindly ask, to have a look? Is there something missing in my payload?

  4 def send_teams_message(flow_url, message):
  5     headers = {
  6         "Content-Type": "application/json"
  7     }
  8
  9     proxyip = "blinded"
 10     proxyport = "3128"
 11     proxies= {"https": f"http://{proxyip}:{proxyport}"}
 12
 13     #payload = {
 14     #    "message": message
 15     #}
 16     payload = {
 17        "title": "Ueberschrift",
 18        "type":"AdaptiveCard",
 19        "attachments":[
 20           {
 21              "contentType":"application/vnd.microsoft.card.adaptive",
 22              "contentUrl":None,
 23              "content":{
 24                 "$schema":"http://adaptivecards.io/schemas/adaptive-card.json",
 25                 "type":"AdaptiveCard",
 26                 "version":"1.2",
 27                 "body":[
 28                     {
 29                     "type": "TextBlock",
 30                     "size": "Medium",
 31                     "weight": "Bolder",
 32                     "text": "Bla Bla"
 33                     }
 34                 ]
 35              }
 36           }
 37        ]
 38     }
 39
 40
 41     response = requests.post(flow_url, headers=headers, json=payload, proxies=proxies)
 42
 43     if response.status_code == 202:
 44         print("Message sent successfully")
 45     else:
 46         print(f"Failed to send message. Status code: {response.status_code}")
 47         print(f"Response: {response.text}")
black-snow commented 4 months ago

grafik Works - so all there is to do (probs) is deciding what to put where to have the information you need and to make it look pretty.

@hajohoetger didn't go through your code but it looks different from the template "draft" I posted above. Give it a try.

P.S.: You can use triple backticks to get a code block or > for quotes - makes it way easier to read.

Wes-Love commented 3 months ago

I got this far, it works for new vulnerabilities.

{ "attachments": [ { "contentType": "object", "content": { "type": "AdaptiveCard", "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.2", "body": [ { "type": "TextBlock", "size": "Medium", "weight": "Bolder", "text": "{{ notification.title | escape(strategy="json") }}" }, { "type": "TextBlock", "text": "Dependency-Track", "weight": "Bolder", "spacing": "Medium" }, { "type": "TextBlock", "text": "{{ timestamp }}", "spacing": "None" }, { "type": "Image", "url": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", "size": "Small", "spacing": "Medium" }, { "type": "FactSet", "facts": [ { "title": "VulnID", "value":"{{ subject.vulnerability.vulnId | escape(strategy="json") }}" }, { "title": "Severity", "value": "{{ subject.vulnerability.severity | escape(strategy="json") }}" }, { "title": "Source", "value": "{{ subject.vulnerability.source | escape(strategy="json") }}" }, { "title": "Component", "value": "{{ subject.component.toString | escape(strategy="json") }}" } ] }, { "type": "TextBlock", "text": "{{ notification.content | escape(strategy="json") }}", "wrap": true } ] } } ] }

image

Use the teams workflow as below:

image

hajohoetger commented 3 months ago

Hi, sorry to bother you again, but i'm trying for hours now without success. I did manage to display my card in teams, but it is cut, though i have defined "targetWidth": "Wide". I tried that in all parts but that does not change the layout. I just want the card to fit the width of the screen. Often the expert sees the problem at first sight. Then it would be very nice to hint me on that...

    proxies= {
            "https": f"http://{proxyip}:{proxyport}"
            }
    payload = { 
            "type":"AdaptiveCard",
            "attachments":[
                {
                    "contentType":"application/vnd.microsoft.card.adaptive",
                    "contentUrl":None,
                    "content":{
                        "$schema":"http://adaptivecards.io/schemas/adaptive-card.json",
                        "type":"AdaptiveCard",
                        "version":"1.2",
                        "targetWidth": "Wide",
                        "body":[
                            {
                                "type": "TextBlock",
                                "size": "Medium",
                                "weight": "Bolder",
                                "text": "Provisionierung erfolgt!"
                                },
                            {
                                "type": "ColumnSet",
                                "columns": [
                                    {   
                                        "type": "Column",
                                        "items": [
                                            {   
                                                "type": "TextBlock",
                                                "text": "Ressource:"
                                                },  
                                            {   
                                                "type": "TextBlock",
                                                "text": "UserID:"
                                                },
                                            {
                                                "type": "TextBlock",
                                                "text": "Action:"
                                                }
                                            ],
                                        "width": "auto"
                                        },
                                    {
                                        "type": "Column",
                                        "items": [
                                            {
                                                "type": "TextBlock",
                                                "text": ressource
                                                },
                                            {
                                                "type": "TextBlock",
                                                "text": uid
                                                },
                                            {
                                                "type": "TextBlock",
                                                "text": action
                                                }
                                            ],
                                        "width": "stretch"
                                        }
                                    ]
                                }
                            ]
                        }
                    }
                ]
            }
    response = requests.post(url[stage], headers=headers, json=payload, proxies=proxies)

The output in Teams looks like this: screenshot

hajohoetger commented 3 months ago

I used three ticks to make the code more readeable, but that didn't work. Maybe, if i upload that as file:

code.txt

nscuro commented 3 months ago

@hajohoetger I used three ticks to make the code more readeable, but that didn't work.

It has to be backticks: ```

hajohoetger commented 3 months ago

@hajohoetger I used three ticks to make the code more readeable, but that didn't work.

It has to be backticks: ```

...indeed, that works! (Have edited my former posts.) Thank you! :-)

AshishDadhich4h2 commented 2 months ago

Getting below error while posting a message with using new workflow URL The execution of template action 'Send_each_adaptive_card' failed: the result of the evaluation of 'foreach' expression '@triggerOutputs()?['body']?['attachments']' is of type 'Null'. The result must be a valid array. I am using legacy card like that used to work with webhook URL. It's throwing an above error when use workflow URL

[Array]$audit = 
    @{
        name  = "LastBuildStatus and Provisioning State"
        value = ("$($results.LastRunStatusRunState)" + " - " + "$($results.ProvisioningState -join ',')")
    }  

$body = ConvertTo-Json -Depth 10 @{
        title     = "Status"
        text      = "Reminder "
        separator = "true"
        sections  = @(

            @{
                activityTitle    = "Test "
                activitySubtitle = "Please take an action, accordingly, refer [document](https: doc link)"
                facts            = $audit 
            }     

        )

    } 

Invoke-RestMethod -Method post -ContentType 'application/Json' -Body $body  -Uri $workflowUrl

Any one encountered same issue? Do we really need to convert legacy card into adaptative card format ?

black-snow commented 2 months ago

Yes we do. Apparently they realized the time window was a bit short and they are going to support old webhooks for longer but you still can no longer create them, only power automate workflows. And they expect a different body with attachments of adaptive cards. I haven't come around to file a PR as the duct taped version above is GEFN for me 🙃

fabian-zeindl-oebb commented 2 months ago

Here is an adaptive-card-template that works well for me:

{
    "type":"message",
    "attachments":[
        {
            "contentType":"application/vnd.microsoft.card.adaptive",
            "contentUrl":"{{ baseUrl }}/projects/{{ subject.project.uuid | escape(strategy='json') }}",
            "content":{
                "type": "AdaptiveCard",
                "body": [
                    {
                        "type": "TextBlock",
                        "size": "Medium",
                        "weight": "Bolder",
                        "text": "{{ notification.title | escape(strategy="json") }}",
                        "wrap": true
                    },
                    {
                        "type": "ColumnSet",
                        "columns": [
                            {
                                "type": "Column",
                                "items": [
                                    {
                                        "type": "Image",
                                        "style": "Person",
                                        "url": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png",
                                        "altText": "DependencyTrack",
                                        "size": "Small"
                                    }
                                ],
                                "width": "auto"
                            },
                            {
                                "type": "Column",
                                "items": [
                                    {
                                        "type": "TextBlock",
                                        "weight": "Bolder",
                                        "text": "DependencyTrack",
                                        "wrap": true
                                    },
                                    {
                                        "type": "TextBlock",
                                        "spacing": "None",
                                        "text": "{{ timestamp }}",
                                        "isSubtle": true,
                                        "wrap": true
                                    }
                                ],
                                "width": "stretch"
                            }
                        ]
                    },
                    {
                        "type": "TextBlock",
                        "text": "{{ notification.content | escape(strategy="json") }}",
                        "wrap": true
                    },
                    {% if notification.group == "NEW_VULNERABILITY" %}
                    {
                        "type": "FactSet",
                        "facts": [
                            {
                                "title": "Project:",
                                "value": "{{ subject.component.project.toString | escape(strategy="json") }}"
                            },
                            {
                                "title": "Component:",
                                "value": "{{ subject.component.toString | escape(strategy="json") }}"
                            },
                            {
                                "title": "Version:",
                                "value": "{{ subject.component.version | escape(strategy="json") }}"
                            },
                            {
                                "title": "Severity:",
                                "value": "{{ subject.vulnerability.severity | escape(strategy="json") }}"
                            },
                            {
                                "title": "VulnId:",
                                "value": "{{ subject.vulnerability.vulnId | escape(strategy="json") }}"
                            }
                        ]
                    }
                    {% elseif notification.group == "NEW_VULNERABLE_DEPENDENCY" %}
                    {
                        "type": "FactSet",
                        "facts": [
                            {
                                "title": "Project:",
                                "value": "{{ subject.component.project.toString | escape(strategy="json") }}"
                            },
                            {
                                "title": "Component:",
                                "value": "{{ subject.component.toString | escape(strategy="json") }}"
                            },
                            {
                                "title": "Version:",
                                "value": "{{ subject.component.version | escape(strategy="json") }}"
                            }
                        ]
                    }
                    {% elseif notification.group == "POLICY_VIOLATION" %}
                    {
                        "type": "FactSet",
                        "facts": [
                            {
                                "title": "Project:",
                                "value": "{{ subject.component.project.toString | escape(strategy="json") }}"
                            },
                            {
                                "title": "Component:",
                                "value": "{{ subject.component.toString | escape(strategy="json") }}"
                            },
                            {
                                "title": "Version:",
                                "value": "{{ subject.component.version | escape(strategy="json") }}"
                            }
                        ]
                    }
                    {% elseif notification.group == "BOM_PROCESSING_FAILED" %}
                    {
                        "type": "FactSet",
                        "facts": [
                            {
                                "title": "Project:",
                                "value": "{{ subject.component.project.toString | escape(strategy="json") }}"
                            },
                            {
                                "title": "Component:",
                                "value": "{{ subject.component.toString | escape(strategy="json") }}"
                            },
                            {
                                "title": "Version:",
                                "value": "{{ subject.component.version | escape(strategy="json") }}"
                            }
                        ]
                    }
                    {% endif %}
                ],
                "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
                "version": "1.4"
            }
        }
    ]
}
nscuro commented 4 weeks ago

Anything on this that could be included per default in DT? Do we need two separate publishers? One for the legacy, and one for the new format?

black-snow commented 3 weeks ago

Someone still has to sit down and work this out for the general case.

As the legacy connectors' life span was extended we should have both.

fabian-zeindl-oebb commented 3 weeks ago

@black-snow My template works for the general case.

There is a designer available here to work on the template: https://adaptivecards.io/

black-snow commented 3 weeks ago

Someone still has to sit down and actually file a PR with @fabian-zeindl-oebb 's template :D

wazzamatazz commented 5 days ago

Note that Microsoft has updated the guidance in the original post and now says that they are looking into allowing messages to continue to be posted using the MessageCard format:

Image