8460: Merge branch 'master' into 8460-websocket-go
[arvados.git] / services / ws / permission.go
1 package main
2
3 import (
4         "net/http"
5         "net/url"
6         "time"
7
8         "git.curoverse.com/arvados.git/sdk/go/arvados"
9 )
10
11 const (
12         maxPermCacheAge = time.Hour
13         minPermCacheAge = 5 * time.Minute
14 )
15
16 type permChecker interface {
17         SetToken(token string)
18         Check(uuid string) (bool, error)
19 }
20
21 func NewPermChecker(ac arvados.Client) permChecker {
22         ac.AuthToken = ""
23         return &cachingPermChecker{
24                 Client:     &ac,
25                 cache:      make(map[string]time.Time),
26                 maxCurrent: 16,
27         }
28 }
29
30 type cachingPermChecker struct {
31         *arvados.Client
32         cache      map[string]time.Time
33         maxCurrent int
34 }
35
36 func (pc *cachingPermChecker) SetToken(token string) {
37         pc.Client.AuthToken = token
38 }
39
40 func (pc *cachingPermChecker) Check(uuid string) (bool, error) {
41         pc.tidy()
42         if t, ok := pc.cache[uuid]; ok && time.Now().Sub(t) < maxPermCacheAge {
43                 debugLogf("perm ok (cached): %+q %+q", pc.Client.AuthToken, uuid)
44                 return true, nil
45         }
46         var buf map[string]interface{}
47         path, err := pc.PathForUUID("get", uuid)
48         if err != nil {
49                 return false, err
50         }
51         err = pc.RequestAndDecode(&buf, "GET", path, nil, url.Values{
52                 "select": {`["uuid"]`},
53         })
54         if err, ok := err.(arvados.TransactionError); ok && err.StatusCode == http.StatusNotFound {
55                 debugLogf("perm err: %+q %+q: %s", pc.Client.AuthToken, uuid, err)
56                 return false, nil
57         }
58         if err != nil {
59                 debugLogf("perm !ok: %+q %+q", pc.Client.AuthToken, uuid)
60                 return false, err
61         }
62         debugLogf("perm ok: %+q %+q", pc.Client.AuthToken, uuid)
63         pc.cache[uuid] = time.Now()
64         return true, nil
65 }
66
67 func (pc *cachingPermChecker) tidy() {
68         if len(pc.cache) <= pc.maxCurrent*2 {
69                 return
70         }
71         tooOld := time.Now().Add(-minPermCacheAge)
72         for uuid, t := range pc.cache {
73                 if t.Before(tooOld) {
74                         delete(pc.cache, uuid)
75                 }
76         }
77         pc.maxCurrent = len(pc.cache)
78 }