Drop SSPI auth support and more Windows files (#7148)
## Dropping SSPI auth support SSPI authentication relied on Microsoft Windows support, removal started in https://codeberg.org/forgejo/forgejo/pulls/5353, because it was broken anyway. We have no knowledge of any users using SSPI authentication. However, if you somehow managed to run Forgejo on Windows, or want to upgrade from a Gitea version which does, please ensure that you do not use SSPI as an authentication mechanism for user accounts. Feel free to reach out if you need assistance. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7148 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: 0ko <0ko@noreply.codeberg.org> Co-authored-by: Otto Richter <otto@codeberg.org> Co-committed-by: Otto Richter <otto@codeberg.org>
This commit is contained in:
		
					parent
					
						
							
								3de904c963
							
						
					
				
			
			
				commit
				
					
						9dea54a9d6
					
				
			
		
					 43 changed files with 39 additions and 816 deletions
				
			
		
							
								
								
									
										27
									
								
								Makefile
									
										
									
									
									
								
							
							
						
						
									
										27
									
								
								Makefile
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -59,20 +59,8 @@ ifeq ($(HAS_GO), yes)
 | 
			
		|||
	CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
ifeq ($(GOOS),windows)
 | 
			
		||||
	IS_WINDOWS := yes
 | 
			
		||||
else ifeq ($(patsubst Windows%,Windows,$(OS)),Windows)
 | 
			
		||||
	ifeq ($(GOOS),)
 | 
			
		||||
		IS_WINDOWS := yes
 | 
			
		||||
	endif
 | 
			
		||||
endif
 | 
			
		||||
ifeq ($(IS_WINDOWS),yes)
 | 
			
		||||
	GOFLAGS := -v -buildmode=exe
 | 
			
		||||
	EXECUTABLE ?= gitea.exe
 | 
			
		||||
else
 | 
			
		||||
	GOFLAGS := -v
 | 
			
		||||
	EXECUTABLE ?= gitea
 | 
			
		||||
endif
 | 
			
		||||
GOFLAGS := -v
 | 
			
		||||
EXECUTABLE ?= gitea
 | 
			
		||||
 | 
			
		||||
ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu)
 | 
			
		||||
	SED_INPLACE := sed -i
 | 
			
		||||
| 
						 | 
				
			
			@ -498,13 +486,6 @@ lint-go-fix:
 | 
			
		|||
	$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) --fix
 | 
			
		||||
	$(RUN_DEADCODE) > .deadcode-out
 | 
			
		||||
 | 
			
		||||
# workaround step for the lint-go-windows CI task because 'go run' can not
 | 
			
		||||
# have distinct GOOS/GOARCH for its build and run steps
 | 
			
		||||
.PHONY: lint-go-windows
 | 
			
		||||
lint-go-windows:
 | 
			
		||||
	@GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE)
 | 
			
		||||
	golangci-lint run
 | 
			
		||||
 | 
			
		||||
.PHONY: lint-go-vet
 | 
			
		||||
lint-go-vet:
 | 
			
		||||
	@echo "Running go vet..."
 | 
			
		||||
| 
						 | 
				
			
			@ -877,10 +858,6 @@ sources-tarbal: frontend generate vendor release-sources release-check
 | 
			
		|||
$(DIST_DIRS):
 | 
			
		||||
	mkdir -p $(DIST_DIRS)
 | 
			
		||||
 | 
			
		||||
.PHONY: release-windows
 | 
			
		||||
release-windows: | $(DIST_DIRS)
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -buildmode exe -dest $(DIST)/binaries -tags 'osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
 | 
			
		||||
 | 
			
		||||
.PHONY: release-linux
 | 
			
		||||
release-linux: | $(DIST_DIRS)
 | 
			
		||||
	CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets '$(LINUX_ARCHS)' -out forgejo-$(VERSION) .
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -87,19 +87,16 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
 | 
			
		|||
	}
 | 
			
		||||
	defer f.Close()
 | 
			
		||||
 | 
			
		||||
	// Note: chmod command does not support in Windows.
 | 
			
		||||
	if !setting.IsWindows {
 | 
			
		||||
		fi, err := f.Stat()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	fi, err := f.Stat()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		// .ssh directory should have mode 700, and authorized_keys file should have mode 600.
 | 
			
		||||
		if fi.Mode().Perm() > 0o600 {
 | 
			
		||||
			log.Error("authorized_keys file has unusual permission flags: %s - setting to -rw-------", fi.Mode().Perm().String())
 | 
			
		||||
			if err = f.Chmod(0o600); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
	// .ssh directory should have mode 700, and authorized_keys file should have mode 600.
 | 
			
		||||
	if fi.Mode().Perm() > 0o600 {
 | 
			
		||||
		log.Error("authorized_keys file has unusual permission flags: %s - setting to -rw-------", fi.Mode().Perm().String())
 | 
			
		||||
		if err = f.Chmod(0o600); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,7 @@ const (
 | 
			
		|||
	PAM         // 4
 | 
			
		||||
	DLDAP       // 5
 | 
			
		||||
	OAuth2      // 6
 | 
			
		||||
	SSPI        // 7
 | 
			
		||||
	_           // 7 (was SSPI)
 | 
			
		||||
	Remote      // 8
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -53,7 +53,6 @@ var Names = map[Type]string{
 | 
			
		|||
	SMTP:   "SMTP",
 | 
			
		||||
	PAM:    "PAM",
 | 
			
		||||
	OAuth2: "OAuth2",
 | 
			
		||||
	SSPI:   "SPNEGO with SSPI",
 | 
			
		||||
	Remote: "Remote",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -178,11 +177,6 @@ func (source *Source) IsOAuth2() bool {
 | 
			
		|||
	return source.Type == OAuth2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsSSPI returns true of this source is of the SSPI type.
 | 
			
		||||
func (source *Source) IsSSPI() bool {
 | 
			
		||||
	return source.Type == SSPI
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (source *Source) IsRemote() bool {
 | 
			
		||||
	return source.Type == Remote
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -265,20 +259,6 @@ func (opts FindSourcesOptions) ToConds() builder.Cond {
 | 
			
		|||
	return conds
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsSSPIEnabled returns true if there is at least one activated login
 | 
			
		||||
// source of type LoginSSPI
 | 
			
		||||
func IsSSPIEnabled(ctx context.Context) bool {
 | 
			
		||||
	exist, err := db.Exist[Source](ctx, FindSourcesOptions{
 | 
			
		||||
		IsActive:  optional.Some(true),
 | 
			
		||||
		LoginType: SSPI,
 | 
			
		||||
	}.ToConds())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("IsSSPIEnabled: failed to query active SSPI sources: %v", err)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return exist
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetSourceByID returns login source by given ID.
 | 
			
		||||
func GetSourceByID(ctx context.Context, id int64) (*Source, error) {
 | 
			
		||||
	source := new(Source)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,6 @@ import (
 | 
			
		|||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
| 
						 | 
				
			
			@ -123,9 +122,6 @@ func MainTest(m *testing.M) {
 | 
			
		|||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
	giteaBinary := "gitea"
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		giteaBinary += ".exe"
 | 
			
		||||
	}
 | 
			
		||||
	setting.AppPath = path.Join(giteaRoot, giteaBinary)
 | 
			
		||||
	if _, err := os.Stat(setting.AppPath); err != nil {
 | 
			
		||||
		fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -139,7 +139,7 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath
 | 
			
		|||
	cmd := NewCommandContextNoGlobals(ctx, "blame", "--porcelain")
 | 
			
		||||
	if ignoreRevsFile != nil {
 | 
			
		||||
		// Possible improvement: use --ignore-revs-file /dev/stdin on unix
 | 
			
		||||
		// There is no equivalent on Windows. May be implemented if Gitea uses an external git backend.
 | 
			
		||||
		// This was not done in Gitea because it would not have been compatible with Windows.
 | 
			
		||||
		cmd.AddOptionValues("--ignore-revs-file", *ignoreRevsFile)
 | 
			
		||||
	}
 | 
			
		||||
	cmd.AddDynamicArguments(commit.ID.String()).
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,6 @@ import (
 | 
			
		|||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"runtime/trace"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
| 
						 | 
				
			
			@ -359,17 +358,6 @@ func (c *Command) Run(opts *RunOpts) error {
 | 
			
		|||
		log.Debug("slow git.Command.Run: %s (%s)", c, elapsed)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// We need to check if the context is canceled by the program on Windows.
 | 
			
		||||
	// This is because Windows does not have signal checking when terminating the process.
 | 
			
		||||
	// It always returns exit code 1, unlike Linux, which has many exit codes for signals.
 | 
			
		||||
	if runtime.GOOS == "windows" &&
 | 
			
		||||
		err != nil &&
 | 
			
		||||
		err.Error() == "" &&
 | 
			
		||||
		cmd.ProcessState.ExitCode() == 1 &&
 | 
			
		||||
		ctx.Err() == context.Canceled {
 | 
			
		||||
		return ctx.Err()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err != nil && ctx.Err() != context.DeadlineExceeded {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,15 +59,7 @@ func loadGitVersion() error {
 | 
			
		|||
		return fmt.Errorf("invalid git version output: %s", stdout)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var versionString string
 | 
			
		||||
 | 
			
		||||
	// Handle special case on Windows.
 | 
			
		||||
	i := strings.Index(fields[2], "windows")
 | 
			
		||||
	if i >= 1 {
 | 
			
		||||
		versionString = fields[2][:i-1]
 | 
			
		||||
	} else {
 | 
			
		||||
		versionString = fields[2]
 | 
			
		||||
	}
 | 
			
		||||
	versionString := fields[2]
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	gitVersion, err = version.NewVersion(versionString)
 | 
			
		||||
| 
						 | 
				
			
			@ -280,24 +272,11 @@ func syncGitConfig() (err error) {
 | 
			
		|||
	// Thus the owner uid/gid for files on these filesystems will be marked as root.
 | 
			
		||||
	// As Gitea now always use its internal git config file, and access to the git repositories is managed through Gitea,
 | 
			
		||||
	// it is now safe to set "safe.directory=*" for internal usage only.
 | 
			
		||||
	// Please note: the wildcard "*" is only supported by Git 2.30.4/2.31.3/2.32.2/2.33.3/2.34.3/2.35.3/2.36 and later
 | 
			
		||||
	// Although only supported by Git 2.30.4/2.31.3/2.32.2/2.33.3/2.34.3/2.35.3/2.36 and later - this setting is tolerated by earlier versions
 | 
			
		||||
	// Please note: the wildcard "*" is only supported by Git 2.30.4/2.31.3/2.32.2/2.33.3/2.34.3/2.35.3/2.36 and later,
 | 
			
		||||
	// but is tolerated by earlier versions
 | 
			
		||||
	if err := configAddNonExist("safe.directory", "*"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		if err := configSet("core.longpaths", "true"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if setting.Git.DisableCoreProtectNTFS {
 | 
			
		||||
			err = configSet("core.protectNTFS", "false")
 | 
			
		||||
		} else {
 | 
			
		||||
			err = configUnsetAll("core.protectNTFS", "false")
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// By default partial clones are disabled, enable them from git v2.22
 | 
			
		||||
	if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,6 @@
 | 
			
		|||
// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
//go:build !windows
 | 
			
		||||
 | 
			
		||||
package graceful
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,8 +3,6 @@
 | 
			
		|||
 | 
			
		||||
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
 | 
			
		||||
 | 
			
		||||
//go:build !windows
 | 
			
		||||
 | 
			
		||||
package graceful
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,8 +3,6 @@
 | 
			
		|||
 | 
			
		||||
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
 | 
			
		||||
 | 
			
		||||
//go:build !windows
 | 
			
		||||
 | 
			
		||||
package graceful
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,6 @@
 | 
			
		|||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
//go:build !windows
 | 
			
		||||
 | 
			
		||||
package log
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,42 +0,0 @@
 | 
			
		|||
// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package log
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/mattn/go-isatty"
 | 
			
		||||
	"golang.org/x/sys/windows"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func enableVTMode(console windows.Handle) bool {
 | 
			
		||||
	mode := uint32(0)
 | 
			
		||||
	err := windows.GetConsoleMode(console, &mode)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// EnableVirtualTerminalProcessing is the console mode to allow ANSI code
 | 
			
		||||
	// interpretation on the console. See:
 | 
			
		||||
	// https://docs.microsoft.com/en-us/windows/console/setconsolemode
 | 
			
		||||
	// It only works on Windows 10. Earlier terminals will fail with an err which we will
 | 
			
		||||
	// handle to say don't color
 | 
			
		||||
	mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
 | 
			
		||||
	err = windows.SetConsoleMode(console, mode)
 | 
			
		||||
	return err == nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	if isatty.IsTerminal(os.Stdout.Fd()) {
 | 
			
		||||
		CanColorStdout = enableVTMode(windows.Stdout)
 | 
			
		||||
	} else {
 | 
			
		||||
		CanColorStdout = isatty.IsCygwinTerminal(os.Stderr.Fd())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if isatty.IsTerminal(os.Stderr.Fd()) {
 | 
			
		||||
		CanColorStderr = enableVTMode(windows.Stderr)
 | 
			
		||||
	} else {
 | 
			
		||||
		CanColorStderr = isatty.IsCygwinTerminal(os.Stderr.Fd())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								modules/markup/external/external.go
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								modules/markup/external/external.go
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -9,7 +9,6 @@ import (
 | 
			
		|||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/graceful"
 | 
			
		||||
| 
						 | 
				
			
			@ -70,9 +69,6 @@ func (p *Renderer) DisplayInIFrame() bool {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func envMark(envName string) string {
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		return "%" + envName + "%"
 | 
			
		||||
	}
 | 
			
		||||
	return "$" + envName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,6 @@
 | 
			
		|||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
//go:build !windows
 | 
			
		||||
 | 
			
		||||
package process
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,6 @@ import (
 | 
			
		|||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
| 
						 | 
				
			
			@ -146,10 +145,6 @@ func CreateDelegateHooks(repoPath string) (err error) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func checkExecutable(filename string) bool {
 | 
			
		||||
	// windows has no concept of a executable bit
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	fileInfo, err := os.Stat(filename)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,11 +34,7 @@ var (
 | 
			
		|||
func getAppPath() (string, error) {
 | 
			
		||||
	var appPath string
 | 
			
		||||
	var err error
 | 
			
		||||
	if IsWindows && filepath.IsAbs(os.Args[0]) {
 | 
			
		||||
		appPath = filepath.Clean(os.Args[0])
 | 
			
		||||
	} else {
 | 
			
		||||
		appPath, err = exec.LookPath(os.Args[0])
 | 
			
		||||
	}
 | 
			
		||||
	appPath, err = exec.LookPath(os.Args[0])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if !errors.Is(err, exec.ErrDot) {
 | 
			
		||||
			return "", err
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,6 @@ package setting
 | 
			
		|||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +33,6 @@ var (
 | 
			
		|||
	RunMode     string
 | 
			
		||||
	RunUser     string
 | 
			
		||||
	IsProd      bool
 | 
			
		||||
	IsWindows   bool
 | 
			
		||||
 | 
			
		||||
	// IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing
 | 
			
		||||
	// TODO: this is only a temporary solution, we should make the test code more reliable
 | 
			
		||||
| 
						 | 
				
			
			@ -42,22 +40,18 @@ var (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	IsWindows = runtime.GOOS == "windows"
 | 
			
		||||
	if AppVer == "" {
 | 
			
		||||
		AppVer = "dev"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// We can rely on log.CanColorStdout being set properly because modules/log/console_windows.go comes before modules/setting/setting.go lexicographically
 | 
			
		||||
	// By default set this logger at Info - we'll change it later, but we need to start with something.
 | 
			
		||||
	log.SetConsoleLogger(log.DEFAULT, "console", log.INFO)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsRunUserMatchCurrentUser returns false if configured run user does not match
 | 
			
		||||
// actual user that runs the app. The first return value is the actual user name.
 | 
			
		||||
// This check is ignored under Windows since SSH remote login is not the main
 | 
			
		||||
// method to login on Windows.
 | 
			
		||||
func IsRunUserMatchCurrentUser(runUser string) (string, bool) {
 | 
			
		||||
	if IsWindows || SSH.StartBuiltinServer {
 | 
			
		||||
	if SSH.StartBuiltinServer {
 | 
			
		||||
		return "", true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,8 +6,6 @@ package user
 | 
			
		|||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/user"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CurrentUsername return current login OS user name
 | 
			
		||||
| 
						 | 
				
			
			@ -16,12 +14,7 @@ func CurrentUsername() string {
 | 
			
		|||
	if err != nil {
 | 
			
		||||
		return fallbackCurrentUsername()
 | 
			
		||||
	}
 | 
			
		||||
	username := userinfo.Username
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		parts := strings.Split(username, "\\")
 | 
			
		||||
		username = parts[len(parts)-1]
 | 
			
		||||
	}
 | 
			
		||||
	return username
 | 
			
		||||
	return userinfo.Username
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Old method, used if new method doesn't work on your OS for some reason
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,6 @@ package user
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -23,10 +22,6 @@ func TestCurrentUsername(t *testing.T) {
 | 
			
		|||
	if len(user) == 0 {
 | 
			
		||||
		t.Errorf("expected non-empty user, got: %s", user)
 | 
			
		||||
	}
 | 
			
		||||
	// Windows whoami is weird, so just skip remaining tests
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		t.Skip("skipped test because of weird whoami on Windows")
 | 
			
		||||
	}
 | 
			
		||||
	whoami, err := getWhoamiOutput()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("failed to run whoami to test current user: %f", err)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,6 @@
 | 
			
		|||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
//go:build !windows
 | 
			
		||||
 | 
			
		||||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,6 @@
 | 
			
		|||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
//go:build !windows
 | 
			
		||||
 | 
			
		||||
package util
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,8 +10,6 @@ import (
 | 
			
		|||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -78,11 +76,7 @@ func FilePathJoinAbs(base string, sub ...string) string {
 | 
			
		|||
 | 
			
		||||
	// POSIX filesystem can have `\` in file names. Windows: `\` and `/` are both used for path separators
 | 
			
		||||
	// to keep the behavior consistent, we do not allow `\` in file names, replace all `\` with `/`
 | 
			
		||||
	if isOSWindows() {
 | 
			
		||||
		elems[0] = filepath.Clean(base)
 | 
			
		||||
	} else {
 | 
			
		||||
		elems[0] = filepath.Clean(strings.ReplaceAll(base, "\\", pathSeparator))
 | 
			
		||||
	}
 | 
			
		||||
	elems[0] = filepath.Clean(strings.ReplaceAll(base, "\\", pathSeparator))
 | 
			
		||||
	if !filepath.IsAbs(elems[0]) {
 | 
			
		||||
		// This shouldn't happen. If there is really necessary to pass in relative path, return the full path with filepath.Abs() instead
 | 
			
		||||
		panic(fmt.Sprintf("FilePathJoinAbs: %q (for path %v) is not absolute, do not guess a relative path based on current working directory", elems[0], elems))
 | 
			
		||||
| 
						 | 
				
			
			@ -91,11 +85,7 @@ func FilePathJoinAbs(base string, sub ...string) string {
 | 
			
		|||
		if s == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if isOSWindows() {
 | 
			
		||||
			elems = append(elems, filepath.Clean(pathSeparator+s))
 | 
			
		||||
		} else {
 | 
			
		||||
			elems = append(elems, filepath.Clean(pathSeparator+strings.ReplaceAll(s, "\\", pathSeparator)))
 | 
			
		||||
		}
 | 
			
		||||
		elems = append(elems, filepath.Clean(pathSeparator+strings.ReplaceAll(s, "\\", pathSeparator)))
 | 
			
		||||
	}
 | 
			
		||||
	// the elems[0] must be an absolute path, just join them together
 | 
			
		||||
	return filepath.Join(elems...)
 | 
			
		||||
| 
						 | 
				
			
			@ -217,12 +207,6 @@ func StatDir(rootPath string, includeDir ...bool) ([]string, error) {
 | 
			
		|||
	return statDir(rootPath, "", isIncludeDir, false, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isOSWindows() bool {
 | 
			
		||||
	return runtime.GOOS == "windows"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var driveLetterRegexp = regexp.MustCompile("/[A-Za-z]:/")
 | 
			
		||||
 | 
			
		||||
// FileURLToPath extracts the path information from a file://... url.
 | 
			
		||||
// It returns an error only if the URL is not a file URL.
 | 
			
		||||
func FileURLToPath(u *url.URL) (string, error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -230,17 +214,7 @@ func FileURLToPath(u *url.URL) (string, error) {
 | 
			
		|||
		return "", errors.New("URL scheme is not 'file': " + u.String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	path := u.Path
 | 
			
		||||
 | 
			
		||||
	if !isOSWindows() {
 | 
			
		||||
		return path, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If it looks like there's a Windows drive letter at the beginning, strip off the leading slash.
 | 
			
		||||
	if driveLetterRegexp.MatchString(path) {
 | 
			
		||||
		return path[1:], nil
 | 
			
		||||
	}
 | 
			
		||||
	return path, nil
 | 
			
		||||
	return u.Path, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HomeDir returns path of '~'(in Linux) on Windows,
 | 
			
		||||
| 
						 | 
				
			
			@ -249,14 +223,7 @@ func HomeDir() (home string, err error) {
 | 
			
		|||
	// TODO: some users run Gitea with mismatched uid  and "HOME=xxx" (they set HOME=xxx by environment manually)
 | 
			
		||||
	// TODO: when running gitea as a sub command inside git, the HOME directory is not the user's home directory
 | 
			
		||||
	// so at the moment we can not use `user.Current().HomeDir`
 | 
			
		||||
	if isOSWindows() {
 | 
			
		||||
		home = os.Getenv("USERPROFILE")
 | 
			
		||||
		if home == "" {
 | 
			
		||||
			home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		home = os.Getenv("HOME")
 | 
			
		||||
	}
 | 
			
		||||
	home = os.Getenv("HOME")
 | 
			
		||||
 | 
			
		||||
	if home == "" {
 | 
			
		||||
		return "", errors.New("cannot get home directory")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,6 @@ package util
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +16,6 @@ func TestFileURLToPath(t *testing.T) {
 | 
			
		|||
		url      string
 | 
			
		||||
		expected string
 | 
			
		||||
		haserror bool
 | 
			
		||||
		windows  bool
 | 
			
		||||
	}{
 | 
			
		||||
		// case 0
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			@ -34,18 +32,9 @@ func TestFileURLToPath(t *testing.T) {
 | 
			
		|||
			url:      "file:///path",
 | 
			
		||||
			expected: "/path",
 | 
			
		||||
		},
 | 
			
		||||
		// case 3
 | 
			
		||||
		{
 | 
			
		||||
			url:      "file:///C:/path",
 | 
			
		||||
			expected: "C:/path",
 | 
			
		||||
			windows:  true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for n, c := range cases {
 | 
			
		||||
		if c.windows && runtime.GOOS != "windows" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		u, _ := url.Parse(c.url)
 | 
			
		||||
		p, err := FileURLToPath(u)
 | 
			
		||||
		if c.haserror {
 | 
			
		||||
| 
						 | 
				
			
			@ -177,35 +166,18 @@ func TestCleanPath(t *testing.T) {
 | 
			
		|||
		assert.Equal(t, c.expected, PathJoinRelX(c.elems...), "case: %v", c.elems)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// for POSIX only, but the result is similar on Windows, because the first element must be an absolute path
 | 
			
		||||
	if isOSWindows() {
 | 
			
		||||
		cases = []struct {
 | 
			
		||||
			elems    []string
 | 
			
		||||
			expected string
 | 
			
		||||
		}{
 | 
			
		||||
			{[]string{`C:\..`}, `C:\`},
 | 
			
		||||
			{[]string{`C:\a`}, `C:\a`},
 | 
			
		||||
			{[]string{`C:\a/`}, `C:\a`},
 | 
			
		||||
			{[]string{`C:\..\a\`, `../b`, `c\..`, `d`}, `C:\a\b\d`},
 | 
			
		||||
			{[]string{`C:\a/..\b`}, `C:\b`},
 | 
			
		||||
			{[]string{`C:\a`, ``, `b`}, `C:\a\b`},
 | 
			
		||||
			{[]string{`C:\a`, `..`, `b`}, `C:\a\b`},
 | 
			
		||||
			{[]string{`C:\lfs`, `repo/..`, `user/../path`}, `C:\lfs\path`},
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		cases = []struct {
 | 
			
		||||
			elems    []string
 | 
			
		||||
			expected string
 | 
			
		||||
		}{
 | 
			
		||||
			{[]string{`/..`}, `/`},
 | 
			
		||||
			{[]string{`/a`}, `/a`},
 | 
			
		||||
			{[]string{`/a/`}, `/a`},
 | 
			
		||||
			{[]string{`/../a/`, `../b`, `c/..`, `d`}, `/a/b/d`},
 | 
			
		||||
			{[]string{`/a\..\b`}, `/b`},
 | 
			
		||||
			{[]string{`/a`, ``, `b`}, `/a/b`},
 | 
			
		||||
			{[]string{`/a`, `..`, `b`}, `/a/b`},
 | 
			
		||||
			{[]string{`/lfs`, `repo/..`, `user/../path`}, `/lfs/path`},
 | 
			
		||||
		}
 | 
			
		||||
	cases = []struct {
 | 
			
		||||
		elems    []string
 | 
			
		||||
		expected string
 | 
			
		||||
	}{
 | 
			
		||||
		{[]string{`/..`}, `/`},
 | 
			
		||||
		{[]string{`/a`}, `/a`},
 | 
			
		||||
		{[]string{`/a/`}, `/a`},
 | 
			
		||||
		{[]string{`/../a/`, `../b`, `c/..`, `d`}, `/a/b/d`},
 | 
			
		||||
		{[]string{`/a\..\b`}, `/b`},
 | 
			
		||||
		{[]string{`/a`, ``, `b`}, `/a/b`},
 | 
			
		||||
		{[]string{`/a`, `..`, `b`}, `/a/b`},
 | 
			
		||||
		{[]string{`/lfs`, `repo/..`, `user/../path`}, `/lfs/path`},
 | 
			
		||||
	}
 | 
			
		||||
	for _, c := range cases {
 | 
			
		||||
		assert.Equal(t, c.expected, FilePathJoinAbs(c.elems[0], c.elems[1:]...), "case: %v", c.elems)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,13 +5,10 @@ package util
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const windowsSharingViolationError syscall.Errno = 32
 | 
			
		||||
 | 
			
		||||
// Remove removes the named file or (empty) directory with at most 5 attempts.
 | 
			
		||||
func Remove(name string) error {
 | 
			
		||||
	var err error
 | 
			
		||||
| 
						 | 
				
			
			@ -27,12 +24,6 @@ func Remove(name string) error {
 | 
			
		|||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" {
 | 
			
		||||
			// try again
 | 
			
		||||
			<-time.After(100 * time.Millisecond)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if unwrapped == syscall.ENOENT {
 | 
			
		||||
			// it's already gone
 | 
			
		||||
			return nil
 | 
			
		||||
| 
						 | 
				
			
			@ -56,12 +47,6 @@ func RemoveAll(name string) error {
 | 
			
		|||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" {
 | 
			
		||||
			// try again
 | 
			
		||||
			<-time.After(100 * time.Millisecond)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if unwrapped == syscall.ENOENT {
 | 
			
		||||
			// it's already gone
 | 
			
		||||
			return nil
 | 
			
		||||
| 
						 | 
				
			
			@ -85,12 +70,6 @@ func Rename(oldpath, newpath string) error {
 | 
			
		|||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" {
 | 
			
		||||
			// try again
 | 
			
		||||
			<-time.After(100 * time.Millisecond)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if i == 0 && os.IsNotExist(err) {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -609,9 +609,6 @@ CommitChoice = Commit choice
 | 
			
		|||
TreeName = File path
 | 
			
		||||
Content = Content
 | 
			
		||||
 | 
			
		||||
SSPISeparatorReplacement = Separator
 | 
			
		||||
SSPIDefaultLanguage = Default language
 | 
			
		||||
 | 
			
		||||
require_error = ` cannot be empty.`
 | 
			
		||||
alpha_dash_error = ` should contain only alphanumeric, dash ("-") and underscore ("_") characters.`
 | 
			
		||||
alpha_dash_dot_error = ` should contain only alphanumeric, dash ("-"), underscore ("_") and dot (".") characters.`
 | 
			
		||||
| 
						 | 
				
			
			@ -3300,16 +3297,6 @@ auths.oauth2_admin_group = Group claim value for administrator users. (Optional
 | 
			
		|||
auths.oauth2_restricted_group = Group claim value for restricted users. (Optional - requires claim name above)
 | 
			
		||||
auths.oauth2_map_group_to_team = Map claimed groups to organization teams. (Optional - requires claim name above)
 | 
			
		||||
auths.oauth2_map_group_to_team_removal = Remove users from synchronized teams if user does not belong to corresponding group.
 | 
			
		||||
auths.sspi_auto_create_users = Automatically create users
 | 
			
		||||
auths.sspi_auto_create_users_helper = Allow SSPI auth method to automatically create new accounts for users that login for the first time
 | 
			
		||||
auths.sspi_auto_activate_users = Automatically activate users
 | 
			
		||||
auths.sspi_auto_activate_users_helper = Allow SSPI auth method to automatically activate new users
 | 
			
		||||
auths.sspi_strip_domain_names = Remove domain names from usernames
 | 
			
		||||
auths.sspi_strip_domain_names_helper = If checked, domain names will be removed from logon names (eg. "DOMAIN\user" and "user@example.org" both will become just "user").
 | 
			
		||||
auths.sspi_separator_replacement = Separator to use instead of \, / and @
 | 
			
		||||
auths.sspi_separator_replacement_helper = The character to use to replace the separators of down-level logon names (eg. the \ in "DOMAIN\user") and user principal names (eg. the @ in "user@example.org").
 | 
			
		||||
auths.sspi_default_language = Default user language
 | 
			
		||||
auths.sspi_default_language_helper = Default language for users automatically created by SSPI auth method. Leave empty if you prefer language to be automatically detected.
 | 
			
		||||
auths.tips = Tips
 | 
			
		||||
auths.tips.gmail_settings = Gmail settings:
 | 
			
		||||
auths.tips.oauth2.general = OAuth2 authentication
 | 
			
		||||
| 
						 | 
				
			
			@ -3965,4 +3952,4 @@ filepreview.lines = Lines %[1]d to %[2]d in %[3]s
 | 
			
		|||
filepreview.truncated = Preview has been truncated
 | 
			
		||||
 | 
			
		||||
[translation_meta]
 | 
			
		||||
test = This is a test string. It is not displayed in Forgejo UI but is used for testing purposes. Feel free to enter "ok" to save time (or a fun fact of your choice) to hit that sweet 100% completion mark :)
 | 
			
		||||
test = This is a test string. It is not displayed in Forgejo UI but is used for testing purposes. Feel free to enter "ok" to save time (or a fun fact of your choice) to hit that sweet 100% completion mark :)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,8 +6,6 @@ package shared
 | 
			
		|||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	auth_model "code.gitea.io/gitea/models/auth"
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/routers/common"
 | 
			
		||||
| 
						 | 
				
			
			@ -51,10 +49,6 @@ func buildAuthGroup() *auth.Group {
 | 
			
		|||
		group.Add(&auth.ReverseProxy{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if setting.IsWindows && auth_model.IsSSPIEnabled(db.DefaultContext) {
 | 
			
		||||
		group.Add(&auth.SSPI{}) // it MUST be the last, see the comment of SSPI
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return group
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,6 @@ import (
 | 
			
		|||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/templates"
 | 
			
		||||
	"code.gitea.io/gitea/modules/translation"
 | 
			
		||||
	"code.gitea.io/gitea/modules/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web/middleware"
 | 
			
		||||
	"code.gitea.io/gitea/routers/common"
 | 
			
		||||
| 
						 | 
				
			
			@ -119,15 +118,7 @@ func Install(ctx *context.Context) {
 | 
			
		|||
	form.AppSlogan = "Beyond coding. We Forge."
 | 
			
		||||
	form.RepoRootPath = setting.RepoRootPath
 | 
			
		||||
	form.LFSRootPath = setting.LFS.Storage.Path
 | 
			
		||||
 | 
			
		||||
	// Note(unknown): it's hard for Windows users change a running user,
 | 
			
		||||
	// 	so just use current one if config says default.
 | 
			
		||||
	if setting.IsWindows && setting.RunUser == "git" {
 | 
			
		||||
		form.RunUser = user.CurrentUsername()
 | 
			
		||||
	} else {
 | 
			
		||||
		form.RunUser = setting.RunUser
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	form.RunUser = setting.RunUser
 | 
			
		||||
	form.Domain = setting.Domain
 | 
			
		||||
	form.SSHPort = setting.SSH.Port
 | 
			
		||||
	form.HTTPPort = setting.HTTPPort
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,6 @@
 | 
			
		|||
// Copyright 2020 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
//go:build !windows
 | 
			
		||||
 | 
			
		||||
package private
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,11 +4,9 @@
 | 
			
		|||
package admin
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -18,14 +16,12 @@ import (
 | 
			
		|||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	auth_service "code.gitea.io/gitea/services/auth"
 | 
			
		||||
	"code.gitea.io/gitea/services/auth/source/ldap"
 | 
			
		||||
	"code.gitea.io/gitea/services/auth/source/oauth2"
 | 
			
		||||
	pam_service "code.gitea.io/gitea/services/auth/source/pam"
 | 
			
		||||
	"code.gitea.io/gitea/services/auth/source/smtp"
 | 
			
		||||
	"code.gitea.io/gitea/services/auth/source/sspi"
 | 
			
		||||
	"code.gitea.io/gitea/services/context"
 | 
			
		||||
	"code.gitea.io/gitea/services/forms"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -38,11 +34,6 @@ const (
 | 
			
		|||
	tplAuthEdit base.TplName = "admin/auth/edit"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	separatorAntiPattern = regexp.MustCompile(`[^\w-\.]`)
 | 
			
		||||
	langCodePattern      = regexp.MustCompile(`^[a-z]{2}-[A-Z]{2}$`)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Authentications show authentication config page
 | 
			
		||||
func Authentications(ctx *context.Context) {
 | 
			
		||||
	ctx.Data["Title"] = ctx.Tr("admin.authentication")
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +61,6 @@ var (
 | 
			
		|||
			{auth.DLDAP.String(), auth.DLDAP},
 | 
			
		||||
			{auth.SMTP.String(), auth.SMTP},
 | 
			
		||||
			{auth.OAuth2.String(), auth.OAuth2},
 | 
			
		||||
			{auth.SSPI.String(), auth.SSPI},
 | 
			
		||||
		}
 | 
			
		||||
		if pam.Supported {
 | 
			
		||||
			items = append(items, dropdownItem{auth.Names[auth.PAM], auth.PAM})
 | 
			
		||||
| 
						 | 
				
			
			@ -102,12 +92,6 @@ func NewAuthSource(ctx *context.Context) {
 | 
			
		|||
	oauth2providers := oauth2.GetSupportedOAuth2Providers()
 | 
			
		||||
	ctx.Data["OAuth2Providers"] = oauth2providers
 | 
			
		||||
 | 
			
		||||
	ctx.Data["SSPIAutoCreateUsers"] = true
 | 
			
		||||
	ctx.Data["SSPIAutoActivateUsers"] = true
 | 
			
		||||
	ctx.Data["SSPIStripDomainNames"] = true
 | 
			
		||||
	ctx.Data["SSPISeparatorReplacement"] = "_"
 | 
			
		||||
	ctx.Data["SSPIDefaultLanguage"] = ""
 | 
			
		||||
 | 
			
		||||
	// only the first as default
 | 
			
		||||
	ctx.Data["oauth2_provider"] = oauth2providers[0].Name()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -209,30 +193,6 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi.Source, error) {
 | 
			
		||||
	if util.IsEmptyString(form.SSPISeparatorReplacement) {
 | 
			
		||||
		ctx.Data["Err_SSPISeparatorReplacement"] = true
 | 
			
		||||
		return nil, errors.New(ctx.Locale.TrString("form.SSPISeparatorReplacement") + ctx.Locale.TrString("form.require_error"))
 | 
			
		||||
	}
 | 
			
		||||
	if separatorAntiPattern.MatchString(form.SSPISeparatorReplacement) {
 | 
			
		||||
		ctx.Data["Err_SSPISeparatorReplacement"] = true
 | 
			
		||||
		return nil, errors.New(ctx.Locale.TrString("form.SSPISeparatorReplacement") + ctx.Locale.TrString("form.alpha_dash_dot_error"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.SSPIDefaultLanguage != "" && !langCodePattern.MatchString(form.SSPIDefaultLanguage) {
 | 
			
		||||
		ctx.Data["Err_SSPIDefaultLanguage"] = true
 | 
			
		||||
		return nil, errors.New(ctx.Locale.TrString("form.lang_select_error"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &sspi.Source{
 | 
			
		||||
		AutoCreateUsers:      form.SSPIAutoCreateUsers,
 | 
			
		||||
		AutoActivateUsers:    form.SSPIAutoActivateUsers,
 | 
			
		||||
		StripDomainNames:     form.SSPIStripDomainNames,
 | 
			
		||||
		SeparatorReplacement: form.SSPISeparatorReplacement,
 | 
			
		||||
		DefaultLanguage:      form.SSPIDefaultLanguage,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAuthSourcePost response for adding an auth source
 | 
			
		||||
func NewAuthSourcePost(ctx *context.Context) {
 | 
			
		||||
	form := *web.GetForm(ctx).(*forms.AuthenticationForm)
 | 
			
		||||
| 
						 | 
				
			
			@ -247,12 +207,6 @@ func NewAuthSourcePost(ctx *context.Context) {
 | 
			
		|||
	oauth2providers := oauth2.GetSupportedOAuth2Providers()
 | 
			
		||||
	ctx.Data["OAuth2Providers"] = oauth2providers
 | 
			
		||||
 | 
			
		||||
	ctx.Data["SSPIAutoCreateUsers"] = true
 | 
			
		||||
	ctx.Data["SSPIAutoActivateUsers"] = true
 | 
			
		||||
	ctx.Data["SSPIStripDomainNames"] = true
 | 
			
		||||
	ctx.Data["SSPISeparatorReplacement"] = "_"
 | 
			
		||||
	ctx.Data["SSPIDefaultLanguage"] = ""
 | 
			
		||||
 | 
			
		||||
	hasTLS := false
 | 
			
		||||
	var config convert.Conversion
 | 
			
		||||
	switch auth.Type(form.Type) {
 | 
			
		||||
| 
						 | 
				
			
			@ -279,19 +233,6 @@ func NewAuthSourcePost(ctx *context.Context) {
 | 
			
		|||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	case auth.SSPI:
 | 
			
		||||
		var err error
 | 
			
		||||
		config, err = parseSSPIConfig(ctx, form)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.RenderWithErr(err.Error(), tplAuthNew, form)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		existing, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{LoginType: auth.SSPI})
 | 
			
		||||
		if err != nil || len(existing) > 0 {
 | 
			
		||||
			ctx.Data["Err_Type"] = true
 | 
			
		||||
			ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_of_type_exist"), tplAuthNew, form)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		ctx.Error(http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
| 
						 | 
				
			
			@ -408,12 +349,6 @@ func EditAuthSourcePost(ctx *context.Context) {
 | 
			
		|||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	case auth.SSPI:
 | 
			
		||||
		config, err = parseSSPIConfig(ctx, form)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.RenderWithErr(err.Error(), tplAuthEdit, form)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		ctx.Error(http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -164,7 +164,6 @@ func SignIn(ctx *context.Context) {
 | 
			
		|||
	ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
 | 
			
		||||
	ctx.Data["PageIsSignIn"] = true
 | 
			
		||||
	ctx.Data["PageIsLogin"] = true
 | 
			
		||||
	ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
 | 
			
		||||
	ctx.Data["EnableInternalSignIn"] = setting.Service.EnableInternalSignIn
 | 
			
		||||
 | 
			
		||||
	if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
 | 
			
		||||
| 
						 | 
				
			
			@ -190,7 +189,6 @@ func SignInPost(ctx *context.Context) {
 | 
			
		|||
	ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
 | 
			
		||||
	ctx.Data["PageIsSignIn"] = true
 | 
			
		||||
	ctx.Data["PageIsLogin"] = true
 | 
			
		||||
	ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
 | 
			
		||||
	ctx.Data["EnableInternalSignIn"] = setting.Service.EnableInternalSignIn
 | 
			
		||||
	ctx.Data["DisablePassword"] = !setting.Service.EnableInternalSignIn
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,8 +8,6 @@ import (
 | 
			
		|||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	auth_model "code.gitea.io/gitea/models/auth"
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/perm"
 | 
			
		||||
	quota_model "code.gitea.io/gitea/models/quota"
 | 
			
		||||
	"code.gitea.io/gitea/models/unit"
 | 
			
		||||
| 
						 | 
				
			
			@ -110,10 +108,6 @@ func buildAuthGroup() *auth_service.Group {
 | 
			
		|||
	}
 | 
			
		||||
	group.Add(&auth_service.Session{})
 | 
			
		||||
 | 
			
		||||
	if setting.IsWindows && auth_model.IsSSPIEnabled(db.DefaultContext) {
 | 
			
		||||
		group.Add(&auth_service.SSPI{}) // it MUST be the last, see the comment of SSPI
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return group
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,7 +18,6 @@ import (
 | 
			
		|||
	_ "code.gitea.io/gitea/services/auth/source/db"   // register the sources (and below)
 | 
			
		||||
	_ "code.gitea.io/gitea/services/auth/source/ldap" // register the ldap source
 | 
			
		||||
	_ "code.gitea.io/gitea/services/auth/source/pam"  // register the pam source
 | 
			
		||||
	_ "code.gitea.io/gitea/services/auth/source/sspi" // register the sspi source
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UserSignIn validates user name and password.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,18 +0,0 @@
 | 
			
		|||
// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package sspi_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"code.gitea.io/gitea/models/auth"
 | 
			
		||||
	"code.gitea.io/gitea/services/auth/source/sspi"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// This test file exists to assert that our Source exposes the interfaces that we expect
 | 
			
		||||
// It tightly binds the interfaces and implementation without breaking go import cycles
 | 
			
		||||
 | 
			
		||||
type sourceInterface interface {
 | 
			
		||||
	auth.Config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ (sourceInterface) = &sspi.Source{}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,39 +0,0 @@
 | 
			
		|||
// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package sspi
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"code.gitea.io/gitea/models/auth"
 | 
			
		||||
	"code.gitea.io/gitea/modules/json"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//   _________ ___________________.___
 | 
			
		||||
//  /   _____//   _____/\______   \   |
 | 
			
		||||
//  \_____  \ \_____  \  |     ___/   |
 | 
			
		||||
//  /        \/        \ |    |   |   |
 | 
			
		||||
// /_______  /_______  / |____|   |___|
 | 
			
		||||
//         \/        \/
 | 
			
		||||
 | 
			
		||||
// Source holds configuration for SSPI single sign-on.
 | 
			
		||||
type Source struct {
 | 
			
		||||
	AutoCreateUsers      bool
 | 
			
		||||
	AutoActivateUsers    bool
 | 
			
		||||
	StripDomainNames     bool
 | 
			
		||||
	SeparatorReplacement string
 | 
			
		||||
	DefaultLanguage      string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FromDB fills up an SSPIConfig from serialized format.
 | 
			
		||||
func (cfg *Source) FromDB(bs []byte) error {
 | 
			
		||||
	return json.UnmarshalHandleDoubleEncode(bs, &cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToDB exports an SSPIConfig to a serialized format.
 | 
			
		||||
func (cfg *Source) ToDB() ([]byte, error) {
 | 
			
		||||
	return json.Marshal(cfg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	auth.RegisterTypeConfig(auth.SSPI, &Source{})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,223 +0,0 @@
 | 
			
		|||
// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package auth
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/auth"
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/base"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/optional"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web/middleware"
 | 
			
		||||
	"code.gitea.io/gitea/services/auth/source/sspi"
 | 
			
		||||
	gitea_context "code.gitea.io/gitea/services/context"
 | 
			
		||||
 | 
			
		||||
	gouuid "github.com/google/uuid"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	tplSignIn base.TplName = "user/auth/signin"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SSPIAuth interface {
 | 
			
		||||
	AppendAuthenticateHeader(w http.ResponseWriter, data string)
 | 
			
		||||
	Authenticate(r *http.Request, w http.ResponseWriter) (userInfo *SSPIUserInfo, outToken string, err error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	sspiAuth        SSPIAuth // a global instance of the websspi authenticator to avoid acquiring the server credential handle on every request
 | 
			
		||||
	sspiAuthOnce    sync.Once
 | 
			
		||||
	sspiAuthErrInit error
 | 
			
		||||
 | 
			
		||||
	// Ensure the struct implements the interface.
 | 
			
		||||
	_ Method = &SSPI{}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SSPI implements the SingleSignOn interface and authenticates requests
 | 
			
		||||
// via the built-in SSPI module in Windows for SPNEGO authentication.
 | 
			
		||||
// The SSPI plugin is expected to be executed last, as it returns 401 status code if negotiation
 | 
			
		||||
// fails (or if negotiation should continue), which would prevent other authentication methods
 | 
			
		||||
// to execute at all.
 | 
			
		||||
type SSPI struct{}
 | 
			
		||||
 | 
			
		||||
// Name represents the name of auth method
 | 
			
		||||
func (s *SSPI) Name() string {
 | 
			
		||||
	return "sspi"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Verify uses SSPI (Windows implementation of SPNEGO) to authenticate the request.
 | 
			
		||||
// If authentication is successful, returns the corresponding user object.
 | 
			
		||||
// If negotiation should continue or authentication fails, immediately returns a 401 HTTP
 | 
			
		||||
// response code, as required by the SPNEGO protocol.
 | 
			
		||||
func (s *SSPI) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
 | 
			
		||||
	sspiAuthOnce.Do(func() { sspiAuthErrInit = sspiAuthInit() })
 | 
			
		||||
	if sspiAuthErrInit != nil {
 | 
			
		||||
		return nil, sspiAuthErrInit
 | 
			
		||||
	}
 | 
			
		||||
	if !s.shouldAuthenticate(req) {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cfg, err := s.getConfig(req.Context())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("could not get SSPI config: %v", err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Trace("SSPI Authorization: Attempting to authenticate")
 | 
			
		||||
	userInfo, outToken, err := sspiAuth.Authenticate(req, w)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Warn("Authentication failed with error: %v\n", err)
 | 
			
		||||
		sspiAuth.AppendAuthenticateHeader(w, outToken)
 | 
			
		||||
 | 
			
		||||
		// Include the user login page in the 401 response to allow the user
 | 
			
		||||
		// to login with another authentication method if SSPI authentication
 | 
			
		||||
		// fails
 | 
			
		||||
		store.GetData()["Flash"] = map[string]string{
 | 
			
		||||
			"ErrorMsg": err.Error(),
 | 
			
		||||
		}
 | 
			
		||||
		store.GetData()["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
 | 
			
		||||
		store.GetData()["EnableSSPI"] = true
 | 
			
		||||
		// in this case, the Verify function is called in Gitea's web context
 | 
			
		||||
		// FIXME: it doesn't look good to render the page here, why not redirect?
 | 
			
		||||
		gitea_context.GetWebContext(req).HTML(http.StatusUnauthorized, tplSignIn)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if outToken != "" {
 | 
			
		||||
		sspiAuth.AppendAuthenticateHeader(w, outToken)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	username := sanitizeUsername(userInfo.Username, cfg)
 | 
			
		||||
	if len(username) == 0 {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
	log.Info("Authenticated as %s\n", username)
 | 
			
		||||
 | 
			
		||||
	user, err := user_model.GetUserByName(req.Context(), username)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if !user_model.IsErrUserNotExist(err) {
 | 
			
		||||
			log.Error("GetUserByName: %v", err)
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		if !cfg.AutoCreateUsers {
 | 
			
		||||
			log.Error("User '%s' not found", username)
 | 
			
		||||
			return nil, nil
 | 
			
		||||
		}
 | 
			
		||||
		user, err = s.newUser(req.Context(), username, cfg)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Error("CreateUser: %v", err)
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make sure requests to API paths and PWA resources do not create a new session
 | 
			
		||||
	if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) {
 | 
			
		||||
		handleSignIn(w, req, sess, user)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Trace("SSPI Authorization: Logged in user %-v", user)
 | 
			
		||||
	return user, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getConfig retrieves the SSPI configuration from login sources
 | 
			
		||||
func (s *SSPI) getConfig(ctx context.Context) (*sspi.Source, error) {
 | 
			
		||||
	sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
 | 
			
		||||
		IsActive:  optional.Some(true),
 | 
			
		||||
		LoginType: auth.SSPI,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if len(sources) == 0 {
 | 
			
		||||
		return nil, errors.New("no active login sources of type SSPI found")
 | 
			
		||||
	}
 | 
			
		||||
	if len(sources) > 1 {
 | 
			
		||||
		return nil, errors.New("more than one active login source of type SSPI found")
 | 
			
		||||
	}
 | 
			
		||||
	return sources[0].Cfg.(*sspi.Source), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *SSPI) shouldAuthenticate(req *http.Request) (shouldAuth bool) {
 | 
			
		||||
	shouldAuth = false
 | 
			
		||||
	path := strings.TrimSuffix(req.URL.Path, "/")
 | 
			
		||||
	if path == "/user/login" {
 | 
			
		||||
		if req.FormValue("user_name") != "" && req.FormValue("password") != "" {
 | 
			
		||||
			shouldAuth = false
 | 
			
		||||
		} else if req.FormValue("auth_with_sspi") == "1" {
 | 
			
		||||
			shouldAuth = true
 | 
			
		||||
		}
 | 
			
		||||
	} else if middleware.IsAPIPath(req) || isAttachmentDownload(req) {
 | 
			
		||||
		shouldAuth = true
 | 
			
		||||
	}
 | 
			
		||||
	return shouldAuth
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// newUser creates a new user object for the purpose of automatic registration
 | 
			
		||||
// and populates its name and email with the information present in request headers.
 | 
			
		||||
func (s *SSPI) newUser(ctx context.Context, username string, cfg *sspi.Source) (*user_model.User, error) {
 | 
			
		||||
	email := gouuid.New().String() + "@localhost.localdomain"
 | 
			
		||||
	user := &user_model.User{
 | 
			
		||||
		Name:     username,
 | 
			
		||||
		Email:    email,
 | 
			
		||||
		Language: cfg.DefaultLanguage,
 | 
			
		||||
	}
 | 
			
		||||
	emailNotificationPreference := user_model.EmailNotificationsDisabled
 | 
			
		||||
	overwriteDefault := &user_model.CreateUserOverwriteOptions{
 | 
			
		||||
		IsActive:                     optional.Some(cfg.AutoActivateUsers),
 | 
			
		||||
		KeepEmailPrivate:             optional.Some(true),
 | 
			
		||||
		EmailNotificationsPreference: &emailNotificationPreference,
 | 
			
		||||
	}
 | 
			
		||||
	if err := user_model.CreateUser(ctx, user, overwriteDefault); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return user, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// stripDomainNames removes NETBIOS domain name and separator from down-level logon names
 | 
			
		||||
// (eg. "DOMAIN\user" becomes "user"), and removes the UPN suffix (domain name) and separator
 | 
			
		||||
// from UPNs (eg. "user@domain.local" becomes "user")
 | 
			
		||||
func stripDomainNames(username string) string {
 | 
			
		||||
	if strings.Contains(username, "\\") {
 | 
			
		||||
		parts := strings.SplitN(username, "\\", 2)
 | 
			
		||||
		if len(parts) > 1 {
 | 
			
		||||
			username = parts[1]
 | 
			
		||||
		}
 | 
			
		||||
	} else if strings.Contains(username, "@") {
 | 
			
		||||
		parts := strings.Split(username, "@")
 | 
			
		||||
		if len(parts) > 1 {
 | 
			
		||||
			username = parts[0]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return username
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func replaceSeparators(username string, cfg *sspi.Source) string {
 | 
			
		||||
	newSep := cfg.SeparatorReplacement
 | 
			
		||||
	username = strings.ReplaceAll(username, "\\", newSep)
 | 
			
		||||
	username = strings.ReplaceAll(username, "/", newSep)
 | 
			
		||||
	username = strings.ReplaceAll(username, "@", newSep)
 | 
			
		||||
	return username
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sanitizeUsername(username string, cfg *sspi.Source) string {
 | 
			
		||||
	if len(username) == 0 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	if cfg.StripDomainNames {
 | 
			
		||||
		username = stripDomainNames(username)
 | 
			
		||||
	}
 | 
			
		||||
	// Replace separators even if we have already stripped the domain name part,
 | 
			
		||||
	// as the username can contain several separators: eg. "MICROSOFT\useremail@live.com"
 | 
			
		||||
	username = replaceSeparators(username, cfg)
 | 
			
		||||
	return username
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,30 +0,0 @@
 | 
			
		|||
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
//go:build !windows
 | 
			
		||||
 | 
			
		||||
package auth
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SSPIUserInfo struct {
 | 
			
		||||
	Username string   // Name of user, usually in the form DOMAIN\User
 | 
			
		||||
	Groups   []string // The global groups the user is a member of
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type sspiAuthMock struct{}
 | 
			
		||||
 | 
			
		||||
func (s sspiAuthMock) AppendAuthenticateHeader(w http.ResponseWriter, data string) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s sspiAuthMock) Authenticate(r *http.Request, w http.ResponseWriter) (userInfo *SSPIUserInfo, outToken string, err error) {
 | 
			
		||||
	return nil, "", errors.New("not implemented")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func sspiAuthInit() error {
 | 
			
		||||
	sspiAuth = &sspiAuthMock{} // TODO: we can mock the SSPI auth in tests
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -77,11 +77,6 @@ type AuthenticationForm struct {
 | 
			
		|||
	Oauth2GroupTeamMapRemoval     bool
 | 
			
		||||
	Oauth2AttributeSSHPublicKey   string
 | 
			
		||||
	SkipLocalTwoFA                bool
 | 
			
		||||
	SSPIAutoCreateUsers           bool
 | 
			
		||||
	SSPIAutoActivateUsers         bool
 | 
			
		||||
	SSPIStripDomainNames          bool
 | 
			
		||||
	SSPISeparatorReplacement      string `binding:"AlphaDashDot;MaxSize(5)"`
 | 
			
		||||
	SSPIDefaultLanguage           string
 | 
			
		||||
	GroupTeamMap                  string `binding:"ValidGroupTeamMap"`
 | 
			
		||||
	GroupTeamMapRemoval           bool
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -380,51 +380,6 @@
 | 
			
		|||
					</div>
 | 
			
		||||
				{{end}}
 | 
			
		||||
 | 
			
		||||
				<!-- SSPI -->
 | 
			
		||||
				{{if .Source.IsSSPI}}
 | 
			
		||||
					{{$cfg:=.Source.Cfg}}
 | 
			
		||||
					<div class="field">
 | 
			
		||||
						<div class="ui checkbox">
 | 
			
		||||
							<label for="sspi_auto_create_users"><strong>{{ctx.Locale.Tr "admin.auths.sspi_auto_create_users"}}</strong></label>
 | 
			
		||||
							<input id="sspi_auto_create_users" name="sspi_auto_create_users" class="sspi-auto-create-users" type="checkbox" {{if $cfg.AutoCreateUsers}}checked{{end}}>
 | 
			
		||||
							<p class="help">{{ctx.Locale.Tr "admin.auths.sspi_auto_create_users_helper"}}</p>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="field">
 | 
			
		||||
						<div class="ui checkbox">
 | 
			
		||||
							<label for="sspi_auto_activate_users"><strong>{{ctx.Locale.Tr "admin.auths.sspi_auto_activate_users"}}</strong></label>
 | 
			
		||||
							<input id="sspi_auto_activate_users" name="sspi_auto_activate_users" class="sspi-auto-activate-users" type="checkbox" {{if $cfg.AutoActivateUsers}}checked{{end}}>
 | 
			
		||||
							<p class="help">{{ctx.Locale.Tr "admin.auths.sspi_auto_activate_users_helper"}}</p>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="field">
 | 
			
		||||
						<div class="ui checkbox">
 | 
			
		||||
							<label for="sspi_strip_domain_names"><strong>{{ctx.Locale.Tr "admin.auths.sspi_strip_domain_names"}}</strong></label>
 | 
			
		||||
							<input id="sspi_strip_domain_names" name="sspi_strip_domain_names" class="sspi-strip-domain-names" type="checkbox" {{if $cfg.StripDomainNames}}checked{{end}}>
 | 
			
		||||
							<p class="help">{{ctx.Locale.Tr "admin.auths.sspi_strip_domain_names_helper"}}</p>
 | 
			
		||||
						</div>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="required field">
 | 
			
		||||
						<label for="sspi_separator_replacement">{{ctx.Locale.Tr "admin.auths.sspi_separator_replacement"}}</label>
 | 
			
		||||
						<input id="sspi_separator_replacement" name="sspi_separator_replacement" value="{{$cfg.SeparatorReplacement}}" required>
 | 
			
		||||
						<p class="help">{{ctx.Locale.Tr "admin.auths.sspi_separator_replacement_helper"}}</p>
 | 
			
		||||
					</div>
 | 
			
		||||
					<div class="field">
 | 
			
		||||
						<label for="sspi_default_language">{{ctx.Locale.Tr "admin.auths.sspi_default_language"}}</label>
 | 
			
		||||
						<div class="ui language selection dropdown" id="sspi_default_language">
 | 
			
		||||
							<input name="sspi_default_language" type="hidden" value="{{$cfg.DefaultLanguage}}">
 | 
			
		||||
							{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
			
		||||
							<div class="text">{{range .AllLangs}}{{if eq $cfg.DefaultLanguage .Lang}}{{.Name}}{{end}}{{end}}</div>
 | 
			
		||||
							<div class="menu">
 | 
			
		||||
								<div class="item{{if not $.SSPIDefaultLanguage}} active selected{{end}}" data-value="">-</div>
 | 
			
		||||
							{{range .AllLangs}}
 | 
			
		||||
								<div class="item{{if eq $cfg.DefaultLanguage .Lang}} active selected{{end}}" data-value="{{.Lang}}">{{.Name}}</div>
 | 
			
		||||
							{{end}}
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
						<p class="help">{{ctx.Locale.Tr "admin.auths.sspi_default_language_helper"}}</p>
 | 
			
		||||
					</div>
 | 
			
		||||
				{{end}}
 | 
			
		||||
				{{if .Source.IsLDAP}}
 | 
			
		||||
					<div class="inline field">
 | 
			
		||||
						<div class="ui checkbox">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,9 +50,6 @@
 | 
			
		|||
				<!-- OAuth2 -->
 | 
			
		||||
				{{template "admin/auth/source/oauth" .}}
 | 
			
		||||
 | 
			
		||||
				<!-- SSPI -->
 | 
			
		||||
				{{template "admin/auth/source/sspi" .}}
 | 
			
		||||
 | 
			
		||||
				<div class="ldap field">
 | 
			
		||||
					<div class="ui checkbox">
 | 
			
		||||
						<label><strong>{{ctx.Locale.Tr "admin.auths.attributes_in_bind"}}</strong></label>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,43 +0,0 @@
 | 
			
		|||
<div class="sspi field {{if not (eq .type 7)}}tw-hidden{{end}}">
 | 
			
		||||
	<div class="field">
 | 
			
		||||
		<div class="ui checkbox">
 | 
			
		||||
			<label for="sspi_auto_create_users"><strong>{{ctx.Locale.Tr "admin.auths.sspi_auto_create_users"}}</strong></label>
 | 
			
		||||
			<input id="sspi_auto_create_users" name="sspi_auto_create_users" class="sspi-auto-create-users" type="checkbox" {{if .SSPIAutoCreateUsers}}checked{{end}}>
 | 
			
		||||
			<p class="help">{{ctx.Locale.Tr "admin.auths.sspi_auto_create_users_helper"}}</p>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="field">
 | 
			
		||||
		<div class="ui checkbox">
 | 
			
		||||
			<label for="sspi_auto_activate_users"><strong>{{ctx.Locale.Tr "admin.auths.sspi_auto_activate_users"}}</strong></label>
 | 
			
		||||
			<input id="sspi_auto_activate_users" name="sspi_auto_activate_users" class="sspi-auto-activate-users" type="checkbox" {{if .SSPIAutoActivateUsers}}checked{{end}}>
 | 
			
		||||
			<p class="help">{{ctx.Locale.Tr "admin.auths.sspi_auto_activate_users_helper"}}</p>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="field">
 | 
			
		||||
		<div class="ui checkbox">
 | 
			
		||||
			<label for="sspi_strip_domain_names"><strong>{{ctx.Locale.Tr "admin.auths.sspi_strip_domain_names"}}</strong></label>
 | 
			
		||||
			<input id="sspi_strip_domain_names" name="sspi_strip_domain_names" class="sspi-strip-domain-names" type="checkbox" {{if .SSPIStripDomainNames}}checked{{end}}>
 | 
			
		||||
			<p class="help">{{ctx.Locale.Tr "admin.auths.sspi_strip_domain_names_helper"}}</p>
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="required field">
 | 
			
		||||
		<label for="sspi_separator_replacement">{{ctx.Locale.Tr "admin.auths.sspi_separator_replacement"}}</label>
 | 
			
		||||
		<input id="sspi_separator_replacement" name="sspi_separator_replacement" value="{{.SSPISeparatorReplacement}}">
 | 
			
		||||
		<p class="help">{{ctx.Locale.Tr "admin.auths.sspi_separator_replacement_helper"}}</p>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div class="field">
 | 
			
		||||
		<label for="sspi_default_language">{{ctx.Locale.Tr "admin.auths.sspi_default_language"}}</label>
 | 
			
		||||
		<div class="ui language selection dropdown" id="sspi_default_language">
 | 
			
		||||
			<input name="sspi_default_language" type="hidden" value="{{.SSPIDefaultLanguage}}">
 | 
			
		||||
			{{svg "octicon-triangle-down" 14 "dropdown icon"}}
 | 
			
		||||
			<div class="text">{{range .AllLangs}}{{if eq $.SSPIDefaultLanguage .Lang}}{{.Name}}{{end}}{{end}}</div>
 | 
			
		||||
			<div class="menu">
 | 
			
		||||
				<div class="item{{if not $.SSPIDefaultLanguage}} active selected{{end}}" data-value="">-</div>
 | 
			
		||||
			{{range .AllLangs}}
 | 
			
		||||
				<div class="item{{if eq $.SSPIDefaultLanguage .Lang}} active selected{{end}}" data-value="{{.Lang}}">{{.Name}}</div>
 | 
			
		||||
			{{end}}
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
		<p class="help">{{ctx.Locale.Tr "admin.auths.sspi_default_language_helper"}}</p>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -19,12 +19,6 @@
 | 
			
		|||
				{{ctx.Locale.Tr "auth.sign_in_openid"}}
 | 
			
		||||
				</a>
 | 
			
		||||
			{{end}}
 | 
			
		||||
			{{if .EnableSSPI}}
 | 
			
		||||
				<a class="ui button tw-flex tw-items-center tw-justify-center tw-py-2 tw-w-full" rel="nofollow" href="{{AppSubUrl}}/user/login?auth_with_sspi=1">
 | 
			
		||||
					{{svg "fontawesome-windows"}}
 | 
			
		||||
					 SSPI
 | 
			
		||||
				</a>
 | 
			
		||||
			{{end}}
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,9 +66,6 @@ func InitTest(requireGitea bool) {
 | 
			
		|||
	setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
 | 
			
		||||
	if requireGitea {
 | 
			
		||||
		giteaBinary := "gitea"
 | 
			
		||||
		if setting.IsWindows {
 | 
			
		||||
			giteaBinary += ".exe"
 | 
			
		||||
		}
 | 
			
		||||
		setting.AppPath = path.Join(giteaRoot, giteaBinary)
 | 
			
		||||
		if _, err := os.Stat(setting.AppPath); err != nil {
 | 
			
		||||
			exitf("Could not find gitea binary at %s", setting.AppPath)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -123,9 +123,9 @@ export function initAdminCommon() {
 | 
			
		|||
  // New authentication
 | 
			
		||||
  if (document.querySelector('.admin.new.authentication')) {
 | 
			
		||||
    document.getElementById('auth_type')?.addEventListener('change', function () {
 | 
			
		||||
      hideElem('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi');
 | 
			
		||||
      hideElem('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size');
 | 
			
		||||
 | 
			
		||||
      for (const input of document.querySelectorAll('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required], .sspi input[required]')) {
 | 
			
		||||
      for (const input of document.querySelectorAll('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required]')) {
 | 
			
		||||
        input.removeAttribute('required');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -166,12 +166,6 @@ export function initAdminCommon() {
 | 
			
		|||
          }
 | 
			
		||||
          onOAuth2Change(true);
 | 
			
		||||
          break;
 | 
			
		||||
        case '7': // SSPI
 | 
			
		||||
          showElem('.sspi');
 | 
			
		||||
          for (const input of document.querySelectorAll('.sspi div.required input')) {
 | 
			
		||||
            input.setAttribute('required', 'required');
 | 
			
		||||
          }
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
      if (authType === '2' || authType === '5') {
 | 
			
		||||
        onSecurityProtocolChange();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue