Init gist with regular urls via git CLI (http) (#501)
This commit is contained in:
2
Makefile
2
Makefile
@@ -50,7 +50,7 @@ watch_backend:
|
||||
OG_DEV=1 npx nodemon --watch '**/*' -e html,yml,go,js --signal SIGTERM --exec 'go run -ldflags "-X $(VERSION_PKG)=$(GIT_TAG)" . --config config.yml'
|
||||
|
||||
watch:
|
||||
@sh ./scripts/watch.sh
|
||||
@bash ./scripts/watch.sh
|
||||
|
||||
clean:
|
||||
@echo "Cleaning up build artifacts..."
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# https://github.com/thomiceli/opengist/blob/master/docs/configuration/cheat-sheet.md
|
||||
|
||||
# Set the log level to one of the following: debug, info, warn, error, fatal. Default: warn
|
||||
log-level: warn
|
||||
log-level: debug
|
||||
|
||||
# Set the log output to one or more of the following: `stdout`, `file`. Default: stdout,file
|
||||
log-output: stdout,file
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Init Gists via Git
|
||||
|
||||
Opengist allows you to create new snippets via Git over HTTP.
|
||||
Opengist allows you to create new snippets via Git over HTTP. You can create gists with either auto-generated URLs or custom URLs of your choice.
|
||||
|
||||
Simply init a new Git repository where your file(s) is/are located:
|
||||
|
||||
@@ -10,19 +10,41 @@ git add .
|
||||
git commit -m "My cool snippet"
|
||||
```
|
||||
|
||||
Then add this Opengist special remote URL and push your changes:
|
||||
### Option A: Regular URL
|
||||
|
||||
Create a gist with a custom URL using the format `http://opengist.url/username/custom-url`, where `username` is your authenticated username and `custom-url` is your desired gist identifier.
|
||||
|
||||
The gist must not exist yet if you want to create it, otherwise you will just push to the existing gist.
|
||||
|
||||
```shell
|
||||
git remote add origin http://localhost:6157/init
|
||||
git remote add origin http://opengist.url/thomas/my-custom-gist
|
||||
|
||||
git push -u origin master
|
||||
```
|
||||
|
||||
Log in with your Opengist account credentials, and your snippet will be created at the specified URL:
|
||||
**Requirements for custom URLs:**
|
||||
- The username must match your authenticated username
|
||||
- URL format: `http://opengist.url/username/custom-url`
|
||||
- The custom URL becomes your gist's identifier and title
|
||||
- `.git` suffix is automatically removed if present
|
||||
|
||||
### Option B: Init endpoint
|
||||
|
||||
Use the special `http://opengist.url/init` endpoint to create a gist with an automatically generated URL:
|
||||
|
||||
```shell
|
||||
Username for 'http://localhost:6157': thomas
|
||||
Password for 'http://thomas@localhost:6157':
|
||||
git remote add origin http://opengist.url/init
|
||||
|
||||
git push -u origin master
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
When you push, you'll be prompted to authenticate:
|
||||
|
||||
```shell
|
||||
Username for 'http://opengist.url': thomas
|
||||
Password for 'http://thomas@opengist.url': [your-password]
|
||||
Enumerating objects: 3, done.
|
||||
Counting objects: 100% (3/3), done.
|
||||
Delta compression using up to 8 threads
|
||||
@@ -30,12 +52,12 @@ Compressing objects: 100% (2/2), done.
|
||||
Writing objects: 100% (3/3), 416 bytes | 416.00 KiB/s, done.
|
||||
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
|
||||
remote:
|
||||
remote: Your new repository has been created here: http://localhost:6157/thomas/6051e930f140429f9a2f3bb1fa101066
|
||||
remote: Your new repository has been created here: http://opengist.url/thomas/6051e930f140429f9a2f3bb1fa101066
|
||||
remote:
|
||||
remote: If you want to keep working with your gist, you could set the remote URL via:
|
||||
remote: git remote set-url origin http://localhost:6157/thomas/6051e930f140429f9a2f3bb1fa101066
|
||||
remote: git remote set-url origin http://opengist.url/thomas/6051e930f140429f9a2f3bb1fa101066
|
||||
remote:
|
||||
To http://localhost:6157/init
|
||||
To http://opengist.url/init
|
||||
* [new branch] master -> master
|
||||
```
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package password
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
@@ -6,8 +6,9 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
|
||||
type argon2ID struct {
|
||||
@@ -1,11 +1,9 @@
|
||||
package password
|
||||
|
||||
import "github.com/thomiceli/opengist/internal/auth"
|
||||
|
||||
func HashPassword(code string) (string, error) {
|
||||
return auth.Argon2id.Hash(code)
|
||||
return Argon2id.Hash(code)
|
||||
}
|
||||
|
||||
func VerifyPassword(code, hashedCode string) (bool, error) {
|
||||
return auth.Argon2id.Verify(code, hashedCode)
|
||||
return Argon2id.Verify(code, hashedCode)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package totp
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
83
internal/auth/try_login.go
Normal file
83
internal/auth/try_login.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/auth/ldap"
|
||||
passwordpkg "github.com/thomiceli/opengist/internal/auth/password"
|
||||
"github.com/thomiceli/opengist/internal/db"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AuthError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func (e AuthError) Error() string {
|
||||
return e.message
|
||||
}
|
||||
|
||||
func TryAuthentication(username, password string) (*db.User, error) {
|
||||
user, err := db.GetUserByUsername(username)
|
||||
if err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Error().Err(err).Msgf("Cannot get user by username %s", username)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if user.Password != "" {
|
||||
return tryDbLogin(user, password)
|
||||
} else {
|
||||
if ldap.Enabled() {
|
||||
return tryLdapLogin(username, password)
|
||||
}
|
||||
return nil, AuthError{"no authentication method available"}
|
||||
}
|
||||
}
|
||||
|
||||
func tryDbLogin(user *db.User, password string) (*db.User, error) {
|
||||
if ok, err := passwordpkg.VerifyPassword(password, user.Password); !ok {
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Password verification failed")
|
||||
return nil, err
|
||||
}
|
||||
return nil, AuthError{"invalid password"}
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func tryLdapLogin(username, password string) (user *db.User, err error) {
|
||||
ok, err := ldap.Authenticate(username, password)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("LDAP authentication failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return nil, AuthError{"invalid LDAP credentials"}
|
||||
}
|
||||
|
||||
if user, err = db.GetUserByUsername(username); err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Error().Err(err).Msgf("Cannot get user by username %s", username)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
user = &db.User{
|
||||
Username: username,
|
||||
}
|
||||
if err = user.Create(); err != nil {
|
||||
log.Warn().Err(err).Msg("Cannot create user after LDAP authentication")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
@@ -269,5 +269,5 @@ func DeprecationDBFilename() {
|
||||
}
|
||||
|
||||
func TruncateDatabase() error {
|
||||
return db.Migrator().DropTable("likes", &User{}, "gists", &SSHKey{}, &AdminSetting{}, &Invitation{}, &WebAuthnCredential{}, &TOTP{}, &GistTopic{}, &GistLanguage{})
|
||||
return db.Migrator().DropTable("likes", &User{}, "gists", &SSHKey{}, &AdminSetting{}, &Invitation{}, &WebAuthnCredential{}, &TOTP{}, &GistTopic{}, &GistLanguage{}, &GistInitQueue{})
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/thomiceli/opengist/internal/auth"
|
||||
"slices"
|
||||
|
||||
"github.com/thomiceli/opengist/internal/auth/password"
|
||||
ogtotp "github.com/thomiceli/opengist/internal/auth/totp"
|
||||
"github.com/thomiceli/opengist/internal/config"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type TOTP struct {
|
||||
@@ -31,7 +31,7 @@ func GetTOTPByUserID(userID uint) (*TOTP, error) {
|
||||
|
||||
func (totp *TOTP) StoreSecret(secret string) error {
|
||||
secretBytes := []byte(secret)
|
||||
encrypted, err := auth.AESEncrypt(config.SecretKey, secretBytes)
|
||||
encrypted, err := ogtotp.AESEncrypt(config.SecretKey, secretBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func (totp *TOTP) ValidateCode(code string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
secretBytes, err := auth.AESDecrypt(config.SecretKey, ciphertext)
|
||||
secretBytes, err := ogtotp.AESDecrypt(config.SecretKey, ciphertext)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -2,15 +2,16 @@ package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/config"
|
||||
"github.com/thomiceli/opengist/internal/db"
|
||||
"github.com/thomiceli/opengist/internal/i18n"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type dataKey string
|
||||
@@ -57,7 +58,7 @@ func (ctx *Context) DataMap() echo.Map {
|
||||
}
|
||||
|
||||
func (ctx *Context) ErrorRes(code int, message string, err error) error {
|
||||
if code >= 500 {
|
||||
if code >= 500 && err != nil {
|
||||
var skipLogger = log.With().CallerWithSkipFrameCount(3).Logger()
|
||||
skipLogger.Error().Err(err).Msg(message)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/auth/ldap"
|
||||
"github.com/thomiceli/opengist/internal/auth"
|
||||
passwordpkg "github.com/thomiceli/opengist/internal/auth/password"
|
||||
"github.com/thomiceli/opengist/internal/db"
|
||||
"github.com/thomiceli/opengist/internal/i18n"
|
||||
@@ -125,24 +125,15 @@ func ProcessLogin(ctx *context.Context) error {
|
||||
return ctx.ErrorRes(400, ctx.Tr("error.cannot-bind-data"), err)
|
||||
}
|
||||
|
||||
localUser, err := db.GetUserByUsername(dto.Username)
|
||||
hasLocalPassword := err == nil && localUser.Password != ""
|
||||
|
||||
if hasLocalPassword {
|
||||
if user, err = tryDbLogin(ctx, dto.Username, dto.Password); user == nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if ldap.Enabled() {
|
||||
if user, err = tryLdapLogin(ctx, dto.Username, dto.Password); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if user == nil {
|
||||
if user, err = tryDbLogin(ctx, dto.Username, dto.Password); user == nil {
|
||||
return err
|
||||
}
|
||||
user, err = auth.TryAuthentication(dto.Username, dto.Password)
|
||||
if err != nil {
|
||||
var authErr auth.AuthError
|
||||
if errors.As(err, &authErr) {
|
||||
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
|
||||
ctx.AddFlash(ctx.Tr("flash.auth.invalid-credentials"), "error")
|
||||
return ctx.RedirectTo("/login")
|
||||
}
|
||||
return ctx.ErrorRes(500, "Authentication system error", nil)
|
||||
}
|
||||
|
||||
// handle MFA
|
||||
@@ -170,59 +161,3 @@ func Logout(ctx *context.Context) error {
|
||||
ctx.DeleteCsrfCookie()
|
||||
return ctx.RedirectTo("/all")
|
||||
}
|
||||
|
||||
func tryDbLogin(ctx *context.Context, username, password string) (user *db.User, err error) {
|
||||
if user, err = db.GetUserByUsername(username); err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ctx.ErrorRes(500, "Cannot get user", err)
|
||||
}
|
||||
|
||||
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
|
||||
ctx.AddFlash(ctx.Tr("flash.auth.invalid-credentials"), "error")
|
||||
return nil, ctx.RedirectTo("/login")
|
||||
}
|
||||
|
||||
if ok, err := passwordpkg.VerifyPassword(password, user.Password); !ok {
|
||||
if err != nil {
|
||||
return nil, ctx.ErrorRes(500, "Cannot check for password", err)
|
||||
}
|
||||
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
|
||||
ctx.AddFlash(ctx.Tr("flash.auth.invalid-credentials"), "error")
|
||||
return nil, ctx.RedirectTo("/login")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func tryLdapLogin(ctx *context.Context, username, password string) (user *db.User, err error) {
|
||||
ok, err := ldap.Authenticate(username, password)
|
||||
if err != nil {
|
||||
log.Info().Err(err).Msgf("LDAP authentication error")
|
||||
return nil, ctx.ErrorRes(500, "Cannot get user", err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
log.Warn().Msg("Invalid LDAP authentication attempt from " + ctx.RealIP())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if user, err = db.GetUserByUsername(username); err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ctx.ErrorRes(500, "Cannot get user", err)
|
||||
}
|
||||
}
|
||||
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
user = &db.User{
|
||||
Username: username,
|
||||
}
|
||||
if err = user.Create(); err != nil {
|
||||
log.Warn().Err(err).Msg("Cannot create user after LDAP authentication")
|
||||
return nil, ctx.ErrorRes(500, "Cannot create user", err)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
@@ -15,17 +15,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thomiceli/opengist/internal/auth/ldap"
|
||||
"github.com/thomiceli/opengist/internal/auth/password"
|
||||
"github.com/thomiceli/opengist/internal/web/context"
|
||||
"github.com/thomiceli/opengist/internal/web/handlers"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/auth"
|
||||
"github.com/thomiceli/opengist/internal/db"
|
||||
"github.com/thomiceli/opengist/internal/git"
|
||||
"gorm.io/gorm"
|
||||
"github.com/thomiceli/opengist/internal/web/context"
|
||||
"github.com/thomiceli/opengist/internal/web/handlers"
|
||||
)
|
||||
|
||||
var routes = []struct {
|
||||
@@ -47,165 +43,211 @@ var routes = []struct {
|
||||
}
|
||||
|
||||
func GitHttp(ctx *context.Context) error {
|
||||
route := findMatchingRoute(ctx)
|
||||
if route == nil {
|
||||
return ctx.NotFound("Gist not found") // regular 404 for non-git routes
|
||||
}
|
||||
gist := ctx.GetData("gist").(*db.Gist)
|
||||
gistExists := gist.ID != 0
|
||||
|
||||
isInitRoute := strings.HasPrefix(ctx.Request().URL.Path, "/init/info/refs")
|
||||
isInitRouteReceive := strings.HasPrefix(ctx.Request().URL.Path, "/init/git-receive-pack")
|
||||
isInfoRefs := strings.HasSuffix(route.gitUrl, "/info/refs$")
|
||||
isPull := ctx.QueryParam("service") == "git-upload-pack" ||
|
||||
strings.HasSuffix(ctx.Request().URL.Path, "git-upload-pack") && !isInfoRefs
|
||||
isPush := ctx.QueryParam("service") == "git-receive-pack" ||
|
||||
strings.HasSuffix(ctx.Request().URL.Path, "git-receive-pack") && !isInfoRefs
|
||||
|
||||
repositoryPath := git.RepositoryPath(gist.User.Username, gist.Uuid)
|
||||
ctx.SetData("repositoryPath", repositoryPath)
|
||||
|
||||
allow, err := auth.ShouldAllowUnauthenticatedGistAccess(handlers.ContextAuthInfo{Context: ctx}, true)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Cannot check if unauthenticated access is allowed")
|
||||
}
|
||||
|
||||
// No need to authenticate if the user wants
|
||||
// to clone/pull ; a non-private gist ; that exists ; where unauthenticated access is allowed in the instance
|
||||
if isPull && gist.Private != db.PrivateVisibility && gistExists && allow {
|
||||
return route.handler(ctx)
|
||||
}
|
||||
|
||||
// Else we need to authenticate the user, that include other cases:
|
||||
// - user wants to push the gist
|
||||
// - user wants to clone/pull a private gist
|
||||
// - user wants to clone/pull a non-private gist but unauthenticated access is not allowed
|
||||
// - gist is not found ; has no right to clone/pull (obfuscation)
|
||||
// - admin setting to require login is set to true
|
||||
authUsername, authPassword, err := parseAuthHeader(ctx)
|
||||
if err != nil {
|
||||
return basicAuth(ctx)
|
||||
}
|
||||
|
||||
// if the user wants to create a gist via the /init route
|
||||
if isInitRoute || isInitRouteReceive {
|
||||
var user *db.User
|
||||
|
||||
// check if the user has a valid account on opengist to push a gist
|
||||
user, err = auth.TryAuthentication(authUsername, authPassword)
|
||||
if err != nil {
|
||||
var authErr auth.AuthError
|
||||
if errors.As(err, &authErr) {
|
||||
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
|
||||
return ctx.PlainText(401, "Invalid credentials")
|
||||
}
|
||||
return ctx.ErrorRes(500, "Authentication system error", nil)
|
||||
}
|
||||
|
||||
if isInitRoute {
|
||||
gist, err = createGist(user, "")
|
||||
if err != nil {
|
||||
return ctx.ErrorRes(500, "Cannot create gist", err)
|
||||
}
|
||||
|
||||
err = db.AddInitGistToQueue(gist.ID, user.ID)
|
||||
if err != nil {
|
||||
return ctx.ErrorRes(500, "Cannot add inited gist to the queue", err)
|
||||
}
|
||||
ctx.SetData("gist", gist)
|
||||
return route.handler(ctx)
|
||||
} else {
|
||||
gist, err = db.GetInitGistInQueueForUser(user.ID)
|
||||
if err != nil {
|
||||
return ctx.ErrorRes(500, "Cannot retrieve inited gist from the queue", err)
|
||||
}
|
||||
|
||||
ctx.SetData("gist", gist)
|
||||
ctx.SetData("repositoryPath", git.RepositoryPath(gist.User.Username, gist.Uuid))
|
||||
return route.handler(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// if clone/pull
|
||||
// check if the gist exists and if the credentials are valid
|
||||
if isPull {
|
||||
log.Debug().Msg("Detected git pull operation")
|
||||
if !gistExists {
|
||||
log.Debug().Str("authUsername", authUsername).Msg("Pulling unknown gist")
|
||||
return ctx.PlainText(404, "Check your credentials or make sure you have access to the Gist")
|
||||
}
|
||||
|
||||
var userToCheckPermissions string
|
||||
// if the user is trying to clone/pull a non-private gist while unauthenticated access is not allowed,
|
||||
// check if the user has a valid account
|
||||
if gist.Private != db.PrivateVisibility {
|
||||
log.Debug().Str("authUsername", authUsername).Msg("Pulling non-private gist with authenticated access")
|
||||
userToCheckPermissions = authUsername
|
||||
} else { // else just check the password against the gist owner
|
||||
log.Debug().Str("authUsername", authUsername).Str("gistOwner", gist.User.Username).Msg("Pulling private gist")
|
||||
userToCheckPermissions = gist.User.Username
|
||||
}
|
||||
|
||||
if _, err = auth.TryAuthentication(userToCheckPermissions, authPassword); err != nil {
|
||||
var authErr auth.AuthError
|
||||
if errors.As(err, &authErr) {
|
||||
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
|
||||
return ctx.PlainText(404, "Check your credentials or make sure you have access to the Gist")
|
||||
}
|
||||
return ctx.ErrorRes(500, "Authentication system error", nil)
|
||||
}
|
||||
log.Debug().Str("authUsername", authUsername).Msg("Pulling gist")
|
||||
|
||||
return route.handler(ctx)
|
||||
}
|
||||
|
||||
if isPush {
|
||||
log.Debug().Msg("Detected git push operation")
|
||||
// if gist exists, check if the credentials are valid and if the user is the gist owner
|
||||
if gistExists {
|
||||
log.Debug().Str("authUsername", authUsername).Str("gistOwner", gist.User.Username).Msg("Pushing to existing gist")
|
||||
if _, err = auth.TryAuthentication(gist.User.Username, authPassword); err != nil {
|
||||
var authErr auth.AuthError
|
||||
if errors.As(err, &authErr) {
|
||||
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
|
||||
return ctx.PlainText(404, "Check your credentials or make sure you have access to the Gist")
|
||||
}
|
||||
return ctx.ErrorRes(500, "Authentication system error", nil)
|
||||
}
|
||||
log.Debug().Str("authUsername", authUsername).Msg("Pushing gist")
|
||||
|
||||
return route.handler(ctx)
|
||||
} else { // if the gist does not exist, check if the user has a valid account on opengist to push a gist and create it
|
||||
log.Debug().Str("authUsername", authUsername).Msg("Creating new gist by pushing")
|
||||
var user *db.User
|
||||
if user, err = auth.TryAuthentication(authUsername, authPassword); err != nil {
|
||||
var authErr auth.AuthError
|
||||
if errors.As(err, &authErr) {
|
||||
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
|
||||
return ctx.PlainText(404, "Check your credentials or make sure you have access to the Gist")
|
||||
}
|
||||
return ctx.ErrorRes(500, "Authentication system error", nil)
|
||||
}
|
||||
|
||||
urlPath := ctx.Request().URL.Path
|
||||
pathParts := strings.Split(strings.Trim(urlPath, "/"), "/")
|
||||
if pathParts[0] == authUsername && len(pathParts) == 4 {
|
||||
log.Debug().Str("authUsername", authUsername).Msg("Valid URL format for push operation")
|
||||
gist, err = createGist(user, pathParts[1])
|
||||
if err != nil {
|
||||
return ctx.ErrorRes(500, "Cannot create gist", err)
|
||||
}
|
||||
log.Debug().Str("authUsername", authUsername).Str("url", urlPath).Msg("Gist created")
|
||||
ctx.SetData("gist", gist)
|
||||
ctx.SetData("repositoryPath", git.RepositoryPath(gist.User.Username, gist.Uuid))
|
||||
} else {
|
||||
log.Debug().Str("authUsername", authUsername).Any("path", pathParts).Msg("Invalid URL format for push operation")
|
||||
return ctx.PlainText(401, "Invalid URL format for push operation")
|
||||
}
|
||||
return route.handler(ctx)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return route.handler(ctx)
|
||||
}
|
||||
|
||||
func findMatchingRoute(ctx *context.Context) *struct {
|
||||
gitUrl string
|
||||
method string
|
||||
handler func(ctx *context.Context) error
|
||||
} {
|
||||
for _, route := range routes {
|
||||
matched, _ := regexp.MatchString(route.gitUrl, ctx.Request().URL.Path)
|
||||
if ctx.Request().Method == route.method && matched {
|
||||
if !strings.HasPrefix(ctx.Request().Header.Get("User-Agent"), "git/") {
|
||||
continue
|
||||
}
|
||||
|
||||
gist := ctx.GetData("gist").(*db.Gist)
|
||||
|
||||
isInit := strings.HasPrefix(ctx.Request().URL.Path, "/init/info/refs")
|
||||
isInitReceive := strings.HasPrefix(ctx.Request().URL.Path, "/init/git-receive-pack")
|
||||
isInfoRefs := strings.HasSuffix(route.gitUrl, "/info/refs$")
|
||||
isPull := ctx.QueryParam("service") == "git-upload-pack" ||
|
||||
strings.HasSuffix(ctx.Request().URL.Path, "git-upload-pack") ||
|
||||
ctx.Request().Method == "GET" && !isInfoRefs
|
||||
|
||||
repositoryPath := git.RepositoryPath(gist.User.Username, gist.Uuid)
|
||||
if _, err := os.Stat(repositoryPath); os.IsNotExist(err) {
|
||||
if err != nil {
|
||||
log.Info().Err(err).Msg("Repository directory does not exist")
|
||||
return ctx.ErrorRes(404, "Repository directory does not exist", err)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetData("repositoryPath", repositoryPath)
|
||||
|
||||
allow, err := auth.ShouldAllowUnauthenticatedGistAccess(handlers.ContextAuthInfo{Context: ctx}, true)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("Cannot check if unauthenticated access is allowed")
|
||||
}
|
||||
|
||||
// Shows basic auth if :
|
||||
// - user wants to push the gist
|
||||
// - user wants to clone/pull a private gist
|
||||
// - gist is not found (obfuscation)
|
||||
// - admin setting to require login is set to true
|
||||
if isPull && gist.Private != db.PrivateVisibility && gist.ID != 0 && allow {
|
||||
return route.handler(ctx)
|
||||
}
|
||||
|
||||
authHeader := ctx.Request().Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
return basicAuth(ctx)
|
||||
}
|
||||
|
||||
authFields := strings.Fields(authHeader)
|
||||
if len(authFields) != 2 || authFields[0] != "Basic" {
|
||||
return basicAuth(ctx)
|
||||
}
|
||||
|
||||
authUsername, authPassword, err := basicAuthDecode(authFields[1])
|
||||
if err != nil {
|
||||
return basicAuth(ctx)
|
||||
}
|
||||
|
||||
if !isInit && !isInitReceive {
|
||||
if gist.ID == 0 {
|
||||
return ctx.PlainText(404, "Check your credentials or make sure you have access to the Gist")
|
||||
}
|
||||
|
||||
var userToCheckPermissions *db.User
|
||||
if gist.Private != db.PrivateVisibility && isPull {
|
||||
userToCheckPermissions, _ = db.GetUserByUsername(authUsername)
|
||||
} else {
|
||||
userToCheckPermissions = &gist.User
|
||||
}
|
||||
|
||||
// ldap
|
||||
ldapSuccess := false
|
||||
if ldap.Enabled() {
|
||||
if ok, err := ldap.Authenticate(userToCheckPermissions.Username, authPassword); !ok {
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("LDAP authentication error")
|
||||
}
|
||||
log.Warn().Msg("Invalid LDAP authentication attempt from " + ctx.RealIP())
|
||||
} else {
|
||||
ldapSuccess = true
|
||||
}
|
||||
}
|
||||
|
||||
// password
|
||||
if !ldapSuccess {
|
||||
if ok, err := password.VerifyPassword(authPassword, userToCheckPermissions.Password); !ok {
|
||||
if err != nil {
|
||||
return ctx.ErrorRes(500, "Cannot verify password", err)
|
||||
}
|
||||
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
|
||||
return ctx.PlainText(404, "Check your credentials or make sure you have access to the Gist")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var user *db.User
|
||||
if user, err = db.GetUserByUsername(authUsername); err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return ctx.ErrorRes(500, "Cannot get user", err)
|
||||
}
|
||||
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
|
||||
return ctx.ErrorRes(401, "Invalid credentials", nil)
|
||||
}
|
||||
ldapSuccess := false
|
||||
if ldap.Enabled() {
|
||||
if ok, err := ldap.Authenticate(user.Username, authPassword); !ok {
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("LDAP authentication error")
|
||||
}
|
||||
log.Warn().Msg("Invalid LDAP authentication attempt from " + ctx.RealIP())
|
||||
} else {
|
||||
ldapSuccess = true
|
||||
}
|
||||
}
|
||||
if !ldapSuccess {
|
||||
if ok, err := password.VerifyPassword(authPassword, user.Password); !ok {
|
||||
if err != nil {
|
||||
return ctx.ErrorRes(500, "Cannot check for password", err)
|
||||
}
|
||||
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
|
||||
return ctx.ErrorRes(401, "Invalid credentials", nil)
|
||||
}
|
||||
}
|
||||
|
||||
if isInit {
|
||||
gist = new(db.Gist)
|
||||
gist.UserID = user.ID
|
||||
gist.User = *user
|
||||
uuidGist, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return ctx.ErrorRes(500, "Error creating an UUID", err)
|
||||
}
|
||||
gist.Uuid = strings.Replace(uuidGist.String(), "-", "", -1)
|
||||
gist.Title = "gist:" + gist.Uuid
|
||||
|
||||
if err = gist.InitRepository(); err != nil {
|
||||
return ctx.ErrorRes(500, "Cannot init repository in the file system", err)
|
||||
}
|
||||
|
||||
if err = gist.Create(); err != nil {
|
||||
return ctx.ErrorRes(500, "Cannot init repository in database", err)
|
||||
}
|
||||
|
||||
err = db.AddInitGistToQueue(gist.ID, user.ID)
|
||||
if err != nil {
|
||||
return ctx.ErrorRes(500, "Cannot add inited gist to the queue", err)
|
||||
}
|
||||
ctx.SetData("gist", gist)
|
||||
} else {
|
||||
gist, err = db.GetInitGistInQueueForUser(user.ID)
|
||||
if err != nil {
|
||||
return ctx.ErrorRes(500, "Cannot retrieve inited gist from the queue", err)
|
||||
}
|
||||
|
||||
ctx.SetData("gist", gist)
|
||||
ctx.SetData("repositoryPath", git.RepositoryPath(gist.User.Username, gist.Uuid))
|
||||
}
|
||||
}
|
||||
|
||||
return route.handler(ctx)
|
||||
return &route
|
||||
}
|
||||
}
|
||||
return ctx.NotFound("Gist not found")
|
||||
return nil
|
||||
}
|
||||
|
||||
func createGist(user *db.User, url string) (*db.Gist, error) {
|
||||
gist := new(db.Gist)
|
||||
gist.UserID = user.ID
|
||||
gist.User = *user
|
||||
uuidGist, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gist.Uuid = strings.Replace(uuidGist.String(), "-", "", -1)
|
||||
gist.Title = "gist:" + gist.Uuid
|
||||
|
||||
if url != "" {
|
||||
gist.URL = strings.TrimSuffix(url, ".git")
|
||||
gist.Title = strings.TrimSuffix(url, ".git")
|
||||
}
|
||||
|
||||
if err := gist.InitRepository(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := gist.Create(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gist, nil
|
||||
}
|
||||
|
||||
func uploadPack(ctx *context.Context) error {
|
||||
@@ -331,6 +373,26 @@ func basicAuth(ctx *context.Context) error {
|
||||
return ctx.PlainText(401, "Requires authentication")
|
||||
}
|
||||
|
||||
func parseAuthHeader(ctx *context.Context) (string, string, error) {
|
||||
authHeader := ctx.Request().Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
return "", "", errors.New("no auth header")
|
||||
}
|
||||
|
||||
authFields := strings.Fields(authHeader)
|
||||
if len(authFields) != 2 || authFields[0] != "Basic" {
|
||||
return "", "", errors.New("invalid auth header")
|
||||
}
|
||||
|
||||
authUsername, authPassword, err := basicAuthDecode(authFields[1])
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot decode basic auth header")
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return authUsername, authPassword, nil
|
||||
}
|
||||
|
||||
func basicAuthDecode(encoded string) (string, string, error) {
|
||||
s, err := base64.StdEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
|
||||
@@ -203,49 +203,28 @@ func TestGitOperations(t *testing.T) {
|
||||
err = s.Request("POST", "/", gist3, 302)
|
||||
require.NoError(t, err)
|
||||
|
||||
gitOperations := func(credentials, owner, url, filename string, expectErrorClone, expectErrorCheck, expectErrorPush bool) {
|
||||
log.Debug().Msgf("Testing %s %s %t %t %t", credentials, url, expectErrorClone, expectErrorCheck, expectErrorPush)
|
||||
err := clientGitClone(credentials, owner, url)
|
||||
if expectErrorClone {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
err = clientCheckRepo(url, filename)
|
||||
if expectErrorCheck {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
err = clientGitPush(url)
|
||||
if expectErrorPush {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
credentials string
|
||||
user string
|
||||
url string
|
||||
pushOptions string
|
||||
expectErrorClone bool
|
||||
expectErrorCheck bool
|
||||
expectErrorPush bool
|
||||
}{
|
||||
{":", "kaguya", "kaguya-pub-gist", false, false, true},
|
||||
{":", "kaguya", "kaguya-unl-gist", false, false, true},
|
||||
{":", "kaguya", "kaguya-priv-gist", true, true, true},
|
||||
{"kaguya:kaguya", "kaguya", "kaguya-pub-gist", false, false, false},
|
||||
{"kaguya:kaguya", "kaguya", "kaguya-unl-gist", false, false, false},
|
||||
{"kaguya:kaguya", "kaguya", "kaguya-priv-gist", false, false, false},
|
||||
{"fujiwara:fujiwara", "kaguya", "kaguya-pub-gist", false, false, true},
|
||||
{"fujiwara:fujiwara", "kaguya", "kaguya-unl-gist", false, false, true},
|
||||
{"fujiwara:fujiwara", "kaguya", "kaguya-priv-gist", true, true, true},
|
||||
{":", "kaguya", "kaguya-pub-gist", "", false, false, true},
|
||||
{":", "kaguya", "kaguya-unl-gist", "", false, false, true},
|
||||
{":", "kaguya", "kaguya-priv-gist", "", true, true, true},
|
||||
{"kaguya:kaguya", "kaguya", "kaguya-pub-gist", "", false, false, false},
|
||||
{"kaguya:kaguya", "kaguya", "kaguya-unl-gist", "", false, false, false},
|
||||
{"kaguya:kaguya", "kaguya", "kaguya-priv-gist", "", false, false, false},
|
||||
{"fujiwara:fujiwara", "kaguya", "kaguya-pub-gist", "", false, false, true},
|
||||
{"fujiwara:fujiwara", "kaguya", "kaguya-unl-gist", "", false, false, true},
|
||||
{"fujiwara:fujiwara", "kaguya", "kaguya-priv-gist", "", true, true, true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
gitOperations(test.credentials, test.user, test.url, "kaguya-file.txt", test.expectErrorClone, test.expectErrorCheck, test.expectErrorPush)
|
||||
gitCloneCheckPush(t, test.credentials, test.user, test.url, "kaguya-file.txt", test.pushOptions, test.expectErrorClone, test.expectErrorCheck, test.expectErrorPush)
|
||||
}
|
||||
|
||||
login(t, s, admin)
|
||||
@@ -256,23 +235,24 @@ func TestGitOperations(t *testing.T) {
|
||||
credentials string
|
||||
user string
|
||||
url string
|
||||
pushOptions string
|
||||
expectErrorClone bool
|
||||
expectErrorCheck bool
|
||||
expectErrorPush bool
|
||||
}{
|
||||
{":", "kaguya", "kaguya-pub-gist", true, true, true},
|
||||
{":", "kaguya", "kaguya-unl-gist", true, true, true},
|
||||
{":", "kaguya", "kaguya-priv-gist", true, true, true},
|
||||
{"kaguya:kaguya", "kaguya", "kaguya-pub-gist", false, false, false},
|
||||
{"kaguya:kaguya", "kaguya", "kaguya-unl-gist", false, false, false},
|
||||
{"kaguya:kaguya", "kaguya", "kaguya-priv-gist", false, false, false},
|
||||
{"fujiwara:fujiwara", "kaguya", "kaguya-pub-gist", false, false, true},
|
||||
{"fujiwara:fujiwara", "kaguya", "kaguya-unl-gist", false, false, true},
|
||||
{"fujiwara:fujiwara", "kaguya", "kaguya-priv-gist", true, true, true},
|
||||
{":", "kaguya", "kaguya-pub-gist", "", true, true, true},
|
||||
{":", "kaguya", "kaguya-unl-gist", "", true, true, true},
|
||||
{":", "kaguya", "kaguya-priv-gist", "", true, true, true},
|
||||
{"kaguya:kaguya", "kaguya", "kaguya-pub-gist", "", false, false, false},
|
||||
{"kaguya:kaguya", "kaguya", "kaguya-unl-gist", "", false, false, false},
|
||||
{"kaguya:kaguya", "kaguya", "kaguya-priv-gist", "", false, false, false},
|
||||
{"fujiwara:fujiwara", "kaguya", "kaguya-pub-gist", "", false, false, true},
|
||||
{"fujiwara:fujiwara", "kaguya", "kaguya-unl-gist", "", false, false, true},
|
||||
{"fujiwara:fujiwara", "kaguya", "kaguya-priv-gist", "", true, true, true},
|
||||
}
|
||||
|
||||
for _, test := range testsRequireLogin {
|
||||
gitOperations(test.credentials, test.user, test.url, "kaguya-file.txt", test.expectErrorClone, test.expectErrorCheck, test.expectErrorPush)
|
||||
gitCloneCheckPush(t, test.credentials, test.user, test.url, "kaguya-file.txt", test.pushOptions, test.expectErrorClone, test.expectErrorCheck, test.expectErrorPush)
|
||||
}
|
||||
|
||||
login(t, s, admin)
|
||||
@@ -280,31 +260,155 @@ func TestGitOperations(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, test := range tests {
|
||||
gitOperations(test.credentials, test.user, test.url, "kaguya-file.txt", test.expectErrorClone, test.expectErrorCheck, test.expectErrorPush)
|
||||
gitCloneCheckPush(t, test.credentials, test.user, test.url, "kaguya-file.txt", test.pushOptions, test.expectErrorClone, test.expectErrorCheck, test.expectErrorPush)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitInit(t *testing.T) {
|
||||
s := Setup(t)
|
||||
defer Teardown(t, s)
|
||||
|
||||
admin := db.UserDTO{Username: "thomas", Password: "thomas"}
|
||||
register(t, s, admin)
|
||||
s.sessionCookie = ""
|
||||
register(t, s, db.UserDTO{Username: "fujiwara", Password: "fujiwara"})
|
||||
s.sessionCookie = ""
|
||||
register(t, s, db.UserDTO{Username: "kaguya", Password: "kaguya"})
|
||||
|
||||
testsNewWithPush := []struct {
|
||||
credentials string
|
||||
user string
|
||||
url string
|
||||
pushOptions string
|
||||
expectErrorClone bool
|
||||
expectErrorCheck bool
|
||||
expectErrorPush bool
|
||||
}{
|
||||
{":", "kaguya", "gist1", "", true, true, true},
|
||||
{"kaguya:wrongpass", "kaguya", "gist2", "", true, true, true},
|
||||
{"fujiwara:fujiwara", "kaguya", "gist3", "", true, true, true},
|
||||
{"kaguya:kaguya", "kaguya", "gist4", "", false, false, false},
|
||||
{"kaguya:kaguya", "kaguya", "gist5/g", "", true, true, true},
|
||||
}
|
||||
|
||||
for _, test := range testsNewWithPush {
|
||||
gitInitPush(t, test.credentials, test.user, test.url, "newfile.txt", test.pushOptions, test.expectErrorPush)
|
||||
}
|
||||
|
||||
gist1db, err := db.GetGistByID("1")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "kaguya", gist1db.User.Username)
|
||||
|
||||
for _, test := range testsNewWithPush {
|
||||
gitCloneCheckPush(t, test.credentials, test.user, test.url, "newfile.txt", test.pushOptions, test.expectErrorClone, test.expectErrorCheck, test.expectErrorPush)
|
||||
}
|
||||
|
||||
count, err := db.CountAll(db.Gist{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), count)
|
||||
|
||||
testsNewWithInit := []struct {
|
||||
credentials string
|
||||
url string
|
||||
pushOptions string
|
||||
expectErrorPush bool
|
||||
}{
|
||||
{":", "init", "", true},
|
||||
{"fujiwara:wrongpass", "init", "", true},
|
||||
{"kaguya:kaguya", "init", "", false},
|
||||
{"fujiwara:fujiwara", "init", "", false},
|
||||
}
|
||||
|
||||
for _, test := range testsNewWithInit {
|
||||
gitInitPush(t, test.credentials, "kaguya", test.url, "newfile.txt", test.pushOptions, test.expectErrorPush)
|
||||
}
|
||||
|
||||
count, err = db.CountAll(db.Gist{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(3), count)
|
||||
|
||||
gist2db, err := db.GetGistByID("2")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "kaguya", gist2db.User.Username)
|
||||
|
||||
gist3db, err := db.GetGistByID("3")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "fujiwara", gist3db.User.Username)
|
||||
}
|
||||
|
||||
func clientGitClone(creds string, user string, url string) error {
|
||||
return exec.Command("git", "clone", "http://"+creds+"@localhost:6157/"+user+"/"+url, filepath.Join(config.GetHomeDir(), "tmp", url)).Run()
|
||||
}
|
||||
|
||||
func clientGitPush(url string) error {
|
||||
f, err := os.Create(filepath.Join(config.GetHomeDir(), "tmp", url, "newfile.txt"))
|
||||
func clientGitPush(url string, pushOptions string, file string) error {
|
||||
f, err := os.Create(filepath.Join(config.GetHomeDir(), "tmp", url, file))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
_, _ = f.WriteString("new file")
|
||||
_ = f.Close()
|
||||
|
||||
_ = exec.Command("git", "-C", filepath.Join(config.GetHomeDir(), "tmp", url), "add", "newfile.txt").Run()
|
||||
_ = exec.Command("git", "-C", filepath.Join(config.GetHomeDir(), "tmp", url), "add", file).Run()
|
||||
_ = exec.Command("git", "-C", filepath.Join(config.GetHomeDir(), "tmp", url), "commit", "-m", "new file").Run()
|
||||
err = exec.Command("git", "-C", filepath.Join(config.GetHomeDir(), "tmp", url), "push", "origin", "master").Run()
|
||||
|
||||
if pushOptions != "" {
|
||||
err = exec.Command("git", "-C", filepath.Join(config.GetHomeDir(), "tmp", url), "push", pushOptions, "origin").Run()
|
||||
} else {
|
||||
err = exec.Command("git", "-C", filepath.Join(config.GetHomeDir(), "tmp", url), "push", "origin").Run()
|
||||
}
|
||||
_ = os.RemoveAll(filepath.Join(config.GetHomeDir(), "tmp", url))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func clientGitInit(path string) error {
|
||||
return exec.Command("git", "init", "--initial-branch=master", filepath.Join(config.GetHomeDir(), "tmp", path)).Run()
|
||||
}
|
||||
|
||||
func clientGitSetRemote(path string, remoteName string, remoteUrl string) error {
|
||||
return exec.Command("git", "-C", filepath.Join(config.GetHomeDir(), "tmp", path), "remote", "add", remoteName, remoteUrl).Run()
|
||||
}
|
||||
|
||||
func clientCheckRepo(url string, file string) error {
|
||||
_, err := os.ReadFile(filepath.Join(config.GetHomeDir(), "tmp", url, file))
|
||||
return err
|
||||
}
|
||||
|
||||
func gitCloneCheckPush(t *testing.T, credentials, owner, url, filename, pushOptions string, expectErrorClone, expectErrorCheck, expectErrorPush bool) {
|
||||
log.Debug().Msgf("Testing %s %s %t %t %t", credentials, url, expectErrorClone, expectErrorCheck, expectErrorPush)
|
||||
err := clientGitClone(credentials, owner, url)
|
||||
if expectErrorClone {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
err = clientCheckRepo(url, filename)
|
||||
if expectErrorCheck {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
err = clientGitPush(url, pushOptions, filename)
|
||||
if expectErrorPush {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func gitInitPush(t *testing.T, credentials, owner, url, filename, pushOptions string, expectErrorPush bool) {
|
||||
log.Debug().Msgf("Testing %s %s %t", credentials, url, expectErrorPush)
|
||||
err := clientGitInit(url)
|
||||
require.NoError(t, err)
|
||||
if url == "init" {
|
||||
err = clientGitSetRemote(url, "origin", "http://"+credentials+"@localhost:6157/init/")
|
||||
} else {
|
||||
err = clientGitSetRemote(url, "origin", "http://"+credentials+"@localhost:6157/"+owner+"/"+url)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
err = clientGitPush(url, pushOptions, filename)
|
||||
if expectErrorPush {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@@ -153,8 +154,16 @@ func Setup(t *testing.T) *TestServer {
|
||||
|
||||
config.C.Index = ""
|
||||
config.C.LogLevel = "error"
|
||||
config.C.GitDefaultBranch = "master"
|
||||
config.InitLog()
|
||||
|
||||
err = exec.Command("git", "config", "--global", "--type", "bool", "push.autoSetupRemote", "true").Run()
|
||||
require.NoError(t, err)
|
||||
err = exec.Command("git", "config", "--global", "user.email", "test@opengist.io").Run()
|
||||
require.NoError(t, err)
|
||||
err = exec.Command("git", "config", "--global", "user.name", "test").Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
homePath := config.GetHomeDir()
|
||||
log.Info().Msg("Data directory: " + homePath)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user