From e334759874fd8f5b7920091cb933984d4091d09a Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Thu, 26 Nov 2020 10:33:46 +0100 Subject: [PATCH] implement basic auth cache --- proxy/pkg/middleware/basic_auth.go | 111 ++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 34 deletions(-) diff --git a/proxy/pkg/middleware/basic_auth.go b/proxy/pkg/middleware/basic_auth.go index 58e0d7a087..19f0a63e44 100644 --- a/proxy/pkg/middleware/basic_auth.go +++ b/proxy/pkg/middleware/basic_auth.go @@ -1,9 +1,13 @@ package middleware import ( + "crypto/sha1" "fmt" + "github.com/owncloud/ocis/proxy/pkg/cache" "net/http" "strings" + "sync" + "time" accounts "github.com/owncloud/ocis/accounts/pkg/proto/v0" "github.com/owncloud/ocis/ocis-pkg/log" @@ -15,60 +19,99 @@ const publicFilesEndpoint = "/remote.php/dav/public-files/" // BasicAuth provides a middleware to check if BasicAuth is provided func BasicAuth(optionSetters ...Option) func(next http.Handler) http.Handler { options := newOptions(optionSetters...) + logger := options.Logger + oidcIss := options.OIDCIss + accountsCache := cache.NewCache(cache.Size(5000)) if options.EnableBasicAuth { options.Logger.Warn().Msg("basic auth enabled, use only for testing or development") } + h := basicAuth{ + logger: logger, + enabled: options.EnableBasicAuth, + accountsClient: options.AccountsClient, + accountsCache: &accountsCache, + } + return func(next http.Handler) http.Handler { - return &basicAuth{ - next: next, - logger: options.Logger, - enabled: options.EnableBasicAuth, - accountsClient: options.AccountsClient, - oidcIss: options.OIDCIss, - } + return http.HandlerFunc( + func(w http.ResponseWriter, req *http.Request) { + if h.isPublicLink(req) || !h.isBasicAuth(req) { + next.ServeHTTP(w, req) + return + } + + account, ok := h.getAccount(req) + + if !ok { + w.WriteHeader(http.StatusUnauthorized) + return + } + + claims := &oidc.StandardClaims{ + OcisID: account.Id, + Iss: oidcIss, + } + + next.ServeHTTP(w, req.WithContext(oidc.NewContext(req.Context(), claims))) + }, + ) } } type basicAuth struct { - next http.Handler logger log.Logger enabled bool accountsClient accounts.AccountsService - oidcIss string + accountsCache *cache.Cache + m sync.Mutex } -func (m basicAuth) ServeHTTP(w http.ResponseWriter, req *http.Request) { - if m.isPublicLink(req) || !m.isBasicAuth(req) { - m.next.ServeHTTP(w, req) - return - } - - login, password, _ := req.BasicAuth() - - account, status := getAccount(m.logger, m.accountsClient, fmt.Sprintf("login eq '%s' and password eq '%s'", strings.ReplaceAll(login, "'", "''"), strings.ReplaceAll(password, "'", "''"))) - - if status != 0 { - w.WriteHeader(http.StatusUnauthorized) - return - } - - claims := &oidc.StandardClaims{ - OcisID: account.Id, - Iss: m.oidcIss, - } - - m.next.ServeHTTP(w, req.WithContext(oidc.NewContext(req.Context(), claims))) -} - -func (m basicAuth) isPublicLink(req *http.Request) bool { +func (m *basicAuth) isPublicLink(req *http.Request) bool { login, _, ok := req.BasicAuth() return ok && login == "public" && strings.HasPrefix(req.URL.Path, publicFilesEndpoint) } -func (m basicAuth) isBasicAuth(req *http.Request) bool { +func (m *basicAuth) isBasicAuth(req *http.Request) bool { login, password, ok := req.BasicAuth() return m.enabled && ok && login != "" && password != "" } + +func (m *basicAuth) getAccount(req *http.Request) (*accounts.Account, bool) { + var ok bool + var hit *cache.Entry + + login, password, _ := req.BasicAuth() + + h := sha1.New() + h.Write([]byte(login)) + + lookup := fmt.Sprintf("%x", h.Sum([]byte(password))) + + m.m.Lock() + defer m.m.Unlock() + + if hit = m.accountsCache.Get(lookup); hit == nil { + account, status := getAccount( + m.logger, + m.accountsClient, + fmt.Sprintf( + "login eq '%s' and password eq '%s'", + strings.ReplaceAll(login, "'", "''"), + strings.ReplaceAll(password, "'", "''"), + ), + ) + + if ok = status == 0; ok { + m.accountsCache.Set(lookup, account, time.Now().Add(10*time.Minute)) + } + + return account, ok + } + + account, ok := hit.V.(*accounts.Account) + + return account, ok +}