package repo //go:generate mockgen -destination mock_repo_test.go -package repo code.gitea.io/sdk/gitea Client import ( "errors" "strings" "time" "git.polynom.me/rio/internal/constants" "github.com/patrickmn/go-cache" log "github.com/sirupsen/logrus" ) var ( pathCache = cache.New(1*time.Hour, 1*time.Hour) // Caching the existence of an user userCache = cache.New(24*time.Hour, 12*time.Hour) ) type PageCacheEntry struct { Repository Repository Path string } func makePageCacheKey(domain, path string) string { return domain + "/" + path } // / Try to find the repository with name @reponame of the user @username. If @cname // / is not "", then it also verifies that the repository contains a "CNAME" with // / the value of @cname as its content. @host, @domain, and @path are passed for // / caching on success. func lookupRepositoryAndCache(username, reponame, branchName, host, domain, path, cname string, giteaClient *GiteaClient) (*Repository, error) { log.Debugf("CNAME: %s", cname) log.Debugf("Looking up repository %s/%s", username, reponame) repo, err := giteaClient.getRepository(username, reponame) if err != nil { return nil, err } if !giteaClient.hasBranch(username, reponame, branchName) { return nil, errors.New("Specified branch does not exist") } // Check if the CNAME file matches if cname != "" { log.Debug("Checking CNAME") file, _, err := giteaClient.GetFile( username, reponame, constants.PagesBranch, "CNAME", nil, ) if err != nil { log.Errorf( "Could not verify CNAME of %s/%s@%s: %v\n", username, reponame, constants.PagesBranch, err, ) return nil, err } cnameContent := strings.Trim( string(file[:]), "\n", ) log.Debugf("CNAME Content: %s", cnameContent) if cnameContent != cname { return nil, errors.New("CNAME mismatch") } } // Cache data pathCache.Set( makePageCacheKey(domain, path), PageCacheEntry{ repo, path, }, cache.DefaultExpiration, ) return &repo, nil } func RepoFromPath(username, host, cname, path string, giteaClient *GiteaClient) (*Repository, string, error) { domain := host // Guess the repository key := makePageCacheKey(domain, path) entry, found := pathCache.Get(key) if found { pageEntry := entry.(PageCacheEntry) return &pageEntry.Repository, pageEntry.Path, nil } // Allow specifying the repository name in the TXT record reponame := "" if cname != "" { repoLookup, err := giteaClient.lookupRepoTXT(cname) if err == nil && repoLookup != "" { log.Infof( "TXT lookup for %s resulted in choosing repository %s", cname, repoLookup, ) reponame = repoLookup } } pathParts := strings.Split(path, "/") if reponame == "" && len(pathParts) > 1 { log.Debugf("Trying repository %s", pathParts[0]) modifiedPath := strings.Join(pathParts[1:], "/") repo, err := lookupRepositoryAndCache( username, pathParts[0], constants.PagesBranch, host, domain, modifiedPath, cname, giteaClient, ) if err == nil { return repo, modifiedPath, nil } } if reponame == "" { reponame = domain } log.Debugf("Trying repository %s/%s", username, reponame) repo, err := lookupRepositoryAndCache( username, reponame, constants.PagesBranch, host, domain, path, cname, giteaClient, ) return repo, path, err } // Checks if the username exists as an organisation or an user on the Gitea // instance, so that an attacker can't just request certificates for random // usernames. func CanRequestCertificate(username string, giteaClient *GiteaClient) bool { if _, found := userCache.Get(username); found { return true } hasUser := giteaClient.hasUser(username) if hasUser { userCache.Set(username, true, cache.DefaultExpiration) } return hasUser }