Merge branch 'forgejo' into report_abuse

This commit is contained in:
floss4good 2025-03-09 21:15:51 +00:00
commit 51580beaee
94 changed files with 1169 additions and 3365 deletions

View file

@ -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

View file

@ -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:

View file

@ -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) .

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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
}
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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()).

View file

@ -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
}

View file

@ -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 {

View file

@ -1,8 +1,6 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build !windows
package graceful
import (

View file

@ -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)

View 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)
}

View file

@ -3,8 +3,6 @@
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
//go:build !windows
package graceful
import (

View file

@ -1,8 +1,6 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build !windows
package log
import (

View file

@ -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())
}
}

View file

@ -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
}

View file

@ -1,8 +1,6 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build !windows
package process
import (

View file

@ -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

View file

@ -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

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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())

View file

@ -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

View file

@ -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)
}

View file

@ -39,6 +39,8 @@ type Locale interface {
TrSize(size int64) ReadableSize
HasKey(trKey string) bool
PrettyNumber(v any) string
}

View file

@ -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

View file

@ -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)

View file

@ -1,8 +1,6 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build !windows
package util
import (

View file

@ -1,8 +1,6 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build !windows
package util
import (

View file

@ -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")

View file

@ -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)

View file

@ -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
}

View file

@ -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 :)

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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"
},

View file

@ -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
}

View file

@ -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

View file

@ -1,8 +1,6 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build !windows
package private
import (

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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})

View file

@ -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)

View file

@ -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)

View file

@ -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.

View file

@ -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{}

View file

@ -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{})
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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">

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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 -->

View file

@ -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>

View file

@ -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>

View file

@ -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">

View file

@ -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>

View file

@ -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}}

View file

@ -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}}

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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"}}
&nbsp;SSPI
</a>
{{end}}
</div>
</div>
</div>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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" .}}

View file

@ -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>

View 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)
}

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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})
})
})
}

View file

@ -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) {

View file

@ -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)
}

View file

@ -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)

View file

@ -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)()

View file

@ -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)

View file

@ -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;

View file

@ -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();

View file

@ -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();
}