X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/5a420beeb6c64efc3ca0ef13d4ab9ac6c654c3ab..aba81749d2477043232b82300c0ce15548b61888:/services/ws/permission.go diff --git a/services/ws/permission.go b/services/ws/permission.go index b2b962c7ce..f71ca61167 100644 --- a/services/ws/permission.go +++ b/services/ws/permission.go @@ -1,11 +1,17 @@ -package main +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +package ws import ( + "context" "net/http" "net/url" "time" - "git.curoverse.com/arvados.git/sdk/go/arvados" + "git.arvados.org/arvados.git/sdk/go/arvados" + "git.arvados.org/arvados.git/sdk/go/ctxlog" ) const ( @@ -15,53 +21,94 @@ const ( type permChecker interface { SetToken(token string) - Check(uuid string) (bool, error) + Check(ctx context.Context, uuid string) (bool, error) } -func NewPermChecker(ac arvados.Client) permChecker { - ac.AuthToken = "" +func newPermChecker(ac *arvados.Client) permChecker { return &cachingPermChecker{ - Client: &ac, - cache: make(map[string]time.Time), + ac: ac, + token: "-", + cache: make(map[string]cacheEnt), maxCurrent: 16, } } +type cacheEnt struct { + time.Time + allowed bool +} + type cachingPermChecker struct { - *arvados.Client - cache map[string]time.Time + ac *arvados.Client + token string + cache map[string]cacheEnt maxCurrent int + + nChecks uint64 + nMisses uint64 + nInvalid uint64 } func (pc *cachingPermChecker) SetToken(token string) { - pc.Client.AuthToken = token + if pc.token == token { + return + } + pc.token = token + pc.cache = make(map[string]cacheEnt) } -func (pc *cachingPermChecker) Check(uuid string) (bool, error) { +func (pc *cachingPermChecker) Check(ctx context.Context, uuid string) (bool, error) { + pc.nChecks++ + logger := ctxlog.FromContext(ctx). + WithField("token", pc.token). + WithField("uuid", uuid) pc.tidy() - if t, ok := pc.cache[uuid]; ok && time.Now().Sub(t) < maxPermCacheAge { - debugLogf("perm ok (cached): %+q %+q", pc.Client.AuthToken, uuid) - return true, nil + now := time.Now() + if perm, ok := pc.cache[uuid]; ok && now.Sub(perm.Time) < maxPermCacheAge { + logger.WithField("allowed", perm.allowed).Debug("cache hit") + return perm.allowed, nil } - var buf map[string]interface{} - path, err := pc.PathForUUID("get", uuid) + + path, err := pc.ac.PathForUUID("get", uuid) if err != nil { + pc.nInvalid++ return false, err } - err = pc.RequestAndDecode(&buf, "GET", path, nil, url.Values{ - "select": {`["uuid"]`}, + + pc.nMisses++ + ctx = arvados.ContextWithAuthorization(ctx, "Bearer "+pc.token) + ctx, cancel := context.WithDeadline(ctx, time.Now().Add(time.Minute)) + defer cancel() + var buf map[string]interface{} + err = pc.ac.RequestAndDecodeContext(ctx, &buf, "GET", path, nil, url.Values{ + "include_trash": {"true"}, + "select": {`["uuid"]`}, }) - if err, ok := err.(arvados.TransactionError); ok && err.StatusCode == http.StatusNotFound { - debugLogf("perm err: %+q %+q: %s", pc.Client.AuthToken, uuid, err) - return false, nil - } - if err != nil { - debugLogf("perm !ok: %+q %+q", pc.Client.AuthToken, uuid) + + var allowed bool + if err == nil { + allowed = true + } else if txErr, ok := err.(*arvados.TransactionError); ok && pc.isNotAllowed(txErr.StatusCode) { + allowed = false + } else { + // If "context deadline exceeded", "client + // disconnected", HTTP 5xx, network error, etc., don't + // cache the result. + logger.WithError(err).Error("lookup error") return false, err } - debugLogf("perm ok: %+q %+q", pc.Client.AuthToken, uuid) - pc.cache[uuid] = time.Now() - return true, nil + logger.WithField("allowed", allowed).Debug("cache miss") + pc.cache[uuid] = cacheEnt{Time: now, allowed: allowed} + return allowed, nil +} + +func (pc *cachingPermChecker) isNotAllowed(status int) bool { + switch status { + case http.StatusForbidden, http.StatusUnauthorized, http.StatusNotFound: + return true + default: + return false + } } func (pc *cachingPermChecker) tidy() {