forked from chinmina/chinmina-bridge
-
Notifications
You must be signed in to change notification settings - Fork 0
/
handlers.go
143 lines (120 loc) · 4.3 KB
/
handlers.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
package main
import (
"encoding/json"
"io"
"net/http"
"strings"
"github.com/jamestelfer/chinmina-bridge/internal/credentialhandler"
"github.com/jamestelfer/chinmina-bridge/internal/jwt"
"github.com/jamestelfer/chinmina-bridge/internal/vendor"
"github.com/rs/zerolog/log"
)
func handlePostToken(tokenVendor vendor.PipelineTokenVendor) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer drainRequestBody(r)
// claims must be present from the middleware
claims := jwt.RequireBuildkiteClaimsFromContext(r.Context())
tokenResponse, err := tokenVendor(r.Context(), claims, "")
if err != nil {
log.Info().Msgf("token creation failed %v\n", err)
requestError(w, http.StatusInternalServerError)
return
}
// write the reponse to the client as JSON, supplying the token and URL
// of the repository it's vended for.
marshalledResponse, err := json.Marshal(tokenResponse)
if err != nil {
requestError(w, http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(marshalledResponse)
if err != nil {
// record failure to log: trying to respond to the client at this
// point will likely fail
log.Info().Msgf("failed to write response: %v\n", err)
return
}
})
}
func handlePostGitCredentials(tokenVendor vendor.PipelineTokenVendor) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer drainRequestBody(r)
// claims must be present from the middleware
claims := jwt.RequireBuildkiteClaimsFromContext(r.Context())
requestedRepo, err := credentialhandler.ReadProperties(r.Body)
if err != nil {
log.Info().Msgf("read repository properties from client failed %v\n", err)
requestError(w, http.StatusInternalServerError)
return
}
requestedRepoURL, err := credentialhandler.ConstructRepositoryURL(requestedRepo)
if err != nil {
log.Info().Msgf("invalid request parameters %v\n", err)
requestError(w, http.StatusBadRequest)
return
}
tokenResponse, err := tokenVendor(r.Context(), claims, requestedRepoURL)
if err != nil {
log.Info().Msgf("token creation failed %v\n", err)
requestError(w, http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/plain")
// Given repository doesn't match the pipeline: empty return this means
// that we understand the request but cannot fulfil it: this is a
// successful case for a credential helper, so we successfully return
// but don't offer credentials.
if tokenResponse == nil {
w.Header().Add("Content-Length", "0")
w.WriteHeader(http.StatusOK)
return
}
// write the reponse to the client in git credentials property format
tokenURL, err := tokenResponse.URL()
if err != nil {
log.Info().Msgf("invalid repo URL: %v\n", err)
requestError(w, http.StatusInternalServerError)
return
}
props := credentialhandler.NewMap(6)
props.Set("protocol", tokenURL.Scheme)
props.Set("host", tokenURL.Host)
props.Set("path", strings.TrimPrefix(tokenURL.Path, "/"))
props.Set("username", "x-access-token")
props.Set("password", tokenResponse.Token)
props.Set("password_expiry_utc", tokenResponse.ExpiryUnix())
err = credentialhandler.WriteProperties(props, w)
if err != nil {
log.Info().Msgf("failed to write response: %v\n", err)
requestError(w, http.StatusInternalServerError)
return
}
})
}
func handleHealthCheck() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer drainRequestBody(r)
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
}
func maxRequestSize(limit int64) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.MaxBytesHandler(next, limit)
}
}
func requestError(w http.ResponseWriter, statusCode int) {
http.Error(w, http.StatusText(statusCode), statusCode)
}
// drainRequestBody drains the request body by reading and discarding the contents.
// This is useful to ensure the request body is fully consumed, which is important
// for connection reuse in HTTP/1 clients.
func drainRequestBody(r *http.Request) {
if r.Body != nil {
// 5kb max: after this we'll assume the client is broken or malicious
// and close the connection
io.CopyN(io.Discard, r.Body, 5*1024*1024)
}
}