generated from mattermost/mattermost-plugin-starter-template
-
Notifications
You must be signed in to change notification settings - Fork 29
/
upstream.go
132 lines (111 loc) · 3.46 KB
/
upstream.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
// Copyright (c) 2020-present Mattermost, Inc. All Rights Reserved.
// See License for license information.
package uphttp
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/pkg/errors"
"github.com/mattermost/mattermost-plugin-apps/apps"
"github.com/mattermost/mattermost-plugin-apps/server/httpout"
"github.com/mattermost/mattermost-plugin-apps/upstream"
"github.com/mattermost/mattermost-plugin-apps/utils"
"github.com/mattermost/mattermost-plugin-apps/utils/httputils"
)
type Upstream struct {
httpOut httpout.Service
appRootURL func(_ apps.App, path string) (string, error)
devMode bool
}
var _ upstream.Upstream = (*Upstream)(nil)
func NewUpstream(httpOut httpout.Service, devMode bool, appRootURL func(apps.App, string) (string, error)) *Upstream {
if appRootURL == nil {
appRootURL = AppRootURL
}
return &Upstream{
httpOut: httpOut,
appRootURL: appRootURL,
devMode: devMode,
}
}
func AppRootURL(app apps.App, _ string) (string, error) {
if !app.Manifest.Contains(apps.DeployHTTP) {
return "", errors.New("failed to get root URL: no http section in manifest.json")
}
return app.Manifest.HTTP.RootURL, nil
}
func (u *Upstream) Roundtrip(ctx context.Context, app apps.App, creq apps.CallRequest, async bool) (io.ReadCloser, error) {
if async {
go func() {
resp, _ := u.invoke(context.Background(), creq.Context.ExpandedContext.BotUserID, app, creq)
if resp != nil {
resp.Body.Close()
}
}()
return nil, nil
}
var actingUserID string
if creq.Context.ExpandedContext.ActingUser != nil {
actingUserID = creq.Context.ExpandedContext.ActingUser.Id
}
resp, err := u.invoke(ctx, actingUserID, app, creq) // nolint:bodyclose
if err != nil {
return nil, errors.Wrap(err, "failed to invoke via HTTP")
}
return resp.Body, nil
}
func (u *Upstream) invoke(ctx context.Context, fromMattermostUserID string, app apps.App, creq apps.CallRequest) (*http.Response, error) {
rootURL, err := u.appRootURL(app, creq.Path)
if err != nil {
return nil, err
}
callURL, err := utils.CleanURL(rootURL + "/" + creq.Path)
if err != nil {
return nil, err
}
data, err := json.Marshal(creq)
if err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, callURL, bytes.NewReader(data))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json")
// TODO: find a better way to control the use of JWT that both OpenFaaS and
// HTTP can share. For now, hard-limit the use of JWT to the HTTP gateway
// itself.
if app.Manifest.Contains(apps.DeployHTTP) && app.Manifest.HTTP.UseJWT {
jwtoken := ""
jwtoken, err = createJWT(fromMattermostUserID, app.Secret)
if err != nil {
return nil, err
}
req.Header.Set(apps.OutgoingAuthHeader, "Bearer "+jwtoken)
}
// Execute the request.
resp, err := u.httpOut.MakeClient(u.devMode).Do(req)
switch {
case err != nil:
return nil, err
case resp.StatusCode == http.StatusNotFound:
return nil, utils.NewNotFoundError(callURL)
case resp.StatusCode != http.StatusOK:
bb, _ := httputils.ReadAndClose(resp.Body)
return nil, errors.New(string(bb))
}
return resp, nil
}
func createJWT(actingUserID, secret string) (string, error) {
claims := apps.JWTClaims{
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * 15).Unix(),
},
ActingUserID: actingUserID,
}
return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret))
}