mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-03-12 14:41:40 -05:00
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
23
Makefile
23
Makefile
|
@ -59,20 +59,8 @@ ifeq ($(HAS_GO), yes)
|
||||||
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
|
CGO_CFLAGS ?= $(shell $(GO) env CGO_CFLAGS) $(CGO_EXTRA_CFLAGS)
|
||||||
endif
|
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
|
GOFLAGS := -v
|
||||||
EXECUTABLE ?= gitea
|
EXECUTABLE ?= gitea
|
||||||
endif
|
|
||||||
|
|
||||||
ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu)
|
ifeq ($(shell sed --version 2>/dev/null | grep -q GNU && echo gnu),gnu)
|
||||||
SED_INPLACE := sed -i
|
SED_INPLACE := sed -i
|
||||||
|
@ -498,13 +486,6 @@ lint-go-fix:
|
||||||
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) --fix
|
$(GO) run $(GOLANGCI_LINT_PACKAGE) run $(GOLANGCI_LINT_ARGS) --fix
|
||||||
$(RUN_DEADCODE) > .deadcode-out
|
$(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
|
.PHONY: lint-go-vet
|
||||||
lint-go-vet:
|
lint-go-vet:
|
||||||
@echo "Running go vet..."
|
@echo "Running go vet..."
|
||||||
|
@ -877,10 +858,6 @@ sources-tarbal: frontend generate vendor release-sources release-check
|
||||||
$(DIST_DIRS):
|
$(DIST_DIRS):
|
||||||
mkdir -p $(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
|
.PHONY: release-linux
|
||||||
release-linux: | $(DIST_DIRS)
|
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) .
|
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,8 +87,6 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
// Note: chmod command does not support in Windows.
|
|
||||||
if !setting.IsWindows {
|
|
||||||
fi, err := f.Stat()
|
fi, err := f.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -101,7 +99,6 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
if key.Type == KeyTypePrincipal {
|
if key.Type == KeyTypePrincipal {
|
||||||
|
|
|
@ -32,7 +32,7 @@ const (
|
||||||
PAM // 4
|
PAM // 4
|
||||||
DLDAP // 5
|
DLDAP // 5
|
||||||
OAuth2 // 6
|
OAuth2 // 6
|
||||||
SSPI // 7
|
_ // 7 (was SSPI)
|
||||||
Remote // 8
|
Remote // 8
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -53,7 +53,6 @@ var Names = map[Type]string{
|
||||||
SMTP: "SMTP",
|
SMTP: "SMTP",
|
||||||
PAM: "PAM",
|
PAM: "PAM",
|
||||||
OAuth2: "OAuth2",
|
OAuth2: "OAuth2",
|
||||||
SSPI: "SPNEGO with SSPI",
|
|
||||||
Remote: "Remote",
|
Remote: "Remote",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,11 +177,6 @@ func (source *Source) IsOAuth2() bool {
|
||||||
return source.Type == OAuth2
|
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 {
|
func (source *Source) IsRemote() bool {
|
||||||
return source.Type == Remote
|
return source.Type == Remote
|
||||||
}
|
}
|
||||||
|
@ -265,20 +259,6 @@ func (opts FindSourcesOptions) ToConds() builder.Cond {
|
||||||
return conds
|
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.
|
// GetSourceByID returns login source by given ID.
|
||||||
func GetSourceByID(ctx context.Context, id int64) (*Source, error) {
|
func GetSourceByID(ctx context.Context, id int64) (*Source, error) {
|
||||||
source := new(Source)
|
source := new(Source)
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -123,9 +122,6 @@ func MainTest(m *testing.M) {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
giteaBinary := "gitea"
|
giteaBinary := "gitea"
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
giteaBinary += ".exe"
|
|
||||||
}
|
|
||||||
setting.AppPath = path.Join(giteaRoot, giteaBinary)
|
setting.AppPath = path.Join(giteaRoot, giteaBinary)
|
||||||
if _, err := os.Stat(setting.AppPath); err != nil {
|
if _, err := os.Stat(setting.AppPath); err != nil {
|
||||||
fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath)
|
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")
|
cmd := NewCommandContextNoGlobals(ctx, "blame", "--porcelain")
|
||||||
if ignoreRevsFile != nil {
|
if ignoreRevsFile != nil {
|
||||||
// Possible improvement: use --ignore-revs-file /dev/stdin on unix
|
// 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.AddOptionValues("--ignore-revs-file", *ignoreRevsFile)
|
||||||
}
|
}
|
||||||
cmd.AddDynamicArguments(commit.ID.String()).
|
cmd.AddDynamicArguments(commit.ID.String()).
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
|
||||||
"runtime/trace"
|
"runtime/trace"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -359,17 +358,6 @@ func (c *Command) Run(opts *RunOpts) error {
|
||||||
log.Debug("slow git.Command.Run: %s (%s)", c, elapsed)
|
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 {
|
if err != nil && ctx.Err() != context.DeadlineExceeded {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,15 +59,7 @@ func loadGitVersion() error {
|
||||||
return fmt.Errorf("invalid git version output: %s", stdout)
|
return fmt.Errorf("invalid git version output: %s", stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
var versionString string
|
versionString := fields[2]
|
||||||
|
|
||||||
// Handle special case on Windows.
|
|
||||||
i := strings.Index(fields[2], "windows")
|
|
||||||
if i >= 1 {
|
|
||||||
versionString = fields[2][:i-1]
|
|
||||||
} else {
|
|
||||||
versionString = fields[2]
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
gitVersion, err = version.NewVersion(versionString)
|
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.
|
// 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,
|
// 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.
|
// 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
|
// 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
|
// but is tolerated by earlier versions
|
||||||
if err := configAddNonExist("safe.directory", "*"); err != nil {
|
if err := configAddNonExist("safe.directory", "*"); err != nil {
|
||||||
return err
|
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
|
// By default partial clones are disabled, enable them from git v2.22
|
||||||
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
|
if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//go:build !windows
|
|
||||||
|
|
||||||
package graceful
|
package graceful
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
|
|
||||||
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
|
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
|
||||||
|
|
||||||
//go:build !windows
|
|
||||||
|
|
||||||
package graceful
|
package graceful
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
|
|
||||||
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
|
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
|
||||||
|
|
||||||
//go:build !windows
|
|
||||||
|
|
||||||
package graceful
|
package graceful
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//go:build !windows
|
|
||||||
|
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
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"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
|
@ -70,9 +69,6 @@ func (p *Renderer) DisplayInIFrame() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func envMark(envName string) string {
|
func envMark(envName string) string {
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return "%" + envName + "%"
|
|
||||||
}
|
|
||||||
return "$" + envName
|
return "$" + envName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//go:build !windows
|
|
||||||
|
|
||||||
package process
|
package process
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
@ -146,10 +145,6 @@ func CreateDelegateHooks(repoPath string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkExecutable(filename string) bool {
|
func checkExecutable(filename string) bool {
|
||||||
// windows has no concept of a executable bit
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
fileInfo, err := os.Stat(filename)
|
fileInfo, err := os.Stat(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -34,11 +34,7 @@ var (
|
||||||
func getAppPath() (string, error) {
|
func getAppPath() (string, error) {
|
||||||
var appPath string
|
var appPath string
|
||||||
var err error
|
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 err != nil {
|
||||||
if !errors.Is(err, exec.ErrDot) {
|
if !errors.Is(err, exec.ErrDot) {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
@ -8,7 +8,6 @@ package setting
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -34,7 +33,6 @@ var (
|
||||||
RunMode string
|
RunMode string
|
||||||
RunUser string
|
RunUser string
|
||||||
IsProd bool
|
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
|
// 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
|
// TODO: this is only a temporary solution, we should make the test code more reliable
|
||||||
|
@ -42,22 +40,18 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
IsWindows = runtime.GOOS == "windows"
|
|
||||||
if AppVer == "" {
|
if AppVer == "" {
|
||||||
AppVer = "dev"
|
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.
|
// 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)
|
log.SetConsoleLogger(log.DEFAULT, "console", log.INFO)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRunUserMatchCurrentUser returns false if configured run user does not match
|
// 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.
|
// 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) {
|
func IsRunUserMatchCurrentUser(runUser string) (string, bool) {
|
||||||
if IsWindows || SSH.StartBuiltinServer {
|
if SSH.StartBuiltinServer {
|
||||||
return "", true
|
return "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,6 @@ package user
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CurrentUsername return current login OS user name
|
// CurrentUsername return current login OS user name
|
||||||
|
@ -16,12 +14,7 @@ func CurrentUsername() string {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fallbackCurrentUsername()
|
return fallbackCurrentUsername()
|
||||||
}
|
}
|
||||||
username := userinfo.Username
|
return userinfo.Username
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
parts := strings.Split(username, "\\")
|
|
||||||
username = parts[len(parts)-1]
|
|
||||||
}
|
|
||||||
return username
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Old method, used if new method doesn't work on your OS for some reason
|
// Old method, used if new method doesn't work on your OS for some reason
|
||||||
|
|
|
@ -5,7 +5,6 @@ package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -23,10 +22,6 @@ func TestCurrentUsername(t *testing.T) {
|
||||||
if len(user) == 0 {
|
if len(user) == 0 {
|
||||||
t.Errorf("expected non-empty user, got: %s", user)
|
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()
|
whoami, err := getWhoamiOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to run whoami to test current user: %f", err)
|
t.Errorf("failed to run whoami to test current user: %f", err)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//go:build !windows
|
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//go:build !windows
|
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -10,8 +10,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
"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
|
// 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 `/`
|
// 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]) {
|
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
|
// 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))
|
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,12 +85,8 @@ func FilePathJoinAbs(base string, sub ...string) string {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
continue
|
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
|
// the elems[0] must be an absolute path, just join them together
|
||||||
return filepath.Join(elems...)
|
return filepath.Join(elems...)
|
||||||
}
|
}
|
||||||
|
@ -217,12 +207,6 @@ func StatDir(rootPath string, includeDir ...bool) ([]string, error) {
|
||||||
return statDir(rootPath, "", isIncludeDir, false, false)
|
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.
|
// FileURLToPath extracts the path information from a file://... url.
|
||||||
// It returns an error only if the URL is not a file URL.
|
// It returns an error only if the URL is not a file URL.
|
||||||
func FileURLToPath(u *url.URL) (string, error) {
|
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())
|
return "", errors.New("URL scheme is not 'file': " + u.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
path := u.Path
|
return u.Path, nil
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HomeDir returns path of '~'(in Linux) on Windows,
|
// 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: 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
|
// 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`
|
// 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 == "" {
|
if home == "" {
|
||||||
return "", errors.New("cannot get home directory")
|
return "", errors.New("cannot get home directory")
|
||||||
|
|
|
@ -5,7 +5,6 @@ package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"runtime"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -17,7 +16,6 @@ func TestFileURLToPath(t *testing.T) {
|
||||||
url string
|
url string
|
||||||
expected string
|
expected string
|
||||||
haserror bool
|
haserror bool
|
||||||
windows bool
|
|
||||||
}{
|
}{
|
||||||
// case 0
|
// case 0
|
||||||
{
|
{
|
||||||
|
@ -34,18 +32,9 @@ func TestFileURLToPath(t *testing.T) {
|
||||||
url: "file:///path",
|
url: "file:///path",
|
||||||
expected: "/path",
|
expected: "/path",
|
||||||
},
|
},
|
||||||
// case 3
|
|
||||||
{
|
|
||||||
url: "file:///C:/path",
|
|
||||||
expected: "C:/path",
|
|
||||||
windows: true,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for n, c := range cases {
|
for n, c := range cases {
|
||||||
if c.windows && runtime.GOOS != "windows" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
u, _ := url.Parse(c.url)
|
u, _ := url.Parse(c.url)
|
||||||
p, err := FileURLToPath(u)
|
p, err := FileURLToPath(u)
|
||||||
if c.haserror {
|
if c.haserror {
|
||||||
|
@ -177,22 +166,6 @@ func TestCleanPath(t *testing.T) {
|
||||||
assert.Equal(t, c.expected, PathJoinRelX(c.elems...), "case: %v", c.elems)
|
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 {
|
cases = []struct {
|
||||||
elems []string
|
elems []string
|
||||||
expected string
|
expected string
|
||||||
|
@ -206,7 +179,6 @@ func TestCleanPath(t *testing.T) {
|
||||||
{[]string{`/a`, `..`, `b`}, `/a/b`},
|
{[]string{`/a`, `..`, `b`}, `/a/b`},
|
||||||
{[]string{`/lfs`, `repo/..`, `user/../path`}, `/lfs/path`},
|
{[]string{`/lfs`, `repo/..`, `user/../path`}, `/lfs/path`},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
assert.Equal(t, c.expected, FilePathJoinAbs(c.elems[0], c.elems[1:]...), "case: %v", c.elems)
|
assert.Equal(t, c.expected, FilePathJoinAbs(c.elems[0], c.elems[1:]...), "case: %v", c.elems)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,10 @@ package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const windowsSharingViolationError syscall.Errno = 32
|
|
||||||
|
|
||||||
// Remove removes the named file or (empty) directory with at most 5 attempts.
|
// Remove removes the named file or (empty) directory with at most 5 attempts.
|
||||||
func Remove(name string) error {
|
func Remove(name string) error {
|
||||||
var err error
|
var err error
|
||||||
|
@ -27,12 +24,6 @@ func Remove(name string) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" {
|
|
||||||
// try again
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if unwrapped == syscall.ENOENT {
|
if unwrapped == syscall.ENOENT {
|
||||||
// it's already gone
|
// it's already gone
|
||||||
return nil
|
return nil
|
||||||
|
@ -56,12 +47,6 @@ func RemoveAll(name string) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" {
|
|
||||||
// try again
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if unwrapped == syscall.ENOENT {
|
if unwrapped == syscall.ENOENT {
|
||||||
// it's already gone
|
// it's already gone
|
||||||
return nil
|
return nil
|
||||||
|
@ -85,12 +70,6 @@ func Rename(oldpath, newpath string) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if unwrapped == windowsSharingViolationError && runtime.GOOS == "windows" {
|
|
||||||
// try again
|
|
||||||
<-time.After(100 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == 0 && os.IsNotExist(err) {
|
if i == 0 && os.IsNotExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -609,9 +609,6 @@ CommitChoice = Commit choice
|
||||||
TreeName = File path
|
TreeName = File path
|
||||||
Content = Content
|
Content = Content
|
||||||
|
|
||||||
SSPISeparatorReplacement = Separator
|
|
||||||
SSPIDefaultLanguage = Default language
|
|
||||||
|
|
||||||
require_error = ` cannot be empty.`
|
require_error = ` cannot be empty.`
|
||||||
alpha_dash_error = ` should contain only alphanumeric, dash ("-") and underscore ("_") characters.`
|
alpha_dash_error = ` should contain only alphanumeric, dash ("-") and underscore ("_") characters.`
|
||||||
alpha_dash_dot_error = ` should contain only alphanumeric, dash ("-"), underscore ("_") and dot (".") 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_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 = 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.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 = Tips
|
||||||
auths.tips.gmail_settings = Gmail settings:
|
auths.tips.gmail_settings = Gmail settings:
|
||||||
auths.tips.oauth2.general = OAuth2 authentication
|
auths.tips.oauth2.general = OAuth2 authentication
|
||||||
|
|
|
@ -6,8 +6,6 @@ package shared
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"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/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/routers/common"
|
"code.gitea.io/gitea/routers/common"
|
||||||
|
@ -51,10 +49,6 @@ func buildAuthGroup() *auth.Group {
|
||||||
group.Add(&auth.ReverseProxy{})
|
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
|
return group
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
"code.gitea.io/gitea/modules/user"
|
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
"code.gitea.io/gitea/routers/common"
|
"code.gitea.io/gitea/routers/common"
|
||||||
|
@ -119,15 +118,7 @@ func Install(ctx *context.Context) {
|
||||||
form.AppSlogan = "Beyond coding. We Forge."
|
form.AppSlogan = "Beyond coding. We Forge."
|
||||||
form.RepoRootPath = setting.RepoRootPath
|
form.RepoRootPath = setting.RepoRootPath
|
||||||
form.LFSRootPath = setting.LFS.Storage.Path
|
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.Domain = setting.Domain
|
||||||
form.SSHPort = setting.SSH.Port
|
form.SSHPort = setting.SSH.Port
|
||||||
form.HTTPPort = setting.HTTPPort
|
form.HTTPPort = setting.HTTPPort
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
//go:build !windows
|
|
||||||
|
|
||||||
package private
|
package private
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -4,11 +4,9 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -18,14 +16,12 @@ import (
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
auth_service "code.gitea.io/gitea/services/auth"
|
auth_service "code.gitea.io/gitea/services/auth"
|
||||||
"code.gitea.io/gitea/services/auth/source/ldap"
|
"code.gitea.io/gitea/services/auth/source/ldap"
|
||||||
"code.gitea.io/gitea/services/auth/source/oauth2"
|
"code.gitea.io/gitea/services/auth/source/oauth2"
|
||||||
pam_service "code.gitea.io/gitea/services/auth/source/pam"
|
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/smtp"
|
||||||
"code.gitea.io/gitea/services/auth/source/sspi"
|
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
|
|
||||||
|
@ -38,11 +34,6 @@ const (
|
||||||
tplAuthEdit base.TplName = "admin/auth/edit"
|
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
|
// Authentications show authentication config page
|
||||||
func Authentications(ctx *context.Context) {
|
func Authentications(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("admin.authentication")
|
ctx.Data["Title"] = ctx.Tr("admin.authentication")
|
||||||
|
@ -70,7 +61,6 @@ var (
|
||||||
{auth.DLDAP.String(), auth.DLDAP},
|
{auth.DLDAP.String(), auth.DLDAP},
|
||||||
{auth.SMTP.String(), auth.SMTP},
|
{auth.SMTP.String(), auth.SMTP},
|
||||||
{auth.OAuth2.String(), auth.OAuth2},
|
{auth.OAuth2.String(), auth.OAuth2},
|
||||||
{auth.SSPI.String(), auth.SSPI},
|
|
||||||
}
|
}
|
||||||
if pam.Supported {
|
if pam.Supported {
|
||||||
items = append(items, dropdownItem{auth.Names[auth.PAM], auth.PAM})
|
items = append(items, dropdownItem{auth.Names[auth.PAM], auth.PAM})
|
||||||
|
@ -102,12 +92,6 @@ func NewAuthSource(ctx *context.Context) {
|
||||||
oauth2providers := oauth2.GetSupportedOAuth2Providers()
|
oauth2providers := oauth2.GetSupportedOAuth2Providers()
|
||||||
ctx.Data["OAuth2Providers"] = oauth2providers
|
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
|
// only the first as default
|
||||||
ctx.Data["oauth2_provider"] = oauth2providers[0].Name()
|
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
|
// NewAuthSourcePost response for adding an auth source
|
||||||
func NewAuthSourcePost(ctx *context.Context) {
|
func NewAuthSourcePost(ctx *context.Context) {
|
||||||
form := *web.GetForm(ctx).(*forms.AuthenticationForm)
|
form := *web.GetForm(ctx).(*forms.AuthenticationForm)
|
||||||
|
@ -247,12 +207,6 @@ func NewAuthSourcePost(ctx *context.Context) {
|
||||||
oauth2providers := oauth2.GetSupportedOAuth2Providers()
|
oauth2providers := oauth2.GetSupportedOAuth2Providers()
|
||||||
ctx.Data["OAuth2Providers"] = oauth2providers
|
ctx.Data["OAuth2Providers"] = oauth2providers
|
||||||
|
|
||||||
ctx.Data["SSPIAutoCreateUsers"] = true
|
|
||||||
ctx.Data["SSPIAutoActivateUsers"] = true
|
|
||||||
ctx.Data["SSPIStripDomainNames"] = true
|
|
||||||
ctx.Data["SSPISeparatorReplacement"] = "_"
|
|
||||||
ctx.Data["SSPIDefaultLanguage"] = ""
|
|
||||||
|
|
||||||
hasTLS := false
|
hasTLS := false
|
||||||
var config convert.Conversion
|
var config convert.Conversion
|
||||||
switch auth.Type(form.Type) {
|
switch auth.Type(form.Type) {
|
||||||
|
@ -279,19 +233,6 @@ func NewAuthSourcePost(ctx *context.Context) {
|
||||||
return
|
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:
|
default:
|
||||||
ctx.Error(http.StatusBadRequest)
|
ctx.Error(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
|
@ -408,12 +349,6 @@ func EditAuthSourcePost(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case auth.SSPI:
|
|
||||||
config, err = parseSSPIConfig(ctx, form)
|
|
||||||
if err != nil {
|
|
||||||
ctx.RenderWithErr(err.Error(), tplAuthEdit, form)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
ctx.Error(http.StatusBadRequest)
|
ctx.Error(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
|
|
|
@ -164,7 +164,6 @@ func SignIn(ctx *context.Context) {
|
||||||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
|
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
|
||||||
ctx.Data["PageIsSignIn"] = true
|
ctx.Data["PageIsSignIn"] = true
|
||||||
ctx.Data["PageIsLogin"] = true
|
ctx.Data["PageIsLogin"] = true
|
||||||
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
|
|
||||||
ctx.Data["EnableInternalSignIn"] = setting.Service.EnableInternalSignIn
|
ctx.Data["EnableInternalSignIn"] = setting.Service.EnableInternalSignIn
|
||||||
|
|
||||||
if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin {
|
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["SignInLink"] = setting.AppSubURL + "/user/login"
|
||||||
ctx.Data["PageIsSignIn"] = true
|
ctx.Data["PageIsSignIn"] = true
|
||||||
ctx.Data["PageIsLogin"] = true
|
ctx.Data["PageIsLogin"] = true
|
||||||
ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx)
|
|
||||||
ctx.Data["EnableInternalSignIn"] = setting.Service.EnableInternalSignIn
|
ctx.Data["EnableInternalSignIn"] = setting.Service.EnableInternalSignIn
|
||||||
ctx.Data["DisablePassword"] = !setting.Service.EnableInternalSignIn
|
ctx.Data["DisablePassword"] = !setting.Service.EnableInternalSignIn
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
quota_model "code.gitea.io/gitea/models/quota"
|
quota_model "code.gitea.io/gitea/models/quota"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
|
@ -110,10 +108,6 @@ func buildAuthGroup() *auth_service.Group {
|
||||||
}
|
}
|
||||||
group.Add(&auth_service.Session{})
|
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
|
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/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/ldap" // register the ldap source
|
||||||
_ "code.gitea.io/gitea/services/auth/source/pam" // register the pam 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.
|
// 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
|
Oauth2GroupTeamMapRemoval bool
|
||||||
Oauth2AttributeSSHPublicKey string
|
Oauth2AttributeSSHPublicKey string
|
||||||
SkipLocalTwoFA bool
|
SkipLocalTwoFA bool
|
||||||
SSPIAutoCreateUsers bool
|
|
||||||
SSPIAutoActivateUsers bool
|
|
||||||
SSPIStripDomainNames bool
|
|
||||||
SSPISeparatorReplacement string `binding:"AlphaDashDot;MaxSize(5)"`
|
|
||||||
SSPIDefaultLanguage string
|
|
||||||
GroupTeamMap string `binding:"ValidGroupTeamMap"`
|
GroupTeamMap string `binding:"ValidGroupTeamMap"`
|
||||||
GroupTeamMapRemoval bool
|
GroupTeamMapRemoval bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -380,51 +380,6 @@
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{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}}
|
{{if .Source.IsLDAP}}
|
||||||
<div class="inline field">
|
<div class="inline field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
|
|
|
@ -50,9 +50,6 @@
|
||||||
<!-- OAuth2 -->
|
<!-- OAuth2 -->
|
||||||
{{template "admin/auth/source/oauth" .}}
|
{{template "admin/auth/source/oauth" .}}
|
||||||
|
|
||||||
<!-- SSPI -->
|
|
||||||
{{template "admin/auth/source/sspi" .}}
|
|
||||||
|
|
||||||
<div class="ldap field">
|
<div class="ldap field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<label><strong>{{ctx.Locale.Tr "admin.auths.attributes_in_bind"}}</strong></label>
|
<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"}}
|
{{ctx.Locale.Tr "auth.sign_in_openid"}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -66,9 +66,6 @@ func InitTest(requireGitea bool) {
|
||||||
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
|
setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom")
|
||||||
if requireGitea {
|
if requireGitea {
|
||||||
giteaBinary := "gitea"
|
giteaBinary := "gitea"
|
||||||
if setting.IsWindows {
|
|
||||||
giteaBinary += ".exe"
|
|
||||||
}
|
|
||||||
setting.AppPath = path.Join(giteaRoot, giteaBinary)
|
setting.AppPath = path.Join(giteaRoot, giteaBinary)
|
||||||
if _, err := os.Stat(setting.AppPath); err != nil {
|
if _, err := os.Stat(setting.AppPath); err != nil {
|
||||||
exitf("Could not find gitea binary at %s", setting.AppPath)
|
exitf("Could not find gitea binary at %s", setting.AppPath)
|
||||||
|
|
|
@ -123,9 +123,9 @@ export function initAdminCommon() {
|
||||||
// New authentication
|
// New authentication
|
||||||
if (document.querySelector('.admin.new.authentication')) {
|
if (document.querySelector('.admin.new.authentication')) {
|
||||||
document.getElementById('auth_type')?.addEventListener('change', function () {
|
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');
|
input.removeAttribute('required');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,12 +166,6 @@ export function initAdminCommon() {
|
||||||
}
|
}
|
||||||
onOAuth2Change(true);
|
onOAuth2Change(true);
|
||||||
break;
|
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') {
|
if (authType === '2' || authType === '5') {
|
||||||
onSecurityProtocolChange();
|
onSecurityProtocolChange();
|
||||||
|
|
Loading…
Add table
Reference in a new issue