mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-03-13 23:32:41 +00:00
Merge branch 'forgejo' into report_abuse
This commit is contained in:
commit
51580beaee
94 changed files with 1169 additions and 3365 deletions
|
@ -189,6 +189,7 @@ code.gitea.io/gitea/modules/translation
|
|||
MockLocale.TrN
|
||||
MockLocale.TrPluralString
|
||||
MockLocale.TrSize
|
||||
MockLocale.HasKey
|
||||
MockLocale.PrettyNumber
|
||||
|
||||
code.gitea.io/gitea/modules/util
|
||||
|
|
|
@ -45,7 +45,7 @@ jobs:
|
|||
|
||||
- uses: https://data.forgejo.org/actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 22
|
||||
|
||||
- uses: https://data.forgejo.org/actions/setup-go@v5
|
||||
with:
|
||||
|
|
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) .
|
||||
|
|
|
@ -723,6 +723,8 @@ export default tseslint.config(
|
|||
'unicode-bom': [2, 'never'],
|
||||
'unicorn/better-regex': [0],
|
||||
'unicorn/catch-error-name': [0],
|
||||
'unicorn/consistent-assert': [0],
|
||||
'unicorn/consistent-date-clone': [2],
|
||||
'unicorn/consistent-destructuring': [2],
|
||||
'unicorn/consistent-empty-array-spread': [2],
|
||||
'unicorn/consistent-existence-index-check': [2],
|
||||
|
@ -737,6 +739,7 @@ export default tseslint.config(
|
|||
'unicorn/import-index': [0],
|
||||
'unicorn/import-style': [0],
|
||||
'unicorn/new-for-builtins': [2],
|
||||
'unicorn/no-accessor-recursion': [2],
|
||||
'unicorn/no-abusive-eslint-disable': [0],
|
||||
'unicorn/no-anonymous-default-export': [0],
|
||||
'unicorn/no-array-callback-reference': [0],
|
||||
|
@ -751,13 +754,14 @@ export default tseslint.config(
|
|||
'unicorn/no-empty-file': [2],
|
||||
'unicorn/no-for-loop': [0],
|
||||
'unicorn/no-hex-escape': [0],
|
||||
'unicorn/no-instanceof-array': [0],
|
||||
'unicorn/no-instanceof-builtins': [0],
|
||||
'unicorn/no-invalid-fetch-options': [2],
|
||||
'unicorn/no-invalid-remove-event-listener': [2],
|
||||
'unicorn/no-keyword-prefix': [0],
|
||||
'unicorn/no-length-as-slice-end': [2],
|
||||
'unicorn/no-lonely-if': [2],
|
||||
'unicorn/no-magic-array-flat-depth': [0],
|
||||
'unicorn/no-named-default': [2],
|
||||
'unicorn/no-negated-condition': [0],
|
||||
'unicorn/no-negation-in-equality-check': [2],
|
||||
'unicorn/no-nested-ternary': [0],
|
||||
|
@ -834,7 +838,6 @@ export default tseslint.config(
|
|||
'unicorn/prefer-structured-clone': [2],
|
||||
'unicorn/prefer-switch': [0],
|
||||
'unicorn/prefer-ternary': [0],
|
||||
'unicorn/prefer-text-content': [2],
|
||||
'unicorn/prefer-top-level-await': [0],
|
||||
'unicorn/prefer-type-error': [0],
|
||||
'unicorn/prevent-abbreviations': [0],
|
||||
|
|
34
go.mod
34
go.mod
|
@ -77,7 +77,7 @@ require (
|
|||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/minio/minio-go/v7 v7.0.85
|
||||
github.com/msteinert/pam v1.2.0
|
||||
github.com/msteinert/pam/v2 v2.0.0
|
||||
github.com/nektos/act v0.2.52
|
||||
github.com/niklasfasching/go-org v1.7.0
|
||||
github.com/olivere/elastic/v7 v7.0.32
|
||||
|
@ -108,8 +108,8 @@ require (
|
|||
golang.org/x/sync v0.12.0
|
||||
golang.org/x/sys v0.31.0
|
||||
golang.org/x/text v0.23.0
|
||||
google.golang.org/grpc v1.70.0
|
||||
google.golang.org/protobuf v1.36.3
|
||||
google.golang.org/grpc v1.71.0
|
||||
google.golang.org/protobuf v1.36.4
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
|
@ -119,11 +119,11 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.19.0 // indirect
|
||||
cel.dev/expr v0.19.1 // indirect
|
||||
cloud.google.com/go v0.116.0 // indirect
|
||||
cloud.google.com/go/auth v0.9.9 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.5.2 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
cloud.google.com/go/iam v1.2.1 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.1 // indirect
|
||||
cloud.google.com/go/monitoring v1.21.1 // indirect
|
||||
|
@ -162,11 +162,10 @@ require (
|
|||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.3.8 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
|
@ -175,8 +174,8 @@ require (
|
|||
github.com/dlclark/regexp2 v1.11.4 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane v0.13.1 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
|
@ -256,14 +255,15 @@ require (
|
|||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.3.9 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.32.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
|
||||
go.opentelemetry.io/otel v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.34.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
|
@ -273,8 +273,8 @@ require (
|
|||
golang.org/x/tools v0.31.0 // indirect
|
||||
google.golang.org/api v0.203.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
|
75
go.sum
75
go.sum
|
@ -1,5 +1,5 @@
|
|||
cel.dev/expr v0.19.0 h1:lXuo+nDhpyJSpWxpPVi5cPUwzKb+dsdOiw6IreM5yt0=
|
||||
cel.dev/expr v0.19.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
|
||||
cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4=
|
||||
cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
|
@ -184,8 +184,8 @@ cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZ
|
|||
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
|
||||
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
|
||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY=
|
||||
cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck=
|
||||
cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w=
|
||||
|
@ -775,7 +775,6 @@ github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+Y
|
|||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
|
||||
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
|
||||
|
@ -811,8 +810,8 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH
|
|||
github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI=
|
||||
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q=
|
||||
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
|
@ -870,14 +869,18 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.
|
|||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34=
|
||||
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q=
|
||||
github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE=
|
||||
github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
|
||||
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
|
||||
|
@ -1276,8 +1279,8 @@ github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUll
|
|||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
|
||||
github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM=
|
||||
github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw=
|
||||
github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE=
|
||||
github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0=
|
||||
github.com/msteinert/pam/v2 v2.0.0 h1:jnObb8MT6jvMbmrUQO5J/puTUjxy7Av+55zVJRJsCyE=
|
||||
github.com/msteinert/pam/v2 v2.0.0/go.mod h1:KT28NNIcDFf3PcBmNI2mIGO4zZJ+9RSs/At2PB3IDVc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek=
|
||||
|
@ -1358,8 +1361,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
|||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
|
@ -1460,22 +1463,24 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
|||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.32.0 h1:P78qWqkLSShicHmAzfECaTgvslqHxblNE9j62Ws1NK8=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
|
||||
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
|
||||
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
||||
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
|
||||
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
|
||||
|
@ -2116,10 +2121,10 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl
|
|||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE=
|
||||
google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53/go.mod h1:fheguH3Am2dGp1LfXkrvwqC/KlFq8F0nLq3LryOMrrE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a h1:hgh8P4EuoxpsuKMXX/To36nOFD7vixReXgn8lPGnt+o=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241202173237-19429a94021a/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
|
@ -2161,8 +2166,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v
|
|||
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
||||
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
||||
google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
@ -2181,8 +2186,8 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
|
|||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
||||
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -98,6 +98,15 @@ func init() {
|
|||
|
||||
// NewAccessToken creates new access token.
|
||||
func NewAccessToken(ctx context.Context, t *AccessToken) error {
|
||||
err := generateAccessToken(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = db.GetEngine(ctx).Insert(t)
|
||||
return err
|
||||
}
|
||||
|
||||
func generateAccessToken(t *AccessToken) error {
|
||||
salt, err := util.CryptoRandomString(10)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -110,8 +119,7 @@ func NewAccessToken(ctx context.Context, t *AccessToken) error {
|
|||
t.Token = hex.EncodeToString(token)
|
||||
t.TokenHash = HashToken(t.Token, t.TokenSalt)
|
||||
t.TokenLastEight = t.Token[len(t.Token)-8:]
|
||||
_, err = db.GetEngine(ctx).Insert(t)
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisplayPublicOnly whether to display this as a public-only token.
|
||||
|
@ -234,3 +242,25 @@ func DeleteAccessTokenByID(ctx context.Context, id, userID int64) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegenerateAccessTokenByID regenerates access token by given ID.
|
||||
// It regenerates token and salt, as well as updates the creation time.
|
||||
func RegenerateAccessTokenByID(ctx context.Context, id, userID int64) (*AccessToken, error) {
|
||||
t := &AccessToken{}
|
||||
found, err := db.GetEngine(ctx).Where("id = ? AND uid = ?", id, userID).Get(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !found {
|
||||
return nil, ErrAccessTokenNotExist{}
|
||||
}
|
||||
|
||||
err = generateAccessToken(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Reset the creation time, token is unused
|
||||
t.UpdatedUnix = timeutil.TimeStampNow()
|
||||
|
||||
return t, UpdateAccessToken(ctx, t)
|
||||
}
|
||||
|
|
|
@ -131,3 +131,28 @@ func TestDeleteAccessTokenByID(t *testing.T) {
|
|||
require.Error(t, err)
|
||||
assert.True(t, auth_model.IsErrAccessTokenNotExist(err))
|
||||
}
|
||||
|
||||
func TestRegenerateAccessTokenByID(t *testing.T) {
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
token, err := auth_model.GetAccessTokenBySHA(db.DefaultContext, "4c6f36e6cf498e2a448662f915d932c09c5a146c")
|
||||
require.NoError(t, err)
|
||||
|
||||
newToken, err := auth_model.RegenerateAccessTokenByID(db.DefaultContext, token.ID, 1)
|
||||
require.NoError(t, err)
|
||||
unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: token.ID, UID: token.UID, TokenHash: token.TokenHash})
|
||||
newToken = &auth_model.AccessToken{
|
||||
ID: newToken.ID,
|
||||
UID: newToken.UID,
|
||||
TokenHash: newToken.TokenHash,
|
||||
}
|
||||
unittest.AssertExistsAndLoadBean(t, newToken)
|
||||
|
||||
// Token has been recreated, new salt and hash, but should retain the same ID, UID, Name and Scope
|
||||
assert.Equal(t, token.ID, newToken.ID)
|
||||
assert.NotEqual(t, token.TokenHash, newToken.TokenHash)
|
||||
assert.NotEqual(t, token.TokenSalt, newToken.TokenSalt)
|
||||
assert.Equal(t, token.UID, newToken.UID)
|
||||
assert.Equal(t, token.Name, newToken.Name)
|
||||
assert.Equal(t, token.Scope, newToken.Scope)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -8,7 +8,7 @@ package pam
|
|||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/msteinert/pam"
|
||||
"github.com/msteinert/pam/v2"
|
||||
)
|
||||
|
||||
// Supported is true when built with PAM
|
||||
|
@ -28,6 +28,7 @@ func Auth(serviceName, userName, passwd string) (string, error) {
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer t.End()
|
||||
|
||||
if err = t.Authenticate(0); err != nil {
|
||||
return "", err
|
||||
|
|
|
@ -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,14 +3,13 @@
|
|||
|
||||
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
|
||||
|
||||
//go:build !windows
|
||||
|
||||
package graceful
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -237,9 +236,11 @@ func GetListenerUnix(network string, address *net.UnixAddr) (*net.UnixListener,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
fileMode := os.FileMode(setting.UnixSocketPermission)
|
||||
if err = os.Chmod(address.Name, fileMode); err != nil {
|
||||
return nil, fmt.Errorf("Failed to set permission of unix socket to %s: %w", fileMode.String(), err)
|
||||
if filepath.IsAbs(address.Name) {
|
||||
fileMode := os.FileMode(setting.UnixSocketPermission)
|
||||
if err = os.Chmod(address.Name, fileMode); err != nil {
|
||||
return nil, fmt.Errorf("Failed to set permission of unix socket to %s: %w", fileMode.String(), err)
|
||||
}
|
||||
}
|
||||
|
||||
activeListeners = append(activeListeners, l)
|
||||
|
|
15
modules/graceful/net_unix_linux_test.go
Normal file
15
modules/graceful/net_unix_linux_test.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package graceful
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAbstractUnixSocket(t *testing.T) {
|
||||
_, err := DefaultGetListener("unix", "@abc")
|
||||
require.NoError(t, err)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -265,7 +265,7 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
|||
}
|
||||
|
||||
UnixSocketPermission = uint32(UnixSocketPermissionParsed)
|
||||
if !filepath.IsAbs(HTTPAddr) {
|
||||
if HTTPAddr[0] != '@' && !filepath.IsAbs(HTTPAddr) {
|
||||
HTTPAddr = filepath.Join(AppWorkPath, HTTPAddr)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,3 +73,16 @@ MAX_USER_REDIRECTS = 8`
|
|||
assert.EqualValues(t, 3, Service.UsernameCooldownPeriod)
|
||||
assert.EqualValues(t, 8, Service.MaxUserRedirects)
|
||||
}
|
||||
|
||||
func TestUnixSocketAbstractNamespace(t *testing.T) {
|
||||
iniStr := `
|
||||
[server]
|
||||
PROTOCOL=http+unix
|
||||
HTTP_ADDR=@forgejo
|
||||
`
|
||||
cfg, err := NewConfigProviderFromData(iniStr)
|
||||
require.NoError(t, err)
|
||||
loadServerFrom(cfg)
|
||||
|
||||
assert.EqualValues(t, "@forgejo", HTTPAddr)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -156,7 +156,15 @@ commits = fallback value for commits
|
|||
assert.ElementsMatch(t, []string{"lang1", "lang2"}, langs)
|
||||
assert.ElementsMatch(t, []string{"Lang1", "Lang2"}, descs)
|
||||
|
||||
found := lang1.HasKey("no-such")
|
||||
// Test HasKey for JSON
|
||||
found := lang2.HasKey("section.json")
|
||||
assert.True(t, found)
|
||||
|
||||
// Test HasKey for INI
|
||||
found = lang2.HasKey("section.sub")
|
||||
assert.True(t, found)
|
||||
|
||||
found = lang1.HasKey("no-such")
|
||||
assert.False(t, found)
|
||||
assert.EqualValues(t, "no-such", lang1.TrString("no-such"))
|
||||
require.NoError(t, ls.Close())
|
||||
|
|
|
@ -303,6 +303,10 @@ func (l *locale) TrPluralString(count any, trKey string, trArgs ...any) template
|
|||
|
||||
// HasKey returns whether a key is present in this locale or not
|
||||
func (l *locale) HasKey(trKey string) bool {
|
||||
_, ok := l.newStyleMessages[trKey]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
idx, ok := l.store.trKeyToIdxMap[trKey]
|
||||
if !ok {
|
||||
return false
|
||||
|
|
|
@ -39,6 +39,10 @@ func (l MockLocale) TrSize(s int64) ReadableSize {
|
|||
return ReadableSize{fmt.Sprint(s), ""}
|
||||
}
|
||||
|
||||
func (l MockLocale) HasKey(key string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (l MockLocale) PrettyNumber(v any) string {
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ type Locale interface {
|
|||
|
||||
TrSize(size int64) ReadableSize
|
||||
|
||||
HasKey(trKey string) bool
|
||||
|
||||
PrettyNumber(v any) string
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -166,7 +166,7 @@ filter.public = Public
|
|||
filter.private = Private
|
||||
|
||||
[search]
|
||||
search = Search...
|
||||
search = Search…
|
||||
type_tooltip = Search type
|
||||
fuzzy = Fuzzy
|
||||
fuzzy_tooltip = Include results that also match the search term closely
|
||||
|
@ -176,20 +176,20 @@ exact = Exact
|
|||
exact_tooltip = Include only results that match the exact search term
|
||||
regexp = RegExp
|
||||
regexp_tooltip = Interpret the search term as a regular expression
|
||||
repo_kind = Search repos...
|
||||
user_kind = Search users...
|
||||
org_kind = Search orgs...
|
||||
team_kind = Search teams...
|
||||
code_kind = Search code...
|
||||
repo_kind = Search repos…
|
||||
user_kind = Search users…
|
||||
org_kind = Search orgs…
|
||||
team_kind = Search teams…
|
||||
code_kind = Search code…
|
||||
code_search_unavailable = Code search is currently not available. Please contact the site administrator.
|
||||
package_kind = Search packages...
|
||||
project_kind = Search projects...
|
||||
branch_kind = Search branches...
|
||||
commit_kind = Search commits...
|
||||
runner_kind = Search runners...
|
||||
package_kind = Search packages…
|
||||
project_kind = Search projects…
|
||||
branch_kind = Search branches…
|
||||
commit_kind = Search commits…
|
||||
runner_kind = Search runners…
|
||||
no_results = No matching results found.
|
||||
issue_kind = Search issues...
|
||||
pull_kind = Search pulls...
|
||||
issue_kind = Search issues…
|
||||
pull_kind = Search pulls…
|
||||
keyword_search_unavailable = Searching by keyword is currently not available. Please contact the site administrator.
|
||||
|
||||
[aria]
|
||||
|
@ -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.`
|
||||
|
@ -946,6 +943,10 @@ delete_token = Delete
|
|||
access_token_deletion = Delete access token
|
||||
access_token_deletion_desc = Deleting a token will revoke access to your account for applications using it. This cannot be undone. Continue?
|
||||
delete_token_success = The token has been deleted. Applications using it no longer have access to your account.
|
||||
regenerate_token = Regenerate
|
||||
access_token_regeneration = Regenerate access token
|
||||
access_token_regeneration_desc = Regenerating a token will revoke access to your account for applications using it. This cannot be undone. Continue?
|
||||
regenerate_token_success = The token has been regenerated. Applications that use it no longer have access to your account and must be updated with the new token.
|
||||
repo_and_org_access = Repository and Organization Access
|
||||
permissions_public_only = Public only
|
||||
permissions_access_all = All (public, private, and limited)
|
||||
|
@ -1170,7 +1171,7 @@ stars = Stars
|
|||
reactions_more = and %d more
|
||||
unit_disabled = The site administrator has disabled this repository section.
|
||||
language_other = Other
|
||||
adopt_search = Enter username to search for unadopted repositories... (leave blank to find all)
|
||||
adopt_search = Enter username to search for unadopted repositories… (leave blank to find all)
|
||||
adopt_preexisting_label = Adopt files
|
||||
adopt_preexisting = Adopt pre-existing files
|
||||
adopt_preexisting_content = Create repository from %s
|
||||
|
@ -1257,7 +1258,7 @@ migrate.migrate_items_options = Access token is required to migrate additional i
|
|||
migrated_from = Migrated from <a href="%[1]s">%[2]s</a>
|
||||
migrated_from_fake = Migrated from %[1]s
|
||||
migrate.migrate = Migrate from %s
|
||||
migrate.migrating = Migrating from <b>%s</b> ...
|
||||
migrate.migrating = Migrating from <b>%s</b> …
|
||||
migrate.migrating_failed = Migrating from <b>%s</b> failed.
|
||||
migrate.migrating_failed.error = Failed to migrate: %s
|
||||
migrate.migrating_failed_no_addr = Migration failed.
|
||||
|
@ -2655,7 +2656,7 @@ settings.lfs_invalid_locking_path=Invalid path: %s
|
|||
settings.lfs_invalid_lock_directory=Cannot lock directory: %s
|
||||
settings.lfs_lock_already_exists=Lock already exists: %s
|
||||
settings.lfs_lock=Lock
|
||||
settings.lfs_lock_path=Filepath to lock...
|
||||
settings.lfs_lock_path=Filepath to lock…
|
||||
settings.lfs_locks_no_locks=No locks
|
||||
settings.lfs_lock_file_no_exist=Locked file does not exist in default branch
|
||||
settings.lfs_force_unlock=Force unlock
|
||||
|
@ -2865,7 +2866,7 @@ ext_issues = Access the link to an external issue tracker. The permissions are m
|
|||
ext_wiki = Access the link to an external wiki. The permissions are managed externally.
|
||||
|
||||
[graphs]
|
||||
component_loading = Loading %s...
|
||||
component_loading = Loading %s…
|
||||
component_loading_failed = Could not load %s
|
||||
component_loading_info = This might take a bit…
|
||||
component_failed_to_load = An unexpected error happened.
|
||||
|
@ -3300,16 +3301,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
|
||||
|
@ -3989,4 +3980,4 @@ reporting_failed = Something went wrong while trying to submit the new abuse rep
|
|||
reported_thank_you = Thank you for your report. An administrator will look into it shortly.
|
||||
|
||||
[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 :)
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
"one": "wants to merge %[1]d commit from <code>%[2]s</code> into <code id=\"%[4]s\">%[3]s</code>",
|
||||
"other": "wants to merge %[1]d commits from <code>%[2]s</code> into <code id=\"%[4]s\">%[3]s</code>"
|
||||
},
|
||||
"search.milestone_kind": "Search milestones...",
|
||||
"incorrect_root_url": "This Forgejo instance is configured to be served on \"%s\". You are currently viewing Forgejo through a different URL, which may cause parts of the application to break. The canonical URL is controlled by Forgejo admins via the ROOT_URL setting in the app.ini."
|
||||
"search.milestone_kind": "Search milestones…",
|
||||
"incorrect_root_url": "This Forgejo instance is configured to be served on \"%s\". You are currently viewing Forgejo through a different URL, which may cause parts of the application to break. The canonical URL is controlled by Forgejo admins via the ROOT_URL setting in the app.ini.",
|
||||
"themes.names.forgejo-auto": "Forgejo (follow system theme)",
|
||||
"themes.names.forgejo-light": "Forgejo light",
|
||||
"themes.names.forgejo-dark": "Forgejo dark"
|
||||
}
|
||||
|
|
2896
package-lock.json
generated
2896
package-lock.json
generated
File diff suppressed because it is too large
Load diff
30
package.json
30
package.json
|
@ -64,40 +64,40 @@
|
|||
"@eslint-community/eslint-plugin-eslint-comments": "4.4.1",
|
||||
"@playwright/test": "1.50.1",
|
||||
"@stoplight/spectral-cli": "6.14.2",
|
||||
"@stylistic/eslint-plugin-js": "2.12.1",
|
||||
"@stylistic/eslint-plugin-js": "4.2.0",
|
||||
"@stylistic/stylelint-plugin": "3.1.2",
|
||||
"@typescript-eslint/parser": "8.18.2",
|
||||
"@typescript-eslint/parser": "8.26.0",
|
||||
"@vitejs/plugin-vue": "5.1.5",
|
||||
"@vitest/coverage-v8": "3.0.5",
|
||||
"@vitest/eslint-plugin": "1.1.25",
|
||||
"@vue/test-utils": "2.4.6",
|
||||
"eslint": "9.17.0",
|
||||
"eslint-import-resolver-typescript": "3.7.0",
|
||||
"eslint": "9.22.0",
|
||||
"eslint-import-resolver-typescript": "3.8.3",
|
||||
"eslint-plugin-array-func": "5.0.2",
|
||||
"eslint-plugin-import-x": "4.6.1",
|
||||
"eslint-plugin-no-jquery": "3.1.0",
|
||||
"eslint-plugin-no-jquery": "3.1.1",
|
||||
"eslint-plugin-no-use-extend-native": "0.7.2",
|
||||
"eslint-plugin-playwright": "2.1.0",
|
||||
"eslint-plugin-playwright": "2.2.0",
|
||||
"eslint-plugin-regexp": "2.7.0",
|
||||
"eslint-plugin-sonarjs": "3.0.1",
|
||||
"eslint-plugin-unicorn": "56.0.1",
|
||||
"eslint-plugin-sonarjs": "3.0.2",
|
||||
"eslint-plugin-unicorn": "57.0.0",
|
||||
"eslint-plugin-toml": "0.12.0",
|
||||
"eslint-plugin-vitest-globals": "1.5.0",
|
||||
"eslint-plugin-vue": "9.32.0",
|
||||
"eslint-plugin-vue": "10.0.0",
|
||||
"eslint-plugin-vue-scoped-css": "2.9.0",
|
||||
"eslint-plugin-wc": "2.2.0",
|
||||
"globals": "15.15.0",
|
||||
"happy-dom": "17.1.8",
|
||||
"eslint-plugin-wc": "2.2.1",
|
||||
"globals": "16.0.0",
|
||||
"happy-dom": "17.4.3",
|
||||
"license-checker-rseidelsohn": "4.4.2",
|
||||
"markdownlint-cli": "0.44.0",
|
||||
"postcss-html": "1.8.0",
|
||||
"stylelint": "16.12.0",
|
||||
"stylelint": "16.15.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
|
||||
"stylelint-declaration-strict-value": "1.10.6",
|
||||
"stylelint-declaration-strict-value": "1.10.11",
|
||||
"stylelint-value-no-unknown-custom-properties": "6.0.1",
|
||||
"svgo": "3.2.0",
|
||||
"typescript": "5.7.3",
|
||||
"typescript-eslint": "8.18.2",
|
||||
"typescript-eslint": "8.26.0",
|
||||
"vite-string-plugin": "1.3.4",
|
||||
"vitest": "3.0.5"
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -82,6 +82,24 @@ func CreateCodeComment(ctx *context.Context) {
|
|||
attachments = form.Files
|
||||
}
|
||||
|
||||
// If the reply is made to a comment that is part of a pending review, then
|
||||
// this comment also should be seen as part of that pending review. Consider
|
||||
// it to be a pending review by default, except when `single_review` was
|
||||
// passed.
|
||||
pendingReview := !form.SingleReview
|
||||
if form.Reply > 0 {
|
||||
r, err := issues_model.GetReviewByID(ctx, form.Reply)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetReviewByID", err)
|
||||
return
|
||||
}
|
||||
if r.IssueID != issue.ID {
|
||||
ctx.NotFound("Review does not belong to pull request", nil)
|
||||
return
|
||||
}
|
||||
pendingReview = r.Type == issues_model.ReviewTypePending
|
||||
}
|
||||
|
||||
comment, err := pull_service.CreateCodeComment(ctx,
|
||||
ctx.Doer,
|
||||
ctx.Repo.GitRepo,
|
||||
|
@ -89,7 +107,7 @@ func CreateCodeComment(ctx *context.Context) {
|
|||
signedLine,
|
||||
form.Content,
|
||||
form.TreePath,
|
||||
!form.SingleReview,
|
||||
pendingReview,
|
||||
form.Reply,
|
||||
form.LatestCommitID,
|
||||
attachments,
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
|
@ -87,6 +88,23 @@ func DeleteApplication(ctx *context.Context) {
|
|||
ctx.JSONRedirect(setting.AppSubURL + "/user/settings/applications")
|
||||
}
|
||||
|
||||
// RegenerateApplication response for regenerating user access token
|
||||
func RegenerateApplication(ctx *context.Context) {
|
||||
if t, err := auth_model.RegenerateAccessTokenByID(ctx, ctx.FormInt64("id"), ctx.Doer.ID); err != nil {
|
||||
if auth_model.IsErrAccessTokenNotExist(err) {
|
||||
ctx.Flash.Error(ctx.Tr("error.not_found"))
|
||||
} else {
|
||||
ctx.Flash.Error(ctx.Tr("error.server_internal"))
|
||||
log.Error("DeleteAccessTokenByID", err)
|
||||
}
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("settings.regenerate_token_success"))
|
||||
ctx.Flash.Info(t.Token)
|
||||
}
|
||||
|
||||
ctx.JSONRedirect(setting.AppSubURL + "/user/settings/applications")
|
||||
}
|
||||
|
||||
func loadApplicationsData(ctx *context.Context) {
|
||||
ctx.Data["AccessTokenScopePublicOnly"] = auth_model.AccessTokenScopePublicOnly
|
||||
tokens, err := db.Find[auth_model.AccessToken](ctx, auth_model.ListAccessTokensOptions{UserID: ctx.Doer.ID})
|
||||
|
|
|
@ -329,6 +329,14 @@ func Repos(ctx *context.Context) {
|
|||
func Appearance(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings.appearance")
|
||||
ctx.Data["PageIsSettingsAppearance"] = true
|
||||
ctx.Data["AllThemes"] = setting.UI.Themes
|
||||
ctx.Data["ThemeName"] = func(themeName string) string {
|
||||
fullThemeName := "themes.names." + themeName
|
||||
if ctx.Locale.HasKey(fullThemeName) {
|
||||
return ctx.Locale.TrString(fullThemeName)
|
||||
}
|
||||
return themeName
|
||||
}
|
||||
|
||||
var hiddenCommentTypes *big.Int
|
||||
val, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes)
|
||||
|
|
|
@ -9,8 +9,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"
|
||||
|
@ -112,10 +110,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
|
||||
}
|
||||
|
||||
|
@ -599,6 +593,7 @@ func registerRoutes(m *web.Route) {
|
|||
m.Combo("").Get(user_setting.Applications).
|
||||
Post(web.Bind(forms.NewAccessTokenForm{}), user_setting.ApplicationsPost)
|
||||
m.Post("/delete", user_setting.DeleteApplication)
|
||||
m.Post("/regenerate", user_setting.RegenerateApplication)
|
||||
})
|
||||
|
||||
m.Combo("/keys").Get(user_setting.Keys).
|
||||
|
@ -652,7 +647,7 @@ func registerRoutes(m *web.Route) {
|
|||
m.Post("/unblock", user_setting.UnblockUser)
|
||||
})
|
||||
m.Get("/storage_overview", user_setting.StorageOverview)
|
||||
}, reqSignIn, ctxDataSet("PageIsUserSettings", true, "AllThemes", setting.UI.Themes, "EnablePackages", setting.Packages.Enabled, "EnableQuota", setting.Quota.Enabled))
|
||||
}, reqSignIn, ctxDataSet("PageIsUserSettings", true, "EnablePackages", setting.Packages.Enabled, "EnableQuota", setting.Quota.Enabled))
|
||||
|
||||
m.Group("/user", func() {
|
||||
m.Get("/activate", auth.Activate)
|
||||
|
|
|
@ -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>
|
|
@ -54,7 +54,7 @@
|
|||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="q" value="{{$.Keyword}}">
|
||||
<input type="hidden" name="page" value="{{$.CurrentPage}}">
|
||||
{{template "base/modal_actions_confirm" (dict "ModalButtonColors" "primary")}}
|
||||
{{template "base/modal_actions_confirm"}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -194,13 +194,13 @@
|
|||
{{else}}
|
||||
{{if .ShowRegistrationButton}}
|
||||
<a class="item{{if .PageIsSignUp}} active{{end}}" href="{{AppSubUrl}}/user/sign_up">
|
||||
{{svg "octicon-person"}}
|
||||
<span class="tw-ml-1">{{ctx.Locale.Tr "register"}}</span>
|
||||
{{svg "octicon-person" 16 "tw-mr-1"}}
|
||||
<span>{{ctx.Locale.Tr "register"}}</span>
|
||||
</a>
|
||||
{{end}}
|
||||
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login{{if not .PageIsSignIn}}?redirect_to={{.CurrentURL}}{{end}}">
|
||||
{{svg "octicon-sign-in"}}
|
||||
<span class="tw-ml-1">{{ctx.Locale.Tr "sign_in"}}</span>
|
||||
{{svg "octicon-sign-in" 16 "tw-mr-1"}}
|
||||
<span>{{ctx.Locale.Tr "sign_in"}}</span>
|
||||
</a>
|
||||
{{end}}
|
||||
</div><!-- end full right menu -->
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
{{/*
|
||||
Two buttons (negative, positive):
|
||||
* ModalButtonTypes: "yes" (default) or "confirm"
|
||||
* ModalButtonColors: "primary" (default) / "blue" / "yellow"
|
||||
* ModalButtonCancelText
|
||||
* ModalButtonOkText
|
||||
|
||||
|
@ -23,13 +22,7 @@ The ".ok.button" and ".cancel.button" selectors are also used by Fomantic Modal
|
|||
{{if .ModalButtonCancelText}}{{$textNegitive = .ModalButtonCancelText}}{{end}}
|
||||
{{if .ModalButtonOkText}}{{$textPositive = .ModalButtonOkText}}{{end}}
|
||||
|
||||
{{$stylePositive := "primary"}}
|
||||
{{if eq .ModalButtonColors "blue"}}
|
||||
{{$stylePositive = "blue"}}
|
||||
{{else if eq .ModalButtonColors "yellow"}}
|
||||
{{$stylePositive = "yellow"}}
|
||||
{{end}}
|
||||
<button class="ui cancel button">{{svg "octicon-x"}} {{$textNegitive}}</button>
|
||||
<button class="ui {{$stylePositive}} ok button">{{svg "octicon-check"}} {{$textPositive}}</button>
|
||||
<button class="ui primary ok button">{{svg "octicon-check"}} {{$textPositive}}</button>
|
||||
{{end}}
|
||||
</div>
|
||||
|
|
|
@ -54,18 +54,6 @@
|
|||
{{template "base/modal_actions_confirm" (dict "ModalButtonTypes" "confirm")}}
|
||||
</div>
|
||||
|
||||
<div class="ui g-modal-confirm modal" id="test-modal-blue">
|
||||
<div class="header">Blue dialog</div>
|
||||
<div class="content">hello, this is the modal dialog content</div>
|
||||
{{template "base/modal_actions_confirm" (dict "ModalButtonColors" "blue")}}
|
||||
</div>
|
||||
|
||||
<div class="ui g-modal-confirm modal" id="test-modal-yellow">
|
||||
<div class="header">yellow dialog</div>
|
||||
<div class="content">hello, this is the modal dialog content</div>
|
||||
{{template "base/modal_actions_confirm" (dict "ModalButtonColors" "yellow")}}
|
||||
</div>
|
||||
|
||||
<div class="ui g-modal-confirm modal" id="test-modal-danger">
|
||||
{{svg "octicon-x" 16 "inside close"}}
|
||||
<div class="header">dangerous action dialog</div>
|
||||
|
|
|
@ -41,10 +41,12 @@
|
|||
<div class="milestone-list">
|
||||
{{range .Projects}}
|
||||
<li class="milestone-card">
|
||||
<h3 class="flex-text-block tw-m-0 tw-gap-3">
|
||||
{{svg .IconName 16}}
|
||||
<a class="muted tw-break-anywhere" href="{{.Link ctx}}">{{.Title}}</a>
|
||||
</h3>
|
||||
<div class="milestone-header">
|
||||
<h3>
|
||||
{{svg .IconName 16}}
|
||||
<a class="muted tw-break-anywhere" href="{{.Link ctx}}">{{.Title}}</a>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="milestone-toolbar">
|
||||
<div class="group">
|
||||
<div class="flex-text-block">
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
{{if $.reply}}
|
||||
<button class="ui submit primary tiny button btn-reply" type="submit">{{ctx.Locale.Tr "repo.diff.comment.reply"}}</button>
|
||||
<input type="hidden" name="reply" value="{{$.reply}}">
|
||||
<input type="hidden" name="single_review" value="true">
|
||||
{{else}}
|
||||
{{if $.root.CurrentReview}}
|
||||
<button name="pending_review" type="submit" class="ui submit primary tiny button btn-add-comment">{{ctx.Locale.Tr "repo.diff.comment.add_review_comment"}}</button>
|
||||
|
|
|
@ -85,7 +85,7 @@
|
|||
</div>
|
||||
{{range .OpenProjects}}
|
||||
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects">
|
||||
{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
|
||||
{{svg .IconName 16 "tw-mr-2"}}{{.Title}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
@ -96,7 +96,7 @@
|
|||
</div>
|
||||
{{range .ClosedProjects}}
|
||||
<div class="item issue-action" data-element-id="{{.ID}}" data-url="{{$.RepoLink}}/issues/projects">
|
||||
{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
|
||||
{{svg .IconName 16 "tw-mr-2"}}{{.Title}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
</div>
|
||||
{{range .OpenProjects}}
|
||||
<a rel="nofollow" class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item tw-flex" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">
|
||||
{{svg .IconName 18 "tw-mr-2 tw-shrink-0"}}<span class="gt-ellipsis">{{.Title}}</span>
|
||||
{{svg .IconName 16 "tw-mr-2 tw-shrink-0"}}<span class="gt-ellipsis">{{.Title}}</span>
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
@ -71,7 +71,7 @@
|
|||
</div>
|
||||
{{range .ClosedProjects}}
|
||||
<a rel="nofollow" class="{{if $.ProjectID}}{{if eq $.ProjectID .ID}}active selected{{end}}{{end}} item" href="?q={{$.Keyword}}&type={{$.ViewType}}&sort={{$.SortType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{$.MilestoneID}}&project={{.ID}}&assignee={{$.AssigneeID}}&poster={{$.PosterID}}{{if $.ShowArchivedLabels}}&archived=true{{end}}">
|
||||
{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
|
||||
{{svg .IconName 16 "tw-mr-2"}}{{.Title}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
{{range .Milestones}}
|
||||
<li class="milestone-card">
|
||||
<div class="milestone-header">
|
||||
<h3 class="flex-text-block tw-m-0">
|
||||
<h3>
|
||||
{{svg "octicon-milestone" 16}}
|
||||
<a class="muted" href="{{$.RepoLink}}/milestone/{{.ID}}">{{.Name}}</a>
|
||||
</h3>
|
||||
|
|
|
@ -110,7 +110,7 @@
|
|||
</div>
|
||||
{{range .OpenProjects}}
|
||||
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
|
||||
{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
|
||||
{{svg .IconName 16 "tw-mr-2"}}{{.Title}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
@ -121,7 +121,7 @@
|
|||
</div>
|
||||
{{range .ClosedProjects}}
|
||||
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
|
||||
{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
|
||||
{{svg .IconName 16 "tw-mr-2"}}{{.Title}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
@ -133,7 +133,7 @@
|
|||
<div class="selected">
|
||||
{{if .Project}}
|
||||
<a class="item muted sidebar-item-link" href="{{.Project.Link ctx}}">
|
||||
{{svg .Project.IconName 18 "tw-mr-2"}}{{.Project.Title}}
|
||||
{{svg .Project.IconName 16 "tw-mr-2"}}{{.Project.Title}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</div>
|
||||
{{range .OpenProjects}}
|
||||
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
|
||||
{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
|
||||
{{svg .IconName 16 "tw-mr-2"}}{{.Title}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
@ -36,7 +36,7 @@
|
|||
</div>
|
||||
{{range .ClosedProjects}}
|
||||
<a class="item muted sidebar-item-link" data-id="{{.ID}}" data-href="{{.Link ctx}}">
|
||||
{{svg .IconName 18 "tw-mr-2"}}{{.Title}}
|
||||
{{svg .IconName 16 "tw-mr-2"}}{{.Title}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
@ -47,7 +47,7 @@
|
|||
<div class="selected">
|
||||
{{if .Issue.Project}}
|
||||
<a class="item muted sidebar-item-link" href="{{.Issue.Project.Link ctx}}">
|
||||
{{svg .Issue.Project.IconName 18 "tw-mr-2"}}{{.Issue.Project.Title}}
|
||||
{{svg .Issue.Project.IconName 16 "tw-mr-2"}}{{.Issue.Project.Title}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
</p>
|
||||
<form class="ui form" action="{{$.Link}}/delete/{{.Oid}}" method="post">
|
||||
{{$.CsrfTokenHtml}}
|
||||
{{template "base/modal_actions_confirm" (dict "ModalButtonColors" "primary")}}
|
||||
{{template "base/modal_actions_confirm"}}
|
||||
</form>
|
||||
</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>
|
||||
|
|
|
@ -107,7 +107,7 @@
|
|||
{{else if .GetOpType.InActions "create_pull_request"}}
|
||||
<span class="text truncate issue title">{{RenderIssueTitle ctx (index .GetIssueInfos 1) (.Repo.ComposeMetas ctx)}}</span>
|
||||
{{else if .GetOpType.InActions "comment_issue" "approve_pull_request" "reject_pull_request" "comment_pull"}}
|
||||
<a href="{{.GetCommentLink ctx}}" class="text truncate issue title">{{RenderIssueTitle ctx (.GetIssueTitle ctx) (.Repo.ComposeMetas ctx)}}</a>
|
||||
<a href="{{.GetCommentLink ctx}}" class="text truncate issue title">{{(.GetIssueTitle ctx) | RenderEmoji $.Context | RenderCodeBlock}}</a>
|
||||
{{$comment := index .GetIssueInfos 1}}
|
||||
{{if $comment}}
|
||||
<div class="markup tw-text-14">{{RenderMarkdownToHtml ctx $comment}}</div>
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
{{range .Milestones}}
|
||||
<li class="milestone-card">
|
||||
<div class="milestone-header">
|
||||
<h3 class="flex-text-block tw-m-0">
|
||||
<h3>
|
||||
<span class="ui large label">
|
||||
{{.Repo.FullName}}
|
||||
</span>
|
||||
|
|
|
@ -19,15 +19,15 @@
|
|||
<input name="theme" type="hidden" value="{{.SignedUser.Theme}}">
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
<div class="text">
|
||||
{{range $i,$a := .AllThemes}}
|
||||
{{if eq $.SignedUser.Theme $a}}{{$a}}{{end}}
|
||||
{{end}}
|
||||
{{- range $i,$a := .AllThemes -}}
|
||||
{{if eq $.SignedUser.Theme $a}}{{call $.ThemeName $a}}{{end}}
|
||||
{{- end -}}
|
||||
</div>
|
||||
|
||||
<div class="menu">
|
||||
{{range $i,$a := .AllThemes}}
|
||||
<div class="item{{if eq $.SignedUser.Theme $a}} active selected{{end}}" data-value="{{$a}}">
|
||||
{{$a}}
|
||||
{{call $.ThemeName $a}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
|
|
@ -40,6 +40,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="flex-item-trailing">
|
||||
<button class="ui primary tiny button delete-button" data-modal-id="regenerate-token" data-url="{{$.Link}}/regenerate" data-id="{{.ID}}">
|
||||
{{svg "octicon-issue-reopened" 16 "tw-mr-1"}}
|
||||
{{ctx.Locale.Tr "settings.regenerate_token"}}
|
||||
</button>
|
||||
<button class="ui red tiny button delete-button" data-modal-id="delete-token" data-url="{{$.Link}}/delete" data-id="{{.ID}}">
|
||||
{{svg "octicon-trash" 16 "tw-mr-1"}}
|
||||
{{ctx.Locale.Tr "settings.delete_token"}}
|
||||
|
@ -99,6 +103,17 @@
|
|||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="ui g-modal-confirm delete modal" id="regenerate-token">
|
||||
<div class="header">
|
||||
{{svg "octicon-issue-reopened" 16 "tw-mr-1"}}
|
||||
{{ctx.Locale.Tr "settings.access_token_regeneration"}}
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "settings.access_token_regeneration_desc"}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" (dict "ModalButtonColors" "primary")}}
|
||||
</div>
|
||||
|
||||
<div class="ui g-modal-confirm delete modal" id="delete-token">
|
||||
<div class="header">
|
||||
{{svg "octicon-trash"}}
|
||||
|
@ -107,7 +122,7 @@
|
|||
<div class="content">
|
||||
<p>{{ctx.Locale.Tr "settings.access_token_deletion_desc"}}</p>
|
||||
</div>
|
||||
{{template "base/modal_actions_confirm" (dict "ModalButtonColors" "primary")}}
|
||||
{{template "base/modal_actions_confirm"}}
|
||||
</div>
|
||||
|
||||
{{template "user/settings/layout_footer" .}}
|
||||
|
|
|
@ -14,8 +14,7 @@
|
|||
<label for="passcode">{{ctx.Locale.Tr "passcode"}}</label>
|
||||
<input id="passcode" name="passcode" autofocus required>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<div class="field">
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "auth.verify"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
57
tests/integration/appearance_settings_test.go
Normal file
57
tests/integration/appearance_settings_test.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestThemeChange(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
user := loginUser(t, "user2")
|
||||
|
||||
// Verify default theme
|
||||
testSelectedTheme(t, user, "forgejo-auto", "Forgejo (follow system theme)")
|
||||
|
||||
// Change theme to forgejo-dark and verify it works fine
|
||||
testChangeTheme(t, user, "forgejo-dark")
|
||||
testSelectedTheme(t, user, "forgejo-dark", "Forgejo dark")
|
||||
|
||||
// Change theme to gitea-dark and also verify that it's name is not translated
|
||||
testChangeTheme(t, user, "gitea-dark")
|
||||
testSelectedTheme(t, user, "gitea-dark", "gitea-dark")
|
||||
}
|
||||
|
||||
// testSelectedTheme checks that the expected theme is used in html[data-theme]
|
||||
// and is default on appearance page
|
||||
func testSelectedTheme(t *testing.T, session *TestSession, expectedTheme, expectedName string) {
|
||||
t.Helper()
|
||||
response := session.MakeRequest(t, NewRequest(t, "GET", "/user/settings/appearance"), http.StatusOK)
|
||||
page := NewHTMLParser(t, response.Body)
|
||||
|
||||
dataTheme, dataThemeExists := page.Find("html").Attr("data-theme")
|
||||
assert.True(t, dataThemeExists)
|
||||
assert.EqualValues(t, expectedTheme, dataTheme)
|
||||
|
||||
selectedTheme := page.Find("form[action='/user/settings/appearance/theme'] .menu .item.selected")
|
||||
selectorTheme, selectorThemeExists := selectedTheme.Attr("data-value")
|
||||
assert.True(t, selectorThemeExists)
|
||||
assert.EqualValues(t, expectedTheme, selectorTheme)
|
||||
assert.EqualValues(t, expectedName, strings.TrimSpace(selectedTheme.Text()))
|
||||
}
|
||||
|
||||
// testSelectedTheme changes user's theme
|
||||
func testChangeTheme(t *testing.T, session *TestSession, newTheme string) {
|
||||
t.Helper()
|
||||
session.MakeRequest(t, NewRequestWithValues(t, "POST", "/user/settings/appearance/theme", map[string]string{
|
||||
"_csrf": GetCSRF(t, session, "/user/settings/appearance"),
|
||||
"theme": newTheme,
|
||||
}), http.StatusSeeOther)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
-
|
||||
id: 1001
|
||||
type: 21 # code comment
|
||||
poster_id: 2
|
||||
issue_id: 2
|
||||
content: "Still pending"
|
||||
review_id: 1002
|
||||
line: 4
|
||||
tree_path: "README.md"
|
||||
created_unix: 1730000000
|
||||
invalidated: false
|
||||
content_version: 1
|
||||
|
||||
-
|
||||
id: 1002
|
||||
type: 21 # code comment
|
||||
poster_id: 2
|
||||
issue_id: 2
|
||||
content: "Visible"
|
||||
review_id: 1001
|
||||
line: 3
|
||||
tree_path: "README.md"
|
||||
created_unix: 1720000000
|
||||
invalidated: false
|
||||
content_version: 1
|
|
@ -0,0 +1,17 @@
|
|||
-
|
||||
id: 1001
|
||||
type: 2
|
||||
reviewer_id: 2
|
||||
issue_id: 2
|
||||
content: "Normal review"
|
||||
updated_unix: 1720000000
|
||||
created_unix: 1720000000
|
||||
|
||||
-
|
||||
id: 1002
|
||||
type: 0
|
||||
reviewer_id: 2
|
||||
issue_id: 2
|
||||
content: "Pending review"
|
||||
updated_unix: 1730000000
|
||||
created_unix: 1730000000
|
|
@ -421,7 +421,15 @@ var tokenCounter int64
|
|||
// but without the "scope_" prefix.
|
||||
func getTokenForLoggedInUser(t testing.TB, session *TestSession, scopes ...auth.AccessTokenScope) string {
|
||||
t.Helper()
|
||||
var token string
|
||||
accessTokenName := fmt.Sprintf("api-testing-token-%d", atomic.AddInt64(&tokenCounter, 1))
|
||||
createApplicationSettingsToken(t, session, accessTokenName, scopes...)
|
||||
token := assertAccessToken(t, session)
|
||||
return token
|
||||
}
|
||||
|
||||
// createApplicationSettingsToken creates a token with given name and scopes for the currently logged in user.
|
||||
// It will assert CSRF token and redirect to the application settings page.
|
||||
func createApplicationSettingsToken(t testing.TB, session *TestSession, name string, scopes ...auth.AccessTokenScope) {
|
||||
req := NewRequest(t, "GET", "/user/settings/applications")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
var csrf string
|
||||
|
@ -439,7 +447,7 @@ func getTokenForLoggedInUser(t testing.TB, session *TestSession, scopes ...auth.
|
|||
assert.NotEmpty(t, csrf)
|
||||
urlValues := url.Values{}
|
||||
urlValues.Add("_csrf", csrf)
|
||||
urlValues.Add("name", fmt.Sprintf("api-testing-token-%d", atomic.AddInt64(&tokenCounter, 1)))
|
||||
urlValues.Add("name", name)
|
||||
for _, scope := range scopes {
|
||||
urlValues.Add("scope", string(scope))
|
||||
}
|
||||
|
@ -458,11 +466,15 @@ func getTokenForLoggedInUser(t testing.TB, session *TestSession, scopes ...auth.
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
req = NewRequest(t, "GET", "/user/settings/applications")
|
||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||
// assertAccessToken retrieves a token from "/user/settings/applications" and returns it.
|
||||
// It will also assert that the page contains a token.
|
||||
func assertAccessToken(t testing.TB, session *TestSession) string {
|
||||
req := NewRequest(t, "GET", "/user/settings/applications")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
token = htmlDoc.doc.Find(".ui.info p").Text()
|
||||
token := htmlDoc.doc.Find(".ui.info p").Text()
|
||||
assert.NotEmpty(t, token)
|
||||
return token
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ func TestViewIssues(t *testing.T) {
|
|||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
search := htmlDoc.doc.Find(".list-header-search > .search > .input > input")
|
||||
placeholder, _ := search.Attr("placeholder")
|
||||
assert.Equal(t, "Search issues...", placeholder)
|
||||
assert.Equal(t, "Search issues…", placeholder)
|
||||
}
|
||||
|
||||
func TestViewIssuesSortByType(t *testing.T) {
|
||||
|
|
|
@ -21,7 +21,7 @@ func TestViewMilestones(t *testing.T) {
|
|||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
search := htmlDoc.doc.Find(".list-header-search > .search > .input > input")
|
||||
placeholder, _ := search.Attr("placeholder")
|
||||
assert.Equal(t, "Search milestones...", placeholder)
|
||||
assert.Equal(t, "Search milestones…", placeholder)
|
||||
}
|
||||
|
||||
func TestMilestonesCount(t *testing.T) {
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
"code.gitea.io/gitea/services/mailer"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
files_service "code.gitea.io/gitea/services/repository/files"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
@ -649,3 +650,115 @@ func getUserNotificationCount(t *testing.T, session *TestSession, csrf string) s
|
|||
doc := NewHTMLParser(t, resp.Body)
|
||||
return doc.Find(`.notification_count`).Text()
|
||||
}
|
||||
|
||||
func TestPullRequestReplyMail(t *testing.T) {
|
||||
defer tests.AddFixtures("tests/integration/fixtures/TestPullRequestReplyMail/")()
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session := loginUser(t, user.Name)
|
||||
|
||||
t.Run("Reply to pending review comment", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
called := false
|
||||
defer test.MockVariableValue(&mailer.SendAsync, func(...*mailer.Message) {
|
||||
called = true
|
||||
})()
|
||||
|
||||
review := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 1002}, "type = 0")
|
||||
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/pulls/2/files/reviews/comments", map[string]string{
|
||||
"_csrf": GetCSRF(t, session, "/user2/repo1/pulls/2"),
|
||||
"origin": "diff",
|
||||
"content": "Just a comment!",
|
||||
"side": "proposed",
|
||||
"line": "4",
|
||||
"path": "README.md",
|
||||
"reply": strconv.FormatInt(review.ID, 10),
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.False(t, called)
|
||||
unittest.AssertExistsIf(t, true, &issues_model.Comment{Content: "Just a comment!", ReviewID: review.ID, IssueID: 2})
|
||||
})
|
||||
|
||||
t.Run("Start a review", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
called := false
|
||||
defer test.MockVariableValue(&mailer.SendAsync, func(msgs ...*mailer.Message) {
|
||||
called = true
|
||||
})()
|
||||
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/pulls/2/files/reviews/comments", map[string]string{
|
||||
"_csrf": GetCSRF(t, session, "/user2/repo1/pulls/2"),
|
||||
"origin": "diff",
|
||||
"content": "Notification time 2!",
|
||||
"side": "proposed",
|
||||
"line": "2",
|
||||
"path": "README.md",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.False(t, called)
|
||||
unittest.AssertExistsIf(t, true, &issues_model.Comment{Content: "Notification time 2!", IssueID: 2})
|
||||
})
|
||||
|
||||
t.Run("Create a single comment", func(t *testing.T) {
|
||||
t.Run("As a reply", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
called := false
|
||||
defer test.MockVariableValue(&mailer.SendAsync, func(msgs ...*mailer.Message) {
|
||||
assert.Len(t, msgs, 2)
|
||||
assert.Equal(t, "user1@example.com", msgs[0].To)
|
||||
assert.EqualValues(t, "Re: [user2/repo1] issue2 (PR #2)", msgs[0].Subject)
|
||||
assert.Contains(t, msgs[0].Body, "Notification time!")
|
||||
called = true
|
||||
})()
|
||||
|
||||
review := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 1001, Type: issues_model.ReviewTypeComment})
|
||||
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/pulls/2/files/reviews/comments", map[string]string{
|
||||
"_csrf": GetCSRF(t, session, "/user2/repo1/pulls/2"),
|
||||
"origin": "diff",
|
||||
"content": "Notification time!",
|
||||
"side": "proposed",
|
||||
"line": "3",
|
||||
"path": "README.md",
|
||||
"reply": strconv.FormatInt(review.ID, 10),
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.True(t, called)
|
||||
unittest.AssertExistsIf(t, true, &issues_model.Comment{Content: "Notification time!", ReviewID: review.ID, IssueID: 2})
|
||||
})
|
||||
t.Run("On a new line", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
called := false
|
||||
defer test.MockVariableValue(&mailer.SendAsync, func(msgs ...*mailer.Message) {
|
||||
assert.Len(t, msgs, 2)
|
||||
assert.Equal(t, "user1@example.com", msgs[0].To)
|
||||
assert.EqualValues(t, "Re: [user2/repo1] issue2 (PR #2)", msgs[0].Subject)
|
||||
assert.Contains(t, msgs[0].Body, "Notification time 2!")
|
||||
called = true
|
||||
})()
|
||||
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/pulls/2/files/reviews/comments", map[string]string{
|
||||
"_csrf": GetCSRF(t, session, "/user2/repo1/pulls/2"),
|
||||
"origin": "diff",
|
||||
"content": "Notification time 2!",
|
||||
"side": "proposed",
|
||||
"line": "5",
|
||||
"path": "README.md",
|
||||
"single_review": "true",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
assert.True(t, called)
|
||||
unittest.AssertExistsIf(t, true, &issues_model.Comment{Content: "Notification time 2!", IssueID: 2})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ func TestViewPulls(t *testing.T) {
|
|||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
search := htmlDoc.doc.Find(".list-header-search > .search > .input > input")
|
||||
placeholder, _ := search.Attr("placeholder")
|
||||
assert.Equal(t, "Search pulls...", placeholder)
|
||||
assert.Equal(t, "Search pulls…", placeholder)
|
||||
}
|
||||
|
||||
func TestPullManuallyMergeWarning(t *testing.T) {
|
||||
|
|
|
@ -33,5 +33,5 @@ func TestRepoCollaborators(t *testing.T) {
|
|||
// Veirfy placeholder
|
||||
placeholder, exists := page.Find("#search-user-box input").Attr("placeholder")
|
||||
assert.True(t, exists)
|
||||
assert.EqualValues(t, "Search users...", placeholder)
|
||||
assert.EqualValues(t, "Search users…", placeholder)
|
||||
}
|
||||
|
|
|
@ -92,7 +92,11 @@ func TestDashboardTitleRendering(t *testing.T) {
|
|||
count := 0
|
||||
htmlDoc.doc.Find("#activity-feed .flex-item-main .title").Each(func(i int, s *goquery.Selection) {
|
||||
count++
|
||||
assert.EqualValues(t, ":exclamation: not rendered", s.Text())
|
||||
if s.IsMatcher(goquery.Single("a")) {
|
||||
assert.EqualValues(t, "❗ not rendered", s.Text())
|
||||
} else {
|
||||
assert.EqualValues(t, ":exclamation: not rendered", s.Text())
|
||||
}
|
||||
})
|
||||
|
||||
assert.EqualValues(t, 6, count)
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"code.gitea.io/gitea/services/mailer"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -247,6 +248,69 @@ func testExportUserGPGKeys(t *testing.T, user, expected string) {
|
|||
assert.Equal(t, expected, resp.Body.String())
|
||||
}
|
||||
|
||||
func TestAccessTokenRegenerate(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
prevLatestTokenName, prevLatestTokenID := findLatestTokenID(t, session)
|
||||
|
||||
createApplicationSettingsToken(t, session, "TestAccessToken", auth_model.AccessTokenScopeWriteUser)
|
||||
oldToken := assertAccessToken(t, session)
|
||||
oldTokenName, oldTokenID := findLatestTokenID(t, session)
|
||||
|
||||
assert.Equal(t, "TestAccessToken", oldTokenName)
|
||||
|
||||
req := NewRequestWithValues(t, "POST", "/user/settings/applications/regenerate", map[string]string{
|
||||
"_csrf": GetCSRF(t, session, "/user/settings/applications"),
|
||||
"id": strconv.Itoa(oldTokenID),
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
newToken := assertAccessToken(t, session)
|
||||
newTokenName, newTokenID := findLatestTokenID(t, session)
|
||||
|
||||
assert.NotEqual(t, oldToken, newToken)
|
||||
assert.Equal(t, oldTokenID, newTokenID)
|
||||
assert.Equal(t, "TestAccessToken", newTokenName)
|
||||
|
||||
req = NewRequestWithValues(t, "POST", "/user/settings/applications/delete", map[string]string{
|
||||
"_csrf": GetCSRF(t, session, "/user/settings/applications"),
|
||||
"id": strconv.Itoa(newTokenID),
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
latestTokenName, latestTokenID := findLatestTokenID(t, session)
|
||||
|
||||
assert.Less(t, latestTokenID, oldTokenID)
|
||||
assert.Equal(t, latestTokenID, prevLatestTokenID)
|
||||
assert.Equal(t, latestTokenName, prevLatestTokenName)
|
||||
assert.NotEqual(t, "TestAccessToken", latestTokenName)
|
||||
}
|
||||
|
||||
func findLatestTokenID(t *testing.T, session *TestSession) (string, int) {
|
||||
req := NewRequest(t, "GET", "/user/settings/applications")
|
||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
latestTokenName := ""
|
||||
latestTokenID := 0
|
||||
htmlDoc.Find(".delete-button").Each(func(i int, s *goquery.Selection) {
|
||||
tokenID, exists := s.Attr("data-id")
|
||||
|
||||
if !exists || tokenID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(tokenID)
|
||||
require.NoError(t, err)
|
||||
if id > latestTokenID {
|
||||
latestTokenName = s.Parent().Parent().Find(".flex-item-title").Text()
|
||||
latestTokenID = id
|
||||
}
|
||||
})
|
||||
|
||||
return latestTokenName, latestTokenID
|
||||
}
|
||||
|
||||
func TestGetUserRss(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -29,6 +29,13 @@
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.milestone-header h3 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.milestone-toolbar {
|
||||
padding-top: 5px;
|
||||
display: flex;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -36,7 +36,7 @@ export function firstStartDateAfterDate(inputDate) {
|
|||
}
|
||||
const dayOfWeek = inputDate.getUTCDay();
|
||||
const daysUntilSunday = 7 - dayOfWeek;
|
||||
const resultDate = new Date(inputDate.getTime());
|
||||
const resultDate = new Date(inputDate);
|
||||
resultDate.setUTCDate(resultDate.getUTCDate() + daysUntilSunday);
|
||||
return resultDate.valueOf();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue