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