Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Api Key not being sent when deleting webhook #3678

Open
BladeMF opened this issue Oct 4, 2024 · 14 comments
Open

[BUG] Api Key not being sent when deleting webhook #3678

BladeMF opened this issue Oct 4, 2024 · 14 comments
Labels
bug Something isn't working

Comments

@BladeMF
Copy link

BladeMF commented Oct 4, 2024

Type of Connector

Custom Connector

Name of Connector

Everhour

Describe the bug

I have create a custom connector for Everhour. It works fine, but they don't include the Location header in their response to the request creating the webhook. So I added custom code to my connector adding the header:

public class Script : ScriptBase
{
public override async Task<HttpResponseMessage> ExecuteAsync()
    {
        HttpResponseMessage response = await this.Context.SendAsync(this.Context.Request, this.CancellationToken).ConfigureAwait(continueOnCapturedContext: false);
        if (response.IsSuccessStatusCode)
        {
            if (this.Context.OperationId == "Trigger")
            {
                return await this.AddLocationHeader(response).ConfigureAwait(false);
            }
        }
        return response;
    }
    private async Task<HttpResponseMessage> AddLocationHeader(HttpResponseMessage response)
    {
        var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false);
        var result = JObject.Parse(responseString);
        response.Headers.Add("Location", "https://private-anon-d7b81b023f-everhour.apiary-mock.com/hooks/" + result["id"]);
        return response;
    }
}

So now the connector runs that URL when the flow is turned off. The problem is that no authentication headers are sent. The delete action is defined in the connector, but the deletion seems to ignore the definition (you can see I even added a random header just to make sure it is sent, but it isn't):

  /hooks/{hook_id}:
    delete:
      responses:
        '204':
          description: Success
          schema: {}
      summary: Delete a Webhook
      description: Deletes a webhook given an id
      operationId: DeleteWebhook
      x-ms-visibility: internal
      parameters:
        - $ref: '#/parameters/Content-type'
        - name: hook_id
          in: path
          required: true
          type: integer
        - name: X-My-Header
          in: header
          type: string
          default: My value

I'd appreciate any help here. I know what the headers are not sent, because their API offers a mock address that displays the calls. The delete call looks like this:

host: private-anon-d7b81b023f-everhour.apiary-mock.com
x-real-ip: 2.18.26.80
content-length: 0
x-ms-trigger-type: openapiconnectionwebhook
accept-language: en-us
x-ms-workflow-id: a342b9ba4f754909a4e476fd7d3094bb
x-ms-workflow-version: 08584735468993414396
x-ms-workflow-name: d666f7fd-f1f0-4d3a-a471-00077a8a487b
x-ms-workflow-system-id: /locations/uksouth/scaleunits/prod-14/workflows/a342b9ba4f754909a4e476fd7d3094bb
x-ms-workflow-run-id: 08584735468990753370210395964cu00
x-ms-workflow-operation-name: trigger
x-ms-execution-location: uksouth
x-ms-workflow-subscription-id: b1b36c61-d929-4258-8ace-6d31523e9820
x-ms-workflow-resourcegroup-name: 21c205cc335d4b0fb444ff527de57ab6-1c51a2c5741d41fe8e6f5ae93dc0bc90
x-ms-tracking-id: 72a6d878-283d-4ef3-b782-56b74e71f6f5
x-ms-correlation-id: 72a6d878-283d-4ef3-b782-56b74e71f6f5
x-ms-client-request-id: 72a6d878-283d-4ef3-b782-56b74e71f6f5
user-agent: azure-logic-apps/1.0 (workflow a342b9ba4f754909a4e476fd7d3094bb; version 08584735468993414396) microsoft-flow/1.0
x-ms-activity-vector: 00.01.in.0b.in.1b.in.03.in.1p
x-akamai-config-log-detail: true
accept-encoding: gzip
akamai-origin-hop: 1
via: 1.1 akamai.net(ghost) (akamaighost)
pragma: no-cache
cache-control: no-cache, max-age=0
akamai-grn: 0.501a1202.1728059986.171cd967
opc-request-id: gen_cd32fd0b-b143-4a21-8153-7cb859ec7f26

The operation "Trigger" definition is:

  /hooks:
    x-ms-notification-content:
      description: Default response
      schema: {}
    post:
      responses:
        '201':
          description: Default
      summary: Triggers
      operationId: Trigger
      x-ms-trigger: single
      parameters:
        - $ref: '#/parameters/Content-type'
        - name: body
          in: body
          required: true
          schema:
            type: object
            properties:
              targetUrl:
                type: string
                description: targetUrl
                x-ms-notification-url: true
                x-ms-visibility: internal
                title: ''
              events:
                type: array
                items:
                  type: string
                description: events
              project:
                type: string
                description: project
            required:
              - targetUrl
      description: Any trigger

Is this a security bug?

No, this is not a security bug

What is the severity of this bug?

Severity 2 - One or more important connector features are down

To Reproduce

Turn off the flow using the connector.

Expected behavior

The authentication header is sent with the delete requests.

Environment summary

Web.

Additional context

No additional context.

@BladeMF BladeMF added the bug Something isn't working label Oct 4, 2024
@troystaylor
Copy link
Contributor

For your delete action, what is 'X-My-Header'?
You have not included what the Security setting is for your connector - is it set like this?:
image

@BladeMF
Copy link
Author

BladeMF commented Oct 4, 2024

X-My-Header is a test to see if that definition was being used when calling the delete URL. It's not sent.
Here is the security:

securityDefinitions:
  API Key:
    type: apiKey
    in: header
    name: X-Api-Key
security:
  - API Key: []

@BladeMF
Copy link
Author

BladeMF commented Oct 4, 2024

I can paste the whole connector if needed.

@troystaylor
Copy link
Contributor

Can you tell me how you are getting that Location URL?
I've been able to create the trigger:
image
And create a flow with the When An HTTP Request Is Received trigger, parse the header, but my HTTP action back to https://api.everhour.com/hooks times out:
image

@BladeMF
Copy link
Author

BladeMF commented Oct 4, 2024

Oh sorry, this is a specific URL generated by the Apiary inspector:
image
Go to the inspector and it will give you the URL. Otherwise use https://api.everhour.com. Does that make sense?

@troystaylor
Copy link
Contributor

I've sent an email to Everhour - if we get the respond to URL figured out, then this should work and I don't think you need to add the location.

@BladeMF
Copy link
Author

BladeMF commented Oct 4, 2024

What do you mean the respond url? It is always DELETE http://api.everhour.com/hooks/<hook-id>. Or am I misunderstanding something?

@troystaylor
Copy link
Contributor

This the part that I can't get to work, which would be a POST back to them:
image

@BladeMF
Copy link
Author

BladeMF commented Oct 4, 2024

I actually don't do this and it is working nonetheless. I assumed it was some sort of standard and you took care of it. I register hooks alright, I just can't get them to unregister. Here is the whole connector:

{
  "swagger": "2.0",
  "info": {
    "title": "Everhour",
    "description": "",
    "version": "1.0"
  },
  "host": "api.everhour.com",
  "basePath": "/",
  "schemes": [
    "https"
  ],
  "consumes": [],
  "produces": [],
  "paths": {
    "/projects": {
      "get": {
        "responses": {
          "default": {
            "description": "default",
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/Project"
              }
            }
          }
        },
        "summary": "Get projects",
        "description": "Gets the project list",
        "operationId": "GetProjects",
        "parameters": [
          {
            "$ref": "#/parameters/Content-type"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "type": "integer"
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "type": "string"
          },
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "type": "string",
            "enum": [
              "as",
              "ev",
              "b3",
              "b2",
              "pv",
              "gh",
              "in",
              "tr",
              "jr"
            ]
          }
        ]
      }
    },
    "/hooks": {
      "x-ms-notification-content": {
        "description": "Default response",
        "schema": {}
      },
      "post": {
        "responses": {
          "201": {
            "description": "Default"
          }
        },
        "summary": "Triggers",
        "operationId": "Trigger",
        "x-ms-trigger": "single",
        "parameters": [
          {
            "$ref": "#/parameters/Content-type"
          },
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "properties": {
                "targetUrl": {
                  "type": "string",
                  "description": "targetUrl",
                  "x-ms-notification-url": true,
                  "x-ms-visibility": "internal",
                  "title": ""
                },
                "events": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "events"
                },
                "project": {
                  "type": "string",
                  "description": "project"
                }
              },
              "required": [
                "targetUrl"
              ]
            }
          }
        ],
        "description": "Any trigger"
      }
    },
    "/hooks/{hook_id}": {
      "delete": {
        "responses": {
          "204": {
            "description": "Success",
            "schema": {}
          }
        },
        "summary": "Delete a Webhook",
        "description": "Deletes a webhook given an id",
        "operationId": "DeleteWebhook",
        "x-ms-visibility": "internal",
        "parameters": [
          {
            "$ref": "#/parameters/Content-type"
          },
          {
            "name": "hook_id",
            "in": "path",
            "required": true,
            "type": "integer"
          },
          {
            "name": "X-My-Header",
            "in": "header",
            "type": "string",
            "default": "My value"
          }
        ]
      },
      "get": {
        "responses": {
          "default": {
            "description": "default",
            "schema": {
              "$ref": "#/definitions/Hook"
            }
          }
        },
        "summary": "Get webhook",
        "description": "Gets a webhook by id",
        "operationId": "GetWebhook",
        "parameters": [
          {
            "$ref": "#/parameters/Content-type"
          },
          {
            "name": "hook_id",
            "in": "path",
            "required": true,
            "type": "string"
          }
        ]
      }
    }
  },
  "definitions": {
    "Hook": {
      "type": "object",
      "properties": {
        "id": {
          "type": "integer",
          "format": "int32",
          "description": "id"
        },
        "targetUrl": {
          "type": "string",
          "description": "targetUrl"
        },
        "events": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "events"
        },
        "project": {
          "type": "string",
          "description": "project"
        },
        "isActive": {
          "type": "boolean",
          "description": "isActive"
        },
        "createdAt": {
          "type": "string",
          "description": "createdAt"
        },
        "lastUsedAt": {
          "type": "string",
          "description": "lastUsedAt"
        }
      }
    },
    "Project": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "description": "id"
        },
        "name": {
          "type": "string",
          "description": "name"
        },
        "workspaceId": {
          "type": "string",
          "description": "workspaceId"
        },
        "workspaceName": {
          "type": "string",
          "description": "workspaceName"
        },
        "client": {
          "type": "integer",
          "format": "int32",
          "description": "client"
        },
        "type": {
          "type": "string",
          "description": "type"
        },
        "favorite": {
          "type": "boolean",
          "description": "favorite"
        },
        "users": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "int32"
          },
          "description": "users"
        },
        "billing": {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "description": "type"
            },
            "fee": {
              "type": "integer",
              "format": "int32",
              "description": "fee"
            }
          },
          "description": "billing"
        },
        "rate": {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "description": "type"
            },
            "rate": {
              "type": "integer",
              "format": "int32",
              "description": "rate"
            },
            "userRateOverrides": {
              "type": "object",
              "properties": {},
              "description": "userRateOverrides"
            },
            "userCostOverrides": {
              "type": "object",
              "properties": {},
              "description": "userCostOverrides"
            }
          },
          "description": "rate"
        },
        "budget": {
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "description": "type"
            },
            "budget": {
              "type": "integer",
              "format": "int32",
              "description": "budget"
            },
            "progress": {
              "type": "integer",
              "format": "int32",
              "description": "progress"
            },
            "timeProgress": {
              "type": "integer",
              "format": "int32",
              "description": "timeProgress"
            },
            "expenseProgress": {
              "type": "integer",
              "format": "int32",
              "description": "expenseProgress"
            },
            "period": {
              "type": "string",
              "description": "period"
            },
            "appliedFrom": {
              "type": "string",
              "description": "appliedFrom"
            },
            "disallowOverbudget": {
              "type": "boolean",
              "description": "disallowOverbudget"
            },
            "excludeUnbillableTime": {
              "type": "boolean",
              "description": "excludeUnbillableTime"
            },
            "excludeExpenses": {
              "type": "boolean",
              "description": "excludeExpenses"
            },
            "showToUsers": {
              "type": "boolean",
              "description": "showToUsers"
            },
            "threshold": {
              "type": "integer",
              "format": "int32",
              "description": "threshold"
            }
          },
          "description": "budget"
        }
      }
    }
  },
  "parameters": {
    "Content-type": {
      "name": "Content-type",
      "in": "header",
      "type": "string",
      "default": "application/json"
    }
  },
  "responses": {},
  "securityDefinitions": {
    "API Key": {
      "type": "apiKey",
      "in": "header",
      "name": "X-Api-Key"
    }
  },
  "security": [
    {
      "API Key": []
    }
  ],
  "tags": []
}

@troystaylor
Copy link
Contributor

I’m not sure why the custom code is needed then. If the location is a static URL, you could return it with a policy.

@BladeMF
Copy link
Author

BladeMF commented Oct 4, 2024

It's not static. I get the id from the response of the hook registration.

@BladeMF
Copy link
Author

BladeMF commented Oct 4, 2024

you could return it with a policy.

I am not sure how to do that. Can you explain what you mean?

@BladeMF
Copy link
Author

BladeMF commented Oct 5, 2024

In fact, I am new to connectors and while I beginning to get it, policies are still a blur to me :-) I will dig in the documentation, but it will certainly help if you can give an example of what you mean. I am switching to paconn now so I can use pagination, so that's my first bump with the policies.

@BladeMF
Copy link
Author

BladeMF commented Oct 5, 2024

I think I understood it. Like:

            {
                "templateId": "setheader",
                "title": "Set Location for webhook response",
                "parameters": {
                    "x-ms-apimTemplateParameter.name": "Location",
                    "x-ms-apimTemplateParameter.value": "https://api.everhour.com/hooks/{@body().id}",
                    "x-ms-apimTemplate-policySection": "Response"
                }
            },

Am I correct?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants