Commit d33010bb authored by Kirill Smelkov's avatar Kirill Smelkov

X first cut with query

parent 87de16ef
...@@ -48,6 +48,13 @@ type AuthCacheEntry struct { ...@@ -48,6 +48,13 @@ type AuthCacheEntry struct {
ready chan struct{} // closed when entry is ready ready chan struct{} // closed when entry is ready
} }
// Entries are keyed by project + credentials
type AuthCacheKey struct {
project string
query string // e.g. with passing in private_token=...
// TODO headers
}
// Authorization reply cache // Authorization reply cache
// {} project -> AuthCacheEntry // {} project -> AuthCacheEntry
// //
...@@ -56,22 +63,22 @@ type AuthCache struct { ...@@ -56,22 +63,22 @@ type AuthCache struct {
u *upstream // for which upstream we cache auth u *upstream // for which upstream we cache auth
mu sync.RWMutex // guards .cached mu sync.RWMutex // guards .cached
cached map[string]*AuthCacheEntry cached map[AuthCacheKey]*AuthCacheEntry
} }
func NewAuthCache(u *upstream) *AuthCache { func NewAuthCache(u *upstream) *AuthCache {
return &AuthCache{u: u, cached: make(map[string]*AuthCacheEntry)} return &AuthCache{u: u, cached: make(map[AuthCacheKey]*AuthCacheEntry)}
} }
// Verify that download access is ok or not. // Verify that download access is ok or not.
// first we try to use the cache; if information is not there -> ask auth backend // first we try to use the cache; if information is not there -> ask auth backend
// download is ok if AuthReply.RepoPath != "" // download is ok if AuthReply.RepoPath != ""
func (c *AuthCache) VerifyDownloadAccess(project string) AuthReply { func (c *AuthCache) VerifyDownloadAccess(key AuthCacheKey) AuthReply {
var authReply AuthReply var authReply AuthReply
// first try to read from cache in parallel with other readers // first try to read from cache in parallel with other readers
c.mu.RLock() c.mu.RLock()
auth := c.cached[project] auth := c.cached[key]
c.mu.RUnlock() c.mu.RUnlock()
have_entry: have_entry:
...@@ -82,7 +89,7 @@ have_entry: ...@@ -82,7 +89,7 @@ have_entry:
auth.Lock() auth.Lock()
auth.Nhit++ auth.Nhit++
//log.Printf("authReply for %v cached ago: %v (hits: %v)", //log.Printf("authReply for %v cached ago: %v (hits: %v)",
// project, // key,
// time.Since(time.Unix(auth.Tauth, 0)), // time.Since(time.Unix(auth.Tauth, 0)),
// auth.Nhit) // auth.Nhit)
authReply = auth.AuthReply authReply = auth.AuthReply
...@@ -95,7 +102,7 @@ have_entry: ...@@ -95,7 +102,7 @@ have_entry:
c.mu.Lock() c.mu.Lock()
// another ex-reader could be trying to create this entry // another ex-reader could be trying to create this entry
// simultaneously with us - recheck // simultaneously with us - recheck
auth = c.cached[project] auth = c.cached[key]
if auth != nil { if auth != nil {
c.mu.Unlock() c.mu.Unlock()
goto have_entry goto have_entry
...@@ -103,11 +110,11 @@ have_entry: ...@@ -103,11 +110,11 @@ have_entry:
// new not-yet-ready entry // new not-yet-ready entry
auth = &AuthCacheEntry{ready: make(chan struct{})} auth = &AuthCacheEntry{ready: make(chan struct{})}
c.cached[project] = auth c.cached[key] = auth
c.mu.Unlock() c.mu.Unlock()
// this goroutine becomes responsible for quering auth backend // this goroutine becomes responsible for quering auth backend
auth.AuthReply = askAuthBackend(c.u, project) auth.AuthReply = askAuthBackend(c.u, key.project, key.query)
auth.Tauth = time.Now().Unix() auth.Tauth = time.Now().Unix()
auth.Nhit = 0 auth.Nhit = 0
...@@ -117,18 +124,18 @@ have_entry: ...@@ -117,18 +124,18 @@ have_entry:
close(auth.ready) close(auth.ready)
// launch entry refresher // launch entry refresher
go c.refreshEntry(auth, project) go c.refreshEntry(auth, key)
} }
return authReply return authReply
} }
// Time period for refreshing / removing unused entires in authCache // Time period for refreshing / removing unused entires in authCache
const authCacheRefresh = 30 * time.Second const authCacheRefresh = 5 * time.Second
// Goroutine to refresh auth cache entry periodically while it is used. // Goroutine to refresh auth cache entry periodically while it is used.
// if the entry is detected to be not used - remove it from cache and stop refreshing. // if the entry is detected to be not used - remove it from cache and stop refreshing.
func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, project string) { func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, key AuthCacheKey) {
for { for {
time.Sleep(authCacheRefresh) time.Sleep(authCacheRefresh)
...@@ -137,19 +144,19 @@ func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, project string) { ...@@ -137,19 +144,19 @@ func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, project string) {
auth.Unlock() auth.Unlock()
// clear cache entry if it is not used // clear cache entry if it is not used
//log.Printf("AUTH refresh - %v #hit: %v", project, nhit) log.Printf("AUTH refresh - %v #hit: %v", key, nhit)
if nhit == 0 { // not used - we can remove and stop refreshing if nhit == 0 { // not used - we can remove and stop refreshing
//log.Printf("AUTH - removing %v", project) log.Printf("AUTH - removing %v", key)
// NOTE it is ok even if someone gets this auth in this time window // NOTE it is ok even if someone gets this auth in this time window
// and use it for some time // and use it for some time
c.mu.Lock() c.mu.Lock()
delete(c.cached, project) delete(c.cached, key)
c.mu.Unlock() c.mu.Unlock()
break break
} }
//log.Printf("AUTH - refreshing %v", project) log.Printf("AUTH - refreshing %v", key)
authReply := askAuthBackend(c.u, project) authReply := askAuthBackend(c.u, key.project, key.query)
auth.Lock() auth.Lock()
auth.AuthReply = authReply auth.AuthReply = authReply
...@@ -162,7 +169,7 @@ func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, project string) { ...@@ -162,7 +169,7 @@ func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, project string) {
// Ask auth backend about whether download is ok for a project. // Ask auth backend about whether download is ok for a project.
// Authorization is approved if AuthReply.RepoPath != "" on return // Authorization is approved if AuthReply.RepoPath != "" on return
// Raw auth backend response is emitted to AuthReply.RawReply // Raw auth backend response is emitted to AuthReply.RawReply
func askAuthBackend(u *upstream, project string) AuthReply { func askAuthBackend(u *upstream, project, query string) AuthReply {
authReply := AuthReply{ authReply := AuthReply{
RawReply: httptest.NewRecorder(), RawReply: httptest.NewRecorder(),
} }
...@@ -174,8 +181,12 @@ func askAuthBackend(u *upstream, project string) AuthReply { ...@@ -174,8 +181,12 @@ func askAuthBackend(u *upstream, project string) AuthReply {
// get repo archive XXX // get repo archive XXX
// XXX private_token not propagated, etc ... // XXX private_token not propagated, etc ...
// XXX PRIVATE-TOKEN (in header) // XXX PRIVATE-TOKEN (in header)
//reqDownloadAccess, err := http.NewRequest("GET", project+".git/info/refs?service=git-upload-pack", nil) // url := project+".git/info/refs?service=git-upload-pack"
reqDownloadAccess, err := http.NewRequest("GET", project+"/repository/archive.zip", nil) url := project+"/repository/archive.zip"
if query != "" {
url += "?"+query
}
reqDownloadAccess, err := http.NewRequest("GET", url, nil)
if err != nil { if err != nil {
fail500(authReply.RawReply, "GET git-upload-pack", err) fail500(authReply.RawReply, "GET git-upload-pack", err)
return authReply return authReply
...@@ -199,8 +210,8 @@ func askAuthBackend(u *upstream, project string) AuthReply { ...@@ -199,8 +210,8 @@ func askAuthBackend(u *upstream, project string) AuthReply {
return authReply return authReply
} }
func verifyDownloadAccess(u *upstream, project string) AuthReply { func verifyDownloadAccess(u *upstream, project, query string) AuthReply {
return u.authCache.VerifyDownloadAccess(project) return u.authCache.VerifyDownloadAccess(AuthCacheKey{project, query})
} }
// HTTP handler for `.../raw/<ref>/path` // HTTP handler for `.../raw/<ref>/path`
...@@ -209,16 +220,17 @@ var rawRe = regexp.MustCompile(`/raw/`) ...@@ -209,16 +220,17 @@ var rawRe = regexp.MustCompile(`/raw/`)
func handleGetBlobRaw(w http.ResponseWriter, r *gitRequest) { func handleGetBlobRaw(w http.ResponseWriter, r *gitRequest) {
// Extract project & refpath // Extract project & refpath
// <project>/raw/branch/file -> <project>, branch/file // <project>/raw/branch/file -> <project>, branch/file
rawLoc := rawRe.FindStringIndex(r.Request.URL.Path) url := r.Request.URL
rawLoc := rawRe.FindStringIndex(url.Path)
if rawLoc == nil { if rawLoc == nil {
fail500(w, "extract project name", nil) // XXX err=nil fail500(w, "extract project name", nil) // XXX err=nil
return return
} }
project := r.Request.URL.Path[:rawLoc[0]] project := url.Path[:rawLoc[0]]
refpath := r.Request.URL.Path[rawLoc[1]:] refpath := url.Path[rawLoc[1]:]
// Query download access auth for this project // Query download access auth for this project
authReply := verifyDownloadAccess(r.u, project) authReply := verifyDownloadAccess(r.u, project, url.RawQuery)
if authReply.RepoPath == "" { if authReply.RepoPath == "" {
// access denied - copy auth reply to client in full - // access denied - copy auth reply to client in full -
// there are HTTP code and other headers / body relevant for // there are HTTP code and other headers / body relevant for
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment