16306: Merge branch 'master'
[arvados.git] / lib / controller / rpc / conn.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package rpc
6
7 import (
8         "bytes"
9         "context"
10         "crypto/tls"
11         "encoding/json"
12         "errors"
13         "fmt"
14         "io"
15         "net"
16         "net/http"
17         "net/url"
18         "strconv"
19         "strings"
20         "time"
21
22         "git.arvados.org/arvados.git/sdk/go/arvados"
23         "git.arvados.org/arvados.git/sdk/go/auth"
24 )
25
26 type TokenProvider func(context.Context) ([]string, error)
27
28 func PassthroughTokenProvider(ctx context.Context) ([]string, error) {
29         incoming, ok := auth.FromContext(ctx)
30         if !ok {
31                 return nil, errors.New("no token provided")
32         }
33         return incoming.Tokens, nil
34 }
35
36 type Conn struct {
37         SendHeader    http.Header
38         clusterID     string
39         httpClient    http.Client
40         baseURL       url.URL
41         tokenProvider TokenProvider
42 }
43
44 func NewConn(clusterID string, url *url.URL, insecure bool, tp TokenProvider) *Conn {
45         transport := http.DefaultTransport
46         if insecure {
47                 // It's not safe to copy *http.DefaultTransport
48                 // because it has a mutex (which might be locked)
49                 // protecting a private map (which might not be nil).
50                 // So we build our own, using the Go 1.12 default
51                 // values, ignoring any changes the application has
52                 // made to http.DefaultTransport.
53                 transport = &http.Transport{
54                         DialContext: (&net.Dialer{
55                                 Timeout:   30 * time.Second,
56                                 KeepAlive: 30 * time.Second,
57                                 DualStack: true,
58                         }).DialContext,
59                         MaxIdleConns:          100,
60                         IdleConnTimeout:       90 * time.Second,
61                         TLSHandshakeTimeout:   10 * time.Second,
62                         ExpectContinueTimeout: 1 * time.Second,
63                         TLSClientConfig:       &tls.Config{InsecureSkipVerify: true},
64                 }
65         }
66         return &Conn{
67                 clusterID: clusterID,
68                 httpClient: http.Client{
69                         CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
70                         Transport:     transport,
71                 },
72                 baseURL:       *url,
73                 tokenProvider: tp,
74         }
75 }
76
77 func (conn *Conn) requestAndDecode(ctx context.Context, dst interface{}, ep arvados.APIEndpoint, body io.Reader, opts interface{}) error {
78         aClient := arvados.Client{
79                 Client:     &conn.httpClient,
80                 Scheme:     conn.baseURL.Scheme,
81                 APIHost:    conn.baseURL.Host,
82                 SendHeader: conn.SendHeader,
83         }
84         tokens, err := conn.tokenProvider(ctx)
85         if err != nil {
86                 return err
87         } else if len(tokens) > 0 {
88                 ctx = arvados.ContextWithAuthorization(ctx, "Bearer "+tokens[0])
89         } else {
90                 // Use a non-empty auth string to ensure we override
91                 // any default token set on aClient -- and to avoid
92                 // having the remote prompt us to send a token by
93                 // responding 401.
94                 ctx = arvados.ContextWithAuthorization(ctx, "Bearer -")
95         }
96
97         // Encode opts to JSON and decode from there to a
98         // map[string]interface{}, so we can munge the query params
99         // using the JSON key names specified by opts' struct tags.
100         j, err := json.Marshal(opts)
101         if err != nil {
102                 return fmt.Errorf("%T: requestAndDecode: Marshal opts: %s", conn, err)
103         }
104         var params map[string]interface{}
105         dec := json.NewDecoder(bytes.NewBuffer(j))
106         dec.UseNumber()
107         err = dec.Decode(&params)
108         if err != nil {
109                 return fmt.Errorf("%T: requestAndDecode: Decode opts: %s", conn, err)
110         }
111         if attrs, ok := params["attrs"]; ok && ep.AttrsKey != "" {
112                 params[ep.AttrsKey] = attrs
113                 delete(params, "attrs")
114         }
115         if limitStr, ok := params["limit"]; ok {
116                 if limit, err := strconv.ParseInt(string(limitStr.(json.Number)), 10, 64); err == nil && limit < 0 {
117                         // Negative limit means "not specified" here, but some
118                         // servers/versions do not accept that, so we need to
119                         // remove it entirely.
120                         delete(params, "limit")
121                 }
122         }
123         if len(tokens) > 1 {
124                 params["reader_tokens"] = tokens[1:]
125         }
126         path := ep.Path
127         if strings.Contains(ep.Path, "/{uuid}") {
128                 uuid, _ := params["uuid"].(string)
129                 path = strings.Replace(path, "/{uuid}", "/"+uuid, 1)
130                 delete(params, "uuid")
131         }
132         return aClient.RequestAndDecodeContext(ctx, dst, ep.Method, path, body, params)
133 }
134
135 func (conn *Conn) BaseURL() url.URL {
136         return conn.baseURL
137 }
138
139 func (conn *Conn) ConfigGet(ctx context.Context) (json.RawMessage, error) {
140         ep := arvados.EndpointConfigGet
141         var resp json.RawMessage
142         err := conn.requestAndDecode(ctx, &resp, ep, nil, nil)
143         return resp, err
144 }
145
146 func (conn *Conn) Login(ctx context.Context, options arvados.LoginOptions) (arvados.LoginResponse, error) {
147         ep := arvados.EndpointLogin
148         var resp arvados.LoginResponse
149         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
150         resp.RedirectLocation = conn.relativeToBaseURL(resp.RedirectLocation)
151         return resp, err
152 }
153
154 func (conn *Conn) Logout(ctx context.Context, options arvados.LogoutOptions) (arvados.LogoutResponse, error) {
155         ep := arvados.EndpointLogout
156         var resp arvados.LogoutResponse
157         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
158         resp.RedirectLocation = conn.relativeToBaseURL(resp.RedirectLocation)
159         return resp, err
160 }
161
162 // If the given location is a valid URL and its origin is the same as
163 // conn.baseURL, return it as a relative URL. Otherwise, return it
164 // unmodified.
165 func (conn *Conn) relativeToBaseURL(location string) string {
166         u, err := url.Parse(location)
167         if err == nil && u.Scheme == conn.baseURL.Scheme && strings.ToLower(u.Host) == strings.ToLower(conn.baseURL.Host) {
168                 u.Opaque = ""
169                 u.Scheme = ""
170                 u.User = nil
171                 u.Host = ""
172                 return u.String()
173         }
174         return location
175 }
176
177 func (conn *Conn) CollectionCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Collection, error) {
178         ep := arvados.EndpointCollectionCreate
179         var resp arvados.Collection
180         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
181         return resp, err
182 }
183
184 func (conn *Conn) CollectionUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Collection, error) {
185         ep := arvados.EndpointCollectionUpdate
186         var resp arvados.Collection
187         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
188         return resp, err
189 }
190
191 func (conn *Conn) CollectionGet(ctx context.Context, options arvados.GetOptions) (arvados.Collection, error) {
192         ep := arvados.EndpointCollectionGet
193         var resp arvados.Collection
194         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
195         return resp, err
196 }
197
198 func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
199         ep := arvados.EndpointCollectionList
200         var resp arvados.CollectionList
201         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
202         return resp, err
203 }
204
205 func (conn *Conn) CollectionProvenance(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
206         ep := arvados.EndpointCollectionProvenance
207         var resp map[string]interface{}
208         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
209         return resp, err
210 }
211
212 func (conn *Conn) CollectionUsedBy(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
213         ep := arvados.EndpointCollectionUsedBy
214         var resp map[string]interface{}
215         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
216         return resp, err
217 }
218
219 func (conn *Conn) CollectionDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Collection, error) {
220         ep := arvados.EndpointCollectionDelete
221         var resp arvados.Collection
222         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
223         return resp, err
224 }
225
226 func (conn *Conn) CollectionTrash(ctx context.Context, options arvados.DeleteOptions) (arvados.Collection, error) {
227         ep := arvados.EndpointCollectionTrash
228         var resp arvados.Collection
229         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
230         return resp, err
231 }
232
233 func (conn *Conn) CollectionUntrash(ctx context.Context, options arvados.UntrashOptions) (arvados.Collection, error) {
234         ep := arvados.EndpointCollectionUntrash
235         var resp arvados.Collection
236         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
237         return resp, err
238 }
239
240 func (conn *Conn) ContainerCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Container, error) {
241         ep := arvados.EndpointContainerCreate
242         var resp arvados.Container
243         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
244         return resp, err
245 }
246
247 func (conn *Conn) ContainerUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Container, error) {
248         ep := arvados.EndpointContainerUpdate
249         var resp arvados.Container
250         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
251         return resp, err
252 }
253
254 func (conn *Conn) ContainerGet(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
255         ep := arvados.EndpointContainerGet
256         var resp arvados.Container
257         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
258         return resp, err
259 }
260
261 func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
262         ep := arvados.EndpointContainerList
263         var resp arvados.ContainerList
264         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
265         return resp, err
266 }
267
268 func (conn *Conn) ContainerDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Container, error) {
269         ep := arvados.EndpointContainerDelete
270         var resp arvados.Container
271         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
272         return resp, err
273 }
274
275 func (conn *Conn) ContainerLock(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
276         ep := arvados.EndpointContainerLock
277         var resp arvados.Container
278         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
279         return resp, err
280 }
281
282 func (conn *Conn) ContainerUnlock(ctx context.Context, options arvados.GetOptions) (arvados.Container, error) {
283         ep := arvados.EndpointContainerUnlock
284         var resp arvados.Container
285         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
286         return resp, err
287 }
288
289 func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
290         ep := arvados.EndpointSpecimenCreate
291         var resp arvados.Specimen
292         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
293         return resp, err
294 }
295
296 func (conn *Conn) SpecimenUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.Specimen, error) {
297         ep := arvados.EndpointSpecimenUpdate
298         var resp arvados.Specimen
299         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
300         return resp, err
301 }
302
303 func (conn *Conn) SpecimenGet(ctx context.Context, options arvados.GetOptions) (arvados.Specimen, error) {
304         ep := arvados.EndpointSpecimenGet
305         var resp arvados.Specimen
306         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
307         return resp, err
308 }
309
310 func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
311         ep := arvados.EndpointSpecimenList
312         var resp arvados.SpecimenList
313         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
314         return resp, err
315 }
316
317 func (conn *Conn) SpecimenDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.Specimen, error) {
318         ep := arvados.EndpointSpecimenDelete
319         var resp arvados.Specimen
320         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
321         return resp, err
322 }
323
324 func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
325         ep := arvados.EndpointUserCreate
326         var resp arvados.User
327         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
328         return resp, err
329 }
330 func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
331         ep := arvados.EndpointUserUpdate
332         var resp arvados.User
333         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
334         return resp, err
335 }
336 func (conn *Conn) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
337         ep := arvados.EndpointUserUpdateUUID
338         var resp arvados.User
339         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
340         return resp, err
341 }
342 func (conn *Conn) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
343         ep := arvados.EndpointUserMerge
344         var resp arvados.User
345         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
346         return resp, err
347 }
348 func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
349         ep := arvados.EndpointUserActivate
350         var resp arvados.User
351         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
352         return resp, err
353 }
354 func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
355         ep := arvados.EndpointUserSetup
356         var resp map[string]interface{}
357         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
358         return resp, err
359 }
360 func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
361         ep := arvados.EndpointUserUnsetup
362         var resp arvados.User
363         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
364         return resp, err
365 }
366 func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
367         ep := arvados.EndpointUserGet
368         var resp arvados.User
369         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
370         return resp, err
371 }
372 func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
373         ep := arvados.EndpointUserGetCurrent
374         var resp arvados.User
375         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
376         return resp, err
377 }
378 func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
379         ep := arvados.EndpointUserGetSystem
380         var resp arvados.User
381         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
382         return resp, err
383 }
384 func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
385         ep := arvados.EndpointUserList
386         var resp arvados.UserList
387         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
388         return resp, err
389 }
390 func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
391         ep := arvados.EndpointUserDelete
392         var resp arvados.User
393         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
394         return resp, err
395 }
396
397 func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
398         ep := arvados.EndpointAPIClientAuthorizationCurrent
399         var resp arvados.APIClientAuthorization
400         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
401         return resp, err
402 }
403
404 type UserSessionAuthInfo struct {
405         Email           string   `json:"email"`
406         AlternateEmails []string `json:"alternate_emails"`
407         FirstName       string   `json:"first_name"`
408         LastName        string   `json:"last_name"`
409         Username        string   `json:"username"`
410 }
411
412 type UserSessionCreateOptions struct {
413         AuthInfo UserSessionAuthInfo `json:"auth_info"`
414         ReturnTo string              `json:"return_to"`
415 }
416
417 func (conn *Conn) UserSessionCreate(ctx context.Context, options UserSessionCreateOptions) (arvados.LoginResponse, error) {
418         ep := arvados.APIEndpoint{Method: "POST", Path: "auth/controller/callback"}
419         var resp arvados.LoginResponse
420         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
421         return resp, err
422 }
423
424 func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
425         ep := arvados.EndpointUserBatchUpdate
426         var resp arvados.UserList
427         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
428         return resp, err
429 }
430
431 func (conn *Conn) UserAuthenticate(ctx context.Context, options arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
432         ep := arvados.EndpointUserAuthenticate
433         var resp arvados.APIClientAuthorization
434         err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
435         return resp, err
436 }