-
Notifications
You must be signed in to change notification settings - Fork 1
/
webhooks.go
155 lines (127 loc) · 4.08 KB
/
webhooks.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package asana
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"hash"
"io"
"net/http"
"time"
)
// Signature headers
const (
hSecret = "X-Hook-Secret"
hSignature = "X-Hook-Signature"
)
type Filter struct {
Action string `json:"action"`
Fields []string `json:"fields,omitempty"`
ResourceType string `json:"resource_type"`
ResourceSubtype string `json:"resource_subtype,omitempty"`
}
type Webhook struct {
ID string `json:"gid"`
ResourceType string `json:"resource_type"`
Active bool `json:"active"`
Resource struct {
ID string `json:"gid"`
ResourceType string `json:"resource_type"`
Name string `json:"name"`
} `json:"resource"`
Target string `json:"target"`
CreatedAt time.Time `json:"created_at"`
Filters []Filter `json:"filters"`
LastFailureAt time.Time `json:"last_failure_at"`
LastFailureContent string `json:"last_failure_content"`
LastSuccessAt time.Time `json:"last_success_at"`
}
// Webhooks returns the compact records for all webhooks your app has registered for the authenticated user in the given workspace.
func (w *Workspace) Webhooks(ctx context.Context, client *Client, options ...*Options) ([]*Webhook, *NextPage, error) {
client.trace("Listing webhooks for workspace %s...\n", w.ID)
var result []*Webhook
workspace := &Options{
Workspace: w.ID,
}
allOptions := append([]*Options{workspace}, options...)
// Make the request
nextPage, err := client.get(ctx, "/webhooks", nil, &result, allOptions...)
return result, nextPage, err
}
// AllWebhooks repeatedly pages through all available webhooks for a user
func (w *Workspace) AllWebhooks(ctx context.Context, client *Client, options ...*Options) ([]*Webhook, error) {
var allWebhooks []*Webhook
nextPage := &NextPage{}
var webhooks []*Webhook
var err error
for nextPage != nil {
page := &Options{
Limit: 100,
Offset: nextPage.Offset,
}
allOptions := append([]*Options{page}, options...)
webhooks, nextPage, err = w.Webhooks(ctx, client, allOptions...)
if err != nil {
return nil, err
}
allWebhooks = append(allWebhooks, webhooks...)
}
return allWebhooks, nil
}
// CreateWebhook registers a new webhook
func (c *Client) CreateWebhook(ctx context.Context, resource, target string, filters []Filter) (*Webhook, error) {
m := map[string]interface{}{}
m["resource"] = resource
m["target"] = target
m["filters"] = filters
result := &Webhook{}
err := c.post(ctx, "/webhooks", m, result)
return result, err
}
// DeleteWebhook deletes an existing webhook
func (c *Client) DeleteWebhook(ctx context.Context, ID string) error {
err := c.delete(ctx, fmt.Sprintf("/webhooks/%s", ID))
return err
}
func ParseHook(body io.ReadCloser) ([]Event, error) {
ed := EventData{}
if err := json.NewDecoder(body).Decode(&ed); err != nil {
return nil, errors.New("Cannot decode payload")
}
return ed.Events, nil
}
// SecretsVerifier contains the information needed to verify that the request comes from Asana
type SecretsVerifier struct {
signature []byte
hmac hash.Hash
}
// NewSecretsVerifier returns a SecretsVerifier object in exchange for an http.Header object and signing secret
func NewSecretsVerifier(header http.Header, secret string) (sv SecretsVerifier, err error) {
var bsignature []byte
signature := header.Get(hSignature)
if signature == "" {
return SecretsVerifier{}, errors.New("Missing header")
}
if bsignature, err = hex.DecodeString(signature); err != nil {
return SecretsVerifier{}, err
}
hash := hmac.New(sha256.New, []byte(secret))
return SecretsVerifier{
signature: bsignature,
hmac: hash,
}, nil
}
func (v *SecretsVerifier) Write(body []byte) (n int, err error) {
return v.hmac.Write(body)
}
// Ensure compares the signature sent from Slack with the actual computed hash to judge validity
func (v SecretsVerifier) Ensure() error {
computed := v.hmac.Sum(nil)
if hmac.Equal(computed, v.signature) {
return nil
}
return fmt.Errorf("Computed unexpected signature of: %s", hex.EncodeToString(computed))
}