Commit 015c6f5f authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent 86b2a17f
Pipeline #563 failed with stage
...@@ -42,9 +42,10 @@ type AuthCacheEntry struct { ...@@ -42,9 +42,10 @@ type AuthCacheEntry struct {
// Entries are keyed by project + credentials // Entries are keyed by project + credentials
type AuthCacheKey struct { type AuthCacheKey struct {
project string project string
query string // e.g. with passing in private_token=... userinfo string // user[:password] or ""
header string // request header url-encoded, e.g. PRIVATE-TOKEN=... query string // e.g. with passing in private_token=...
header string // request header url-encoded, e.g. PRIVATE-TOKEN=...
} }
// Authorization reply cache // Authorization reply cache
...@@ -63,7 +64,13 @@ func NewAuthCache(a *API) *AuthCache { ...@@ -63,7 +64,13 @@ func NewAuthCache(a *API) *AuthCache {
// 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, query string, header http.Header) AuthReply { func (c *AuthCache) VerifyDownloadAccess(project string, userinfo *url.Userinfo, query string, header http.Header) AuthReply {
// In addition to userinfo:
u := ""
if userinfo != nil {
u = userinfo.String()
}
// Use only tokens from query/header and selected cookies to minimize cache and avoid // Use only tokens from query/header and selected cookies to minimize cache and avoid
// creating redundant cache entries because of e.g. unrelated headers. // creating redundant cache entries because of e.g. unrelated headers.
queryValues, _ := url.ParseQuery(query) // this is what URL.Query() does queryValues, _ := url.ParseQuery(query) // this is what URL.Query() does
...@@ -95,7 +102,7 @@ func (c *AuthCache) VerifyDownloadAccess(project string, query string, header ht ...@@ -95,7 +102,7 @@ func (c *AuthCache) VerifyDownloadAccess(project string, query string, header ht
h["Cookie"] = []string{hc} h["Cookie"] = []string{hc}
} }
key := AuthCacheKey{project, q.Encode(), h.Encode()} key := AuthCacheKey{project, u, q.Encode(), h.Encode()}
return c.verifyDownloadAccess(key) return c.verifyDownloadAccess(key)
} }
...@@ -194,6 +201,18 @@ func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, key AuthCacheKey) { ...@@ -194,6 +201,18 @@ func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, key AuthCacheKey) {
// Ask auth backend about cache key // Ask auth backend about cache key
func (c *AuthCache) askAuthBackend(key AuthCacheKey) AuthReply { func (c *AuthCache) askAuthBackend(key AuthCacheKey) AuthReply {
// key.userinfo -> url.Userinfo
var user *url.Userinfo
if key.userinfo != "" {
u, err := url.Parse(key.userinfo + "@/")
// url prepared-to-parse userinfo must be valid
panic(err)
if (u.User == nil) {
panic(fmt.Errorf("userinfo parse: `%s` -> empty", key.userinfo))
}
user = u.User
}
// key.header -> url.Values -> http.Header // key.header -> url.Values -> http.Header
hv, err := url.ParseQuery(key.header) hv, err := url.ParseQuery(key.header)
if err != nil { if err != nil {
...@@ -206,7 +225,7 @@ func (c *AuthCache) askAuthBackend(key AuthCacheKey) AuthReply { ...@@ -206,7 +225,7 @@ func (c *AuthCache) askAuthBackend(key AuthCacheKey) AuthReply {
header[k] = v header[k] = v
} }
return c.a.verifyDownloadAccess(key.project, key.query, header) return c.a.verifyDownloadAccess(key.project, user, key.query, header)
} }
// Ask auth backend about whether download is ok for a project. // Ask auth backend about whether download is ok for a project.
...@@ -215,11 +234,11 @@ func (c *AuthCache) askAuthBackend(key AuthCacheKey) AuthReply { ...@@ -215,11 +234,11 @@ func (c *AuthCache) askAuthBackend(key AuthCacheKey) AuthReply {
// //
// Replies from authentication backend are cached for 30 seconds as each // Replies from authentication backend are cached for 30 seconds as each
// request to Rails code is heavy and slow. // request to Rails code is heavy and slow.
func (a *API) VerifyDownloadAccess(project, query string, header http.Header) AuthReply { func (a *API) VerifyDownloadAccess(project string, user *url.Userinfo, query string, header http.Header) AuthReply {
return a.authCache.VerifyDownloadAccess(project, query, header) return a.authCache.VerifyDownloadAccess(project, user, query, header)
} }
func (a *API) verifyDownloadAccess(project, query string, header http.Header) AuthReply { func (a *API) verifyDownloadAccess(project string, user *url.Userinfo, query string, header http.Header) AuthReply {
authReply := AuthReply{ authReply := AuthReply{
RawReply: httptest.NewRecorder(), RawReply: httptest.NewRecorder(),
} }
...@@ -230,7 +249,12 @@ func (a *API) verifyDownloadAccess(project, query string, header http.Header) Au ...@@ -230,7 +249,12 @@ func (a *API) verifyDownloadAccess(project, query string, header http.Header) Au
// - that's why we auth backend to authenticate as if it was request to // - that's why we auth backend to authenticate as if it was request to
// get repo archive and propagate request query and header. // get repo archive and propagate request query and header.
// url := project + ".git/info/refs?service=git-upload-pack" // url := project + ".git/info/refs?service=git-upload-pack"
url := project + "/repository/archive.zip" url := ""
if user != nil {
url += fmt.Sprintf("%s@", user)
}
url += project + "/repository/archive.zip"
if query != "" { if query != "" {
url += "?" + query url += "?" + query
} }
......
...@@ -40,7 +40,7 @@ func handleGetBlobRaw(a *api.API, w http.ResponseWriter, r *http.Request) { ...@@ -40,7 +40,7 @@ func handleGetBlobRaw(a *api.API, w http.ResponseWriter, r *http.Request) {
refpath := u.Path[rawLoc[1]:] refpath := u.Path[rawLoc[1]:]
// Query download access auth for this project // Query download access auth for this project
authReply := a.VerifyDownloadAccess(project, u.RawQuery, r.Header) authReply := a.VerifyDownloadAccess(project, u.User, u.RawQuery, r.Header)
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
......
...@@ -815,7 +815,13 @@ func TestPrivateBlobDownload(t *testing.T) { ...@@ -815,7 +815,13 @@ func TestPrivateBlobDownload(t *testing.T) {
token_ok2 := r.Header.Get("BBB-TOKEN") == "TOKEN-4BBB" token_ok2 := r.Header.Get("BBB-TOKEN") == "TOKEN-4BBB"
cookie, _ := r.Cookie("_gitlab_session") cookie, _ := r.Cookie("_gitlab_session")
cookie_ok3 := (cookie != nil && cookie.Value == "COOKIE-CCC") cookie_ok3 := (cookie != nil && cookie.Value == "COOKIE-CCC")
if !(token_ok1 || token_ok2 || cookie_ok3) { user := r.URL.User
user_ok4 := false
if user != nil {
password, _ := user.Password()
user_ok4 = (user.Username() == "user-ddd" && password == "password-eee")
}
if !(token_ok1 || token_ok2 || cookie_ok3 || user_ok4) {
w.WriteHeader(403) w.WriteHeader(403)
fmt.Fprintf(w, "Access denied") fmt.Fprintf(w, "Access denied")
return return
...@@ -854,4 +860,13 @@ func TestPrivateBlobDownload(t *testing.T) { ...@@ -854,4 +860,13 @@ func TestPrivateBlobDownload(t *testing.T) {
dl.Header.Set("Cookie", "alpha=1; _gitlab_session=COOKIE-CCC; beta=2") dl.Header.Set("Cookie", "alpha=1; _gitlab_session=COOKIE-CCC; beta=2")
dl.ExpectCode("/5f923865/README.md", 200) dl.ExpectCode("/5f923865/README.md", 200)
dl.ExpectSha1("/5f923865/README.md", "5f7af35c185a9e5face2f4afb6d7c4f00328d04c") dl.ExpectSha1("/5f923865/README.md", "5f7af35c185a9e5face2f4afb6d7c4f00328d04c")
dl.Header = make(http.Header) // clear
dl.ExpectCode("/5f923865/README.md", 403)
dlBaseUrl := dl.urlPrefix
dl.urlPrefix = "user-aaa:password-bbb@" + dlBaseUrl
dl.ExpectCode("/5f923865/README.md", 403)
dl.urlPrefix = "user-ddd:password-eee@" + dlBaseUrl
dl.ExpectCode("/5f923865/README.md", 200)
dl.ExpectSha1("/5f923865/README.md", "5f7af35c185a9e5face2f4afb6d7c4f00328d04c")
} }
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