mirror of
https://github.com/gogs/gogs.git
synced 2026-02-28 01:00:57 +01:00
Compare commits
14 Commits
v0.13.3-rc
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d958a47a0e | ||
|
|
89ad16f9b3 | ||
|
|
9ea429faaf | ||
|
|
c7d2d8b525 | ||
|
|
4dc0a99919 | ||
|
|
9e70cdf437 | ||
|
|
961a79e8f9 | ||
|
|
d568e04831 | ||
|
|
af825ff56f | ||
|
|
71a72a72ad | ||
|
|
4167a4d568 | ||
|
|
5b5793bb4a | ||
|
|
c3eca1fca3 | ||
|
|
33990972fa |
35
.github/workflows/codeball.yml
vendored
35
.github/workflows/codeball.yml
vendored
@@ -1,35 +0,0 @@
|
||||
# Docs: https://github.com/sturdy-dev/codeball-action
|
||||
name: Codeball
|
||||
on: [ pull_request ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
codeball:
|
||||
runs-on: ubuntu-latest
|
||||
name: Codeball
|
||||
steps:
|
||||
|
||||
# Start a new Codeball review job
|
||||
# This step is asynchronous and will return a job id
|
||||
- name: Trigger Codeball
|
||||
id: codeball_baller
|
||||
uses: sturdy-dev/codeball-action/baller@v2
|
||||
|
||||
|
||||
# Wait for Codeball to return the status
|
||||
- name: Get Status
|
||||
id: codeball_status
|
||||
uses: sturdy-dev/codeball-action/status@v2
|
||||
with:
|
||||
codeball-job-id: ${{ steps.codeball_baller.outputs.codeball-job-id }}
|
||||
|
||||
# If Codeball approved the contribution, approve the PR
|
||||
- name: Approve PR
|
||||
uses: sturdy-dev/codeball-action/approver@v2
|
||||
if: ${{ steps.codeball_status.outputs.approved == 'true' }}
|
||||
with:
|
||||
message: "Codeball: LGTM! :+1:"
|
||||
8
.github/workflows/codeql.yml
vendored
8
.github/workflows/codeql.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v3.28.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@fdbfb4d2750291e159f0156def62b853c2798ca2 # v3.28.3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -72,4 +72,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v3.28.3
|
||||
|
||||
68
.github/workflows/docker.yml
vendored
68
.github/workflows/docker.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
- main
|
||||
pull_request:
|
||||
paths:
|
||||
- '.trivy.yaml'
|
||||
- 'Dockerfile'
|
||||
- 'docker/**'
|
||||
- '.github/workflows/docker.yml'
|
||||
@@ -13,29 +14,25 @@ on:
|
||||
|
||||
jobs:
|
||||
buildx:
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'gogs/gogs' }}
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Canel previous runs
|
||||
uses: styfle/cancel-workflow-action@0.9.1
|
||||
with:
|
||||
all_but_latest: true
|
||||
access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
config-inline: |
|
||||
[worker.oci]
|
||||
max-parallelism = 2
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
- name: Inspect builder
|
||||
run: |
|
||||
echo "Name: ${{ steps.buildx.outputs.name }}"
|
||||
@@ -44,18 +41,18 @@ jobs:
|
||||
echo "Flags: ${{ steps.buildx.outputs.flags }}"
|
||||
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Login to GitHub Container registry
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push images
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
@@ -63,14 +60,13 @@ jobs:
|
||||
tags: |
|
||||
gogs/gogs:latest
|
||||
ghcr.io/gogs/gogs:latest
|
||||
registry.digitalocean.com/gogs/gogs:latest
|
||||
- name: Scan for container vulnerabilities
|
||||
uses: aquasecurity/trivy-action@master
|
||||
uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1
|
||||
with:
|
||||
image-ref: gogs/gogs:latest
|
||||
exit-code: '1'
|
||||
- name: Send email on failure
|
||||
uses: dawidd6/action-send-mail@v3
|
||||
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
server_address: smtp.mailgun.org
|
||||
@@ -93,10 +89,10 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
with:
|
||||
config-inline: |
|
||||
[worker.oci]
|
||||
@@ -110,19 +106,19 @@ jobs:
|
||||
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
|
||||
- name: Compute short commit SHA
|
||||
id: short-sha
|
||||
uses: benjlevesque/short-sha@v2.1
|
||||
uses: benjlevesque/short-sha@599815c8ee942a9616c92bcfb4f947a3b670ab0b # v3.0
|
||||
- name: Build and push images
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: |
|
||||
ttl.sh/gogs/gogs-${{ steps.short-sha.outputs.sha }}:1d
|
||||
ttl.sh/gogs/gogs-${{ steps.short-sha.outputs.sha }}:7d
|
||||
- name: Scan for container vulnerabilities
|
||||
uses: aquasecurity/trivy-action@master
|
||||
uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1
|
||||
with:
|
||||
image-ref: ttl.sh/gogs/gogs-${{ steps.short-sha.outputs.sha }}:1d
|
||||
image-ref: ttl.sh/gogs/gogs-${{ steps.short-sha.outputs.sha }}:7d
|
||||
exit-code: '1'
|
||||
|
||||
# Updates to the following section needs to be synced to all release branches within their lifecycles.
|
||||
@@ -137,16 +133,14 @@ jobs:
|
||||
- name: Compute image tag name
|
||||
run: echo "IMAGE_TAG=$(echo $GITHUB_REF_NAME | cut -c 2-)" >> $GITHUB_ENV
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
with:
|
||||
config-inline: |
|
||||
[worker.oci]
|
||||
max-parallelism = 2
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
- name: Inspect builder
|
||||
run: |
|
||||
echo "Name: ${{ steps.buildx.outputs.name }}"
|
||||
@@ -155,18 +149,18 @@ jobs:
|
||||
echo "Flags: ${{ steps.buildx.outputs.flags }}"
|
||||
echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Login to GitHub Container registry
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push images
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
@@ -175,7 +169,7 @@ jobs:
|
||||
gogs/gogs:${{ env.IMAGE_TAG }}
|
||||
ghcr.io/gogs/gogs:${{ env.IMAGE_TAG }}
|
||||
- name: Send email on failure
|
||||
uses: dawidd6/action-send-mail@v3
|
||||
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
server_address: smtp.mailgun.org
|
||||
|
||||
62
.github/workflows/go.yml
vendored
62
.github/workflows/go.yml
vendored
@@ -30,13 +30,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: 1.23.x
|
||||
go-version: 1.25.x
|
||||
- name: Install Task
|
||||
uses: arduino/setup-task@v2
|
||||
uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611 # v2.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Check Go module tidiness and generated files
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
uses: golangci/golangci-lint-action@9fae48acfc02a90574d7c304a1758ef9895495fa # v7.0.1
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=30m
|
||||
@@ -61,25 +61,25 @@ jobs:
|
||||
name: Test
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.23.x ]
|
||||
go-version: [ 1.25.x ]
|
||||
platform: [ ubuntu-latest, macos-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Run tests with coverage
|
||||
run: go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic ./...
|
||||
- name: Upload coverage report to Codecov
|
||||
uses: codecov/codecov-action@v1.5.0
|
||||
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
|
||||
with:
|
||||
file: ./coverage
|
||||
flags: unittests
|
||||
- name: Send email on failure
|
||||
uses: dawidd6/action-send-mail@v3
|
||||
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
|
||||
if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
with:
|
||||
server_address: smtp.mailgun.org
|
||||
@@ -98,28 +98,28 @@ jobs:
|
||||
# Running tests with race detection consumes too much memory on Windows,
|
||||
# see https://github.com/golang/go/issues/46099 for details.
|
||||
test-windows:
|
||||
name: Test
|
||||
name: Test Windows
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.23.x ]
|
||||
go-version: [ 1.25.x ]
|
||||
platform: [ windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Run tests with coverage
|
||||
run: go test -shuffle=on -v -coverprofile=coverage -covermode=atomic ./...
|
||||
- name: Upload coverage report to Codecov
|
||||
uses: codecov/codecov-action@v1.5.0
|
||||
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
|
||||
with:
|
||||
file: ./coverage
|
||||
flags: unittests
|
||||
- name: Send email on failure
|
||||
uses: dawidd6/action-send-mail@v3
|
||||
uses: dawidd6/action-send-mail@2cea9617b09d79a095af21254fbcb7ae95903dde # v3.12.0
|
||||
if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
with:
|
||||
server_address: smtp.mailgun.org
|
||||
@@ -139,7 +139,7 @@ jobs:
|
||||
name: Postgres
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.23.x ]
|
||||
go-version: [ 1.25.x ]
|
||||
platform: [ ubuntu-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
services:
|
||||
@@ -155,12 +155,12 @@ jobs:
|
||||
ports:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Run tests with coverage
|
||||
run: go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic ./internal/db/...
|
||||
env:
|
||||
@@ -175,18 +175,18 @@ jobs:
|
||||
name: MySQL
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.23.x ]
|
||||
platform: [ ubuntu-22.04 ]
|
||||
go-version: [ 1.25.x ]
|
||||
platform: [ ubuntu-22.04 ] # Use the lowest version possible for backwards compatibility
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Start MySQL server
|
||||
run: sudo systemctl start mysql
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Run tests with coverage
|
||||
run: go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic ./internal/db/...
|
||||
env:
|
||||
@@ -200,16 +200,16 @@ jobs:
|
||||
name: SQLite - Go
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.23.x ]
|
||||
go-version: [ 1.25.x ]
|
||||
platform: [ ubuntu-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Run tests with coverage
|
||||
run: go test -shuffle=on -v -race -parallel=1 -coverprofile=coverage -covermode=atomic ./internal/db/...
|
||||
env:
|
||||
|
||||
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v3
|
||||
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '90'
|
||||
|
||||
4
.github/workflows/shell.yml
vendored
4
.github/workflows/shell.yml
vendored
@@ -12,6 +12,6 @@ jobs:
|
||||
name: Shellcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Run ShellCheck
|
||||
uses: ludeeus/action-shellcheck@master
|
||||
uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 # 2.0.0
|
||||
|
||||
170
.golangci.yml
170
.golangci.yml
@@ -1,29 +1,153 @@
|
||||
linters-settings:
|
||||
staticcheck:
|
||||
checks: [
|
||||
"all",
|
||||
"-SA1019" # There are valid use cases of strings.Title
|
||||
]
|
||||
nakedret:
|
||||
max-func-lines: 0 # Disallow any unnamed return statement
|
||||
govet:
|
||||
disable:
|
||||
# printf: non-constant format string in call to fmt.Errorf (govet)
|
||||
# showing up since golangci-lint version 1.60.1
|
||||
- printf
|
||||
|
||||
version: "2"
|
||||
linters:
|
||||
enable:
|
||||
- unused
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- nakedret
|
||||
- gofmt
|
||||
- rowserrcheck
|
||||
- unconvert
|
||||
- goimports
|
||||
- unparam
|
||||
settings:
|
||||
govet:
|
||||
disable:
|
||||
# printf: non-constant format string in call to fmt.Errorf (govet)
|
||||
# showing up since golangci-lint version 1.60.1
|
||||
- printf
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
- "-SA1019" # This project is under active refactoring and not all code is up to date.
|
||||
- "-QF1001" # I'm a math noob
|
||||
- "-ST1016" # Some legit code uses this pattern
|
||||
nakedret:
|
||||
max-func-lines: 0 # Disallow any unnamed return statement
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
rules:
|
||||
- path: internal/auth/ldap/config.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/cmd/import.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/context/context.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/attachment.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/errors/errors.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/issue.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/issue_mail.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/org.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/repo.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/schemadoc/main.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/users.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/webhook.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/webhook_dingtalk.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/email/email.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/email/message.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/form/repo.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/form/user.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/httplib/httplib.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/markup/markdown.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/api/v1/repo/commits.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/api/v1/user/follower.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/repo/branch.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/repo/commit.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/repo/issue.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/user/profile.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/template/template.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/tool/tool.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/cmd/serv.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/actions_test.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/milestone.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/pull.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/home.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/release.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/org/members.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/org/setting.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/repo_branch.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/user_mail.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/user/auth.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/webhook_slack.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/form/form.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/org/teams.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/admin/auths.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/admin/users.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/admin.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/comment.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/user/home.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/user/setting.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/org_team.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/repo_editor.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/process/manager.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/db/ssh_key.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/repo/pull.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/repo/release.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/repo/setting.go
|
||||
linters: [staticcheck]
|
||||
- path: internal/route/repo/wiki.go
|
||||
linters: [staticcheck]
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
||||
4
gen.go
4
gen.go
@@ -4,5 +4,5 @@
|
||||
|
||||
package main
|
||||
|
||||
//go:generate go install golang.org/x/tools/cmd/goimports@v0.17.0
|
||||
//go:generate go run github.com/derision-test/go-mockgen/v2/cmd/go-mockgen@v2.0.1
|
||||
//go:generate go install golang.org/x/tools/cmd/goimports@v0.33.0
|
||||
//go:generate go run github.com/unknwon/go-mockgen/cmd/go-mockgen@v0.0.0-20251002032800-a9a94b119e3b
|
||||
|
||||
2
gogs.go
2
gogs.go
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
conf.App.Version = "0.13.3"
|
||||
conf.App.Version = "0.13.4"
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -324,7 +324,7 @@ func SyncMirrors() {
|
||||
|
||||
m, err := GetMirrorByRepoID(com.StrTo(repoID).MustInt64())
|
||||
if err != nil {
|
||||
log.Error("GetMirrorByRepoID [%d]: %v", m.RepoID, err)
|
||||
log.Error("GetMirrorByRepoID [%v]: %v", repoID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by go-mockgen 1.3.7; DO NOT EDIT.
|
||||
// Code generated by go-mockgen 2.1.1; DO NOT EDIT.
|
||||
//
|
||||
// This file was generated by running `go-mockgen` at the root of this repository.
|
||||
// To add additional mocks to this or another package, add a new entry to the
|
||||
|
||||
@@ -107,6 +107,19 @@ func (repo *Repository) CheckoutNewBranch(oldBranch, newBranch string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasSymlinkInPath returns true if there is any symlink in path hierarchy using
|
||||
// the given base and relative path.
|
||||
func hasSymlinkInPath(base, relPath string) bool {
|
||||
parts := strings.Split(filepath.ToSlash(relPath), "/")
|
||||
for i := range parts {
|
||||
filePath := path.Join(append([]string{base}, parts[:i+1]...)...)
|
||||
if osutil.IsSymlink(filePath) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type UpdateRepoFileOptions struct {
|
||||
OldBranch string
|
||||
NewBranch string
|
||||
@@ -118,7 +131,7 @@ type UpdateRepoFileOptions struct {
|
||||
}
|
||||
|
||||
// UpdateRepoFile adds or updates a file in repository.
|
||||
func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (err error) {
|
||||
func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) error {
|
||||
// 🚨 SECURITY: Prevent uploading files into the ".git" directory.
|
||||
if isRepositoryGitPath(opts.NewTreeName) {
|
||||
return errors.Errorf("bad tree path %q", opts.NewTreeName)
|
||||
@@ -127,15 +140,21 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (
|
||||
repoWorkingPool.CheckIn(com.ToStr(repo.ID))
|
||||
defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
|
||||
|
||||
if err = repo.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil {
|
||||
if err := repo.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil {
|
||||
return fmt.Errorf("discard local repo branch[%s] changes: %v", opts.OldBranch, err)
|
||||
} else if err = repo.UpdateLocalCopyBranch(opts.OldBranch); err != nil {
|
||||
return fmt.Errorf("update local copy branch[%s]: %v", opts.OldBranch, err)
|
||||
}
|
||||
|
||||
repoPath := repo.RepoPath()
|
||||
localPath := repo.LocalCopyPath()
|
||||
|
||||
// 🚨 SECURITY: Prevent touching files in surprising places, reject operations
|
||||
// that involve symlinks.
|
||||
if hasSymlinkInPath(localPath, opts.OldTreeName) || hasSymlinkInPath(localPath, opts.NewTreeName) {
|
||||
return errors.New("cannot update file with symbolic link in path")
|
||||
}
|
||||
|
||||
repoPath := repo.RepoPath()
|
||||
if opts.OldBranch != opts.NewBranch {
|
||||
// Directly return error if new branch already exists in the server
|
||||
if git.RepoHasBranch(repoPath, opts.NewBranch) {
|
||||
@@ -144,7 +163,7 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (
|
||||
|
||||
// Otherwise, delete branch from local copy in case out of sync
|
||||
if git.RepoHasBranch(localPath, opts.NewBranch) {
|
||||
if err = git.DeleteBranch(localPath, opts.NewBranch, git.DeleteBranchOptions{
|
||||
if err := git.DeleteBranch(localPath, opts.NewBranch, git.DeleteBranchOptions{
|
||||
Force: true,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("delete branch %q: %v", opts.NewBranch, err)
|
||||
@@ -157,46 +176,31 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (
|
||||
}
|
||||
|
||||
oldFilePath := path.Join(localPath, opts.OldTreeName)
|
||||
filePath := path.Join(localPath, opts.NewTreeName)
|
||||
if err = os.MkdirAll(path.Dir(filePath), os.ModePerm); err != nil {
|
||||
return err
|
||||
newFilePath := path.Join(localPath, opts.NewTreeName)
|
||||
|
||||
// Prompt the user if the meant-to-be new file already exists.
|
||||
if osutil.IsExist(newFilePath) && opts.IsNewFile {
|
||||
return ErrRepoFileAlreadyExist{newFilePath}
|
||||
}
|
||||
|
||||
// If it's meant to be a new file, make sure it doesn't exist.
|
||||
if opts.IsNewFile {
|
||||
// 🚨 SECURITY: Prevent updating files in surprising place, check if the file is
|
||||
// a symlink.
|
||||
if osutil.IsSymlink(filePath) {
|
||||
return fmt.Errorf("cannot update symbolic link: %s", opts.NewTreeName)
|
||||
}
|
||||
if osutil.IsExist(filePath) {
|
||||
return ErrRepoFileAlreadyExist{filePath}
|
||||
}
|
||||
if err := os.MkdirAll(path.Dir(newFilePath), os.ModePerm); err != nil {
|
||||
return errors.Wrapf(err, "create parent directories of %q", newFilePath)
|
||||
}
|
||||
|
||||
// Ignore move step if it's a new file under a directory.
|
||||
// Otherwise, move the file when name changed.
|
||||
if osutil.IsFile(oldFilePath) && opts.OldTreeName != opts.NewTreeName {
|
||||
// 🚨 SECURITY: Prevent updating files in surprising place, check if the file is
|
||||
// a symlink.
|
||||
if osutil.IsSymlink(oldFilePath) {
|
||||
return fmt.Errorf("cannot move symbolic link: %s", opts.OldTreeName)
|
||||
}
|
||||
|
||||
if err = git.Move(localPath, opts.OldTreeName, opts.NewTreeName); err != nil {
|
||||
return fmt.Errorf("git mv %q %q: %v", opts.OldTreeName, opts.NewTreeName, err)
|
||||
if err := git.Move(localPath, opts.OldTreeName, opts.NewTreeName); err != nil {
|
||||
return errors.Wrapf(err, "git mv %q %q", opts.OldTreeName, opts.NewTreeName)
|
||||
}
|
||||
}
|
||||
|
||||
if err = os.WriteFile(filePath, []byte(opts.Content), 0600); err != nil {
|
||||
if err := os.WriteFile(newFilePath, []byte(opts.Content), 0o600); err != nil {
|
||||
return fmt.Errorf("write file: %v", err)
|
||||
}
|
||||
|
||||
if err = git.Add(localPath, git.AddOptions{All: true}); err != nil {
|
||||
if err := git.Add(localPath, git.AddOptions{All: true}); err != nil {
|
||||
return fmt.Errorf("git add --all: %v", err)
|
||||
}
|
||||
|
||||
err = git.CreateCommit(
|
||||
err := git.CreateCommit(
|
||||
localPath,
|
||||
&git.Signature{
|
||||
Name: doer.DisplayName(),
|
||||
@@ -230,7 +234,7 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (
|
||||
}
|
||||
|
||||
// GetDiffPreview produces and returns diff result of a file which is not yet committed.
|
||||
func (repo *Repository) GetDiffPreview(branch, treePath, content string) (diff *gitutil.Diff, err error) {
|
||||
func (repo *Repository) GetDiffPreview(branch, treePath, content string) (*gitutil.Diff, error) {
|
||||
// 🚨 SECURITY: Prevent uploading files into the ".git" directory.
|
||||
if isRepositoryGitPath(treePath) {
|
||||
return nil, errors.Errorf("bad tree path %q", treePath)
|
||||
@@ -239,7 +243,7 @@ func (repo *Repository) GetDiffPreview(branch, treePath, content string) (diff *
|
||||
repoWorkingPool.CheckIn(com.ToStr(repo.ID))
|
||||
defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
|
||||
|
||||
if err = repo.DiscardLocalRepoBranchChanges(branch); err != nil {
|
||||
if err := repo.DiscardLocalRepoBranchChanges(branch); err != nil {
|
||||
return nil, fmt.Errorf("discard local repo branch[%s] changes: %v", branch, err)
|
||||
} else if err = repo.UpdateLocalCopyBranch(branch); err != nil {
|
||||
return nil, fmt.Errorf("update local copy branch[%s]: %v", branch, err)
|
||||
@@ -248,14 +252,15 @@ func (repo *Repository) GetDiffPreview(branch, treePath, content string) (diff *
|
||||
localPath := repo.LocalCopyPath()
|
||||
filePath := path.Join(localPath, treePath)
|
||||
|
||||
// 🚨 SECURITY: Prevent updating files in surprising place, check if the target is
|
||||
// a symlink.
|
||||
if osutil.IsSymlink(filePath) {
|
||||
return nil, fmt.Errorf("cannot get diff preview for symbolic link: %s", treePath)
|
||||
// 🚨 SECURITY: Prevent touching files in surprising places, reject operations
|
||||
// that involve symlinks.
|
||||
if hasSymlinkInPath(localPath, treePath) {
|
||||
return nil, errors.New("cannot update file with symbolic link in path")
|
||||
}
|
||||
if err = os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
} else if err = os.WriteFile(filePath, []byte(content), 0600); err != nil {
|
||||
|
||||
if err := os.MkdirAll(path.Dir(filePath), os.ModePerm); err != nil {
|
||||
return nil, errors.Wrap(err, "create parent directories")
|
||||
} else if err = os.WriteFile(filePath, []byte(content), 0o600); err != nil {
|
||||
return nil, fmt.Errorf("write file: %v", err)
|
||||
}
|
||||
|
||||
@@ -276,15 +281,13 @@ func (repo *Repository) GetDiffPreview(branch, treePath, content string) (diff *
|
||||
pid := process.Add(fmt.Sprintf("GetDiffPreview [repo_path: %s]", repo.RepoPath()), cmd)
|
||||
defer process.Remove(pid)
|
||||
|
||||
diff, err = gitutil.ParseDiff(stdout, conf.Git.MaxDiffFiles, conf.Git.MaxDiffLines, conf.Git.MaxDiffLineChars)
|
||||
diff, err := gitutil.ParseDiff(stdout, conf.Git.MaxDiffFiles, conf.Git.MaxDiffLines, conf.Git.MaxDiffLineChars)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse diff: %v", err)
|
||||
}
|
||||
|
||||
if err = cmd.Wait(); err != nil {
|
||||
return nil, fmt.Errorf("wait: %v", err)
|
||||
}
|
||||
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
@@ -319,21 +322,21 @@ func (repo *Repository) DeleteRepoFile(doer *User, opts DeleteRepoFileOptions) (
|
||||
return fmt.Errorf("update local copy branch[%s]: %v", opts.OldBranch, err)
|
||||
}
|
||||
|
||||
localPath := repo.LocalCopyPath()
|
||||
|
||||
// 🚨 SECURITY: Prevent touching files in surprising places, reject operations
|
||||
// that involve symlinks.
|
||||
if hasSymlinkInPath(localPath, opts.TreePath) {
|
||||
return errors.New("cannot update file with symbolic link in path")
|
||||
}
|
||||
|
||||
if opts.OldBranch != opts.NewBranch {
|
||||
if err := repo.CheckoutNewBranch(opts.OldBranch, opts.NewBranch); err != nil {
|
||||
return fmt.Errorf("checkout new branch[%s] from old branch[%s]: %v", opts.NewBranch, opts.OldBranch, err)
|
||||
}
|
||||
}
|
||||
|
||||
localPath := repo.LocalCopyPath()
|
||||
filePath := path.Join(localPath, opts.TreePath)
|
||||
|
||||
// 🚨 SECURITY: Prevent updating files in surprising place, check if the file is
|
||||
// a symlink.
|
||||
if osutil.IsSymlink(filePath) {
|
||||
return fmt.Errorf("cannot delete symbolic link: %s", opts.TreePath)
|
||||
}
|
||||
|
||||
if err = os.Remove(filePath); err != nil {
|
||||
return fmt.Errorf("remove file %q: %v", opts.TreePath, err)
|
||||
}
|
||||
|
||||
@@ -21,12 +21,6 @@ func Test_SSHParsePublicKey(t *testing.T) {
|
||||
expType string
|
||||
expLength int
|
||||
}{
|
||||
{
|
||||
name: "dsa-1024",
|
||||
content: "ssh-dss AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgPaguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982daopE7zQ/NPAnJfag= nocomment",
|
||||
expType: "dsa",
|
||||
expLength: 1024,
|
||||
},
|
||||
{
|
||||
name: "rsa-1024",
|
||||
content: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ== nocomment",
|
||||
|
||||
@@ -109,21 +109,3 @@ func IsTwoFactorRecoveryCodeNotFound(err error) bool {
|
||||
func (err ErrTwoFactorRecoveryCodeNotFound) Error() string {
|
||||
return fmt.Sprintf("two-factor recovery code does not found [code: %s]", err.Code)
|
||||
}
|
||||
|
||||
// UseRecoveryCode validates recovery code of given user and marks it is used if valid.
|
||||
func UseRecoveryCode(_ int64, code string) error {
|
||||
recoveryCode := new(TwoFactorRecoveryCode)
|
||||
has, err := x.Where("code = ?", code).And("is_used = ?", false).Get(recoveryCode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get unused code: %v", err)
|
||||
} else if !has {
|
||||
return ErrTwoFactorRecoveryCodeNotFound{Code: code}
|
||||
}
|
||||
|
||||
recoveryCode.IsUsed = true
|
||||
if _, err = x.Id(recoveryCode.ID).Cols("is_used").Update(recoveryCode); err != nil {
|
||||
return fmt.Errorf("mark code as used: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ type TwoFactorsStore interface {
|
||||
GetByUserID(ctx context.Context, userID int64) (*TwoFactor, error)
|
||||
// IsEnabled returns true if the user has enabled 2FA.
|
||||
IsEnabled(ctx context.Context, userID int64) bool
|
||||
UseRecoveryCode(ctx context.Context, userID int64, code string) error
|
||||
}
|
||||
|
||||
var TwoFactors TwoFactorsStore
|
||||
@@ -121,6 +122,28 @@ func (db *twoFactors) IsEnabled(ctx context.Context, userID int64) bool {
|
||||
return count > 0
|
||||
}
|
||||
|
||||
// UseRecoveryCode validates a recovery code of given user and marks it as used
|
||||
// if valid. It returns ErrTwoFactorRecoveryCodeNotFound if the code is invalid
|
||||
// or already used.
|
||||
func (db *twoFactors) UseRecoveryCode(ctx context.Context, userID int64, code string) error {
|
||||
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var recoveryCode TwoFactorRecoveryCode
|
||||
err := tx.Where("user_id = ? AND code = ? AND is_used = ?", userID, code, false).First(&recoveryCode).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return ErrTwoFactorRecoveryCodeNotFound{Code: code}
|
||||
}
|
||||
return errors.Wrap(err, "get unused recovery code")
|
||||
}
|
||||
|
||||
err = tx.Model(&recoveryCode).Update("is_used", true).Error
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "mark recovery code as used")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// generateRecoveryCodes generates N number of recovery codes for 2FA.
|
||||
func generateRecoveryCodes(userID int64, n int) ([]*TwoFactorRecoveryCode, error) {
|
||||
recoveryCodes := make([]*TwoFactorRecoveryCode, n)
|
||||
|
||||
@@ -79,6 +79,7 @@ func TestTwoFactors(t *testing.T) {
|
||||
{"Create", twoFactorsCreate},
|
||||
{"GetByUserID", twoFactorsGetByUserID},
|
||||
{"IsEnabled", twoFactorsIsEnabled},
|
||||
{"UseRecoveryCode", twoFactorsUseRecoveryCode},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
@@ -139,3 +140,57 @@ func twoFactorsIsEnabled(t *testing.T, db *twoFactors) {
|
||||
assert.True(t, db.IsEnabled(ctx, 1))
|
||||
assert.False(t, db.IsEnabled(ctx, 2))
|
||||
}
|
||||
|
||||
func twoFactorsUseRecoveryCode(t *testing.T, db *twoFactors) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create 2FA tokens for two users
|
||||
err := db.Create(ctx, 1, "secure-key", "secure-secret")
|
||||
require.NoError(t, err)
|
||||
err = db.Create(ctx, 2, "secure-key", "secure-secret")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get recovery codes for both users
|
||||
var user1Codes []TwoFactorRecoveryCode
|
||||
err = db.DB.Where("user_id = ?", 1).Find(&user1Codes).Error
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, user1Codes)
|
||||
|
||||
var user2Codes []TwoFactorRecoveryCode
|
||||
err = db.DB.Where("user_id = ?", 2).Find(&user2Codes).Error
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, user2Codes)
|
||||
|
||||
// User 1 should be able to use their own recovery code
|
||||
err = db.UseRecoveryCode(ctx, 1, user1Codes[0].Code)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the code is now marked as used
|
||||
var usedCode TwoFactorRecoveryCode
|
||||
err = db.DB.Where("id = ?", user1Codes[0].ID).First(&usedCode).Error
|
||||
require.NoError(t, err)
|
||||
assert.True(t, usedCode.IsUsed)
|
||||
|
||||
// User 1 should NOT be able to use user 2's recovery code
|
||||
// This is the key security test - recovery codes must be scoped by user
|
||||
err = db.UseRecoveryCode(ctx, 1, user2Codes[0].Code)
|
||||
assert.True(t, IsTwoFactorRecoveryCodeNotFound(err), "expected recovery code not found error when using another user's code")
|
||||
|
||||
// User 2's code should still be unused
|
||||
var user2Code TwoFactorRecoveryCode
|
||||
err = db.DB.Where("id = ?", user2Codes[0].ID).First(&user2Code).Error
|
||||
require.NoError(t, err)
|
||||
assert.False(t, user2Code.IsUsed, "user 2's recovery code should not be marked as used")
|
||||
|
||||
// User 2 should be able to use their own code
|
||||
err = db.UseRecoveryCode(ctx, 2, user2Codes[0].Code)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Using an already-used code should fail
|
||||
err = db.UseRecoveryCode(ctx, 1, user1Codes[0].Code)
|
||||
assert.True(t, IsTwoFactorRecoveryCodeNotFound(err), "expected error when reusing a recovery code")
|
||||
|
||||
// Using a non-existent code should fail
|
||||
err = db.UseRecoveryCode(ctx, 1, "invalid-code")
|
||||
assert.True(t, IsTwoFactorRecoveryCodeNotFound(err), "expected error for invalid recovery code")
|
||||
}
|
||||
|
||||
@@ -18,23 +18,33 @@ import (
|
||||
"github.com/gogs/git-module"
|
||||
|
||||
"gogs.io/gogs/internal/conf"
|
||||
"gogs.io/gogs/internal/pathutil"
|
||||
"gogs.io/gogs/internal/repoutil"
|
||||
"gogs.io/gogs/internal/sync"
|
||||
)
|
||||
|
||||
var wikiWorkingPool = sync.NewExclusivePool()
|
||||
|
||||
// WikiBranch returns the branch name used by the wiki repository. It checks if
|
||||
// "main" branch exists, otherwise falls back to "master".
|
||||
func WikiBranch(repoPath string) string {
|
||||
if git.RepoHasBranch(repoPath, "main") {
|
||||
return "main"
|
||||
}
|
||||
return "master"
|
||||
}
|
||||
|
||||
// ToWikiPageURL formats a string to corresponding wiki URL name.
|
||||
func ToWikiPageURL(name string) string {
|
||||
return url.QueryEscape(name)
|
||||
}
|
||||
|
||||
// ToWikiPageName formats a URL back to corresponding wiki page name,
|
||||
// and removes leading characters './' to prevent changing files
|
||||
// that are not belong to wiki repository.
|
||||
// ToWikiPageName formats a URL back to corresponding wiki page name. It enforces
|
||||
// single-level hierarchy by replacing all "/" with spaces.
|
||||
func ToWikiPageName(urlString string) string {
|
||||
name, _ := url.QueryUnescape(urlString)
|
||||
return strings.ReplaceAll(strings.TrimLeft(path.Clean("/"+name), "/"), "/", " ")
|
||||
name = pathutil.Clean(name)
|
||||
return strings.ReplaceAll(name, "/", " ")
|
||||
}
|
||||
|
||||
// WikiCloneLink returns clone URLs of repository wiki.
|
||||
@@ -79,24 +89,25 @@ func (repo *Repository) LocalWikiPath() string {
|
||||
|
||||
// UpdateLocalWiki makes sure the local copy of repository wiki is up-to-date.
|
||||
func (repo *Repository) UpdateLocalWiki() error {
|
||||
return UpdateLocalCopyBranch(repo.WikiPath(), repo.LocalWikiPath(), "master", true)
|
||||
wikiPath := repo.WikiPath()
|
||||
return UpdateLocalCopyBranch(wikiPath, repo.LocalWikiPath(), WikiBranch(wikiPath), true)
|
||||
}
|
||||
|
||||
func discardLocalWikiChanges(localPath string) error {
|
||||
return discardLocalRepoBranchChanges(localPath, "master")
|
||||
return discardLocalRepoBranchChanges(localPath, WikiBranch(localPath))
|
||||
}
|
||||
|
||||
// updateWikiPage adds new page to repository wiki.
|
||||
func (repo *Repository) updateWikiPage(doer *User, oldTitle, title, content, message string, isNew bool) (err error) {
|
||||
func (repo *Repository) updateWikiPage(doer *User, oldTitle, title, content, message string, isNew bool) error {
|
||||
wikiWorkingPool.CheckIn(com.ToStr(repo.ID))
|
||||
defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID))
|
||||
|
||||
if err = repo.InitWiki(); err != nil {
|
||||
if err := repo.InitWiki(); err != nil {
|
||||
return fmt.Errorf("InitWiki: %v", err)
|
||||
}
|
||||
|
||||
localPath := repo.LocalWikiPath()
|
||||
if err = discardLocalWikiChanges(localPath); err != nil {
|
||||
if err := discardLocalWikiChanges(localPath); err != nil {
|
||||
return fmt.Errorf("discardLocalWikiChanges: %v", err)
|
||||
} else if err = repo.UpdateLocalWiki(); err != nil {
|
||||
return fmt.Errorf("UpdateLocalWiki: %v", err)
|
||||
@@ -111,7 +122,8 @@ func (repo *Repository) updateWikiPage(doer *User, oldTitle, title, content, mes
|
||||
return ErrWikiAlreadyExist{filename}
|
||||
}
|
||||
} else {
|
||||
os.Remove(path.Join(localPath, oldTitle+".md"))
|
||||
oldTitle = ToWikiPageName(oldTitle)
|
||||
_ = os.Remove(path.Join(localPath, oldTitle+".md"))
|
||||
}
|
||||
|
||||
// SECURITY: if new file is a symlink to non-exist critical file,
|
||||
@@ -119,20 +131,20 @@ func (repo *Repository) updateWikiPage(doer *User, oldTitle, title, content, mes
|
||||
// as a new page operation.
|
||||
// So we want to make sure the symlink is removed before write anything.
|
||||
// The new file we created will be in normal text format.
|
||||
os.Remove(filename)
|
||||
_ = os.Remove(filename)
|
||||
|
||||
if err = os.WriteFile(filename, []byte(content), 0666); err != nil {
|
||||
if err := os.WriteFile(filename, []byte(content), 0o666); err != nil {
|
||||
return fmt.Errorf("WriteFile: %v", err)
|
||||
}
|
||||
|
||||
if message == "" {
|
||||
message = "Update page '" + title + "'"
|
||||
}
|
||||
if err = git.Add(localPath, git.AddOptions{All: true}); err != nil {
|
||||
if err := git.Add(localPath, git.AddOptions{All: true}); err != nil {
|
||||
return fmt.Errorf("add all changes: %v", err)
|
||||
}
|
||||
|
||||
err = git.CreateCommit(
|
||||
err := git.CreateCommit(
|
||||
localPath,
|
||||
&git.Signature{
|
||||
Name: doer.DisplayName(),
|
||||
@@ -143,7 +155,7 @@ func (repo *Repository) updateWikiPage(doer *User, oldTitle, title, content, mes
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("commit changes: %v", err)
|
||||
} else if err = git.Push(localPath, "origin", "master"); err != nil {
|
||||
} else if err = git.Push(localPath, "origin", WikiBranch(localPath)); err != nil {
|
||||
return fmt.Errorf("push: %v", err)
|
||||
}
|
||||
|
||||
@@ -170,8 +182,7 @@ func (repo *Repository) DeleteWikiPage(doer *User, title string) (err error) {
|
||||
}
|
||||
|
||||
title = ToWikiPageName(title)
|
||||
filename := path.Join(localPath, title+".md")
|
||||
os.Remove(filename)
|
||||
_ = os.Remove(path.Join(localPath, title+".md"))
|
||||
|
||||
message := "Delete page '" + title + "'"
|
||||
|
||||
@@ -190,7 +201,7 @@ func (repo *Repository) DeleteWikiPage(doer *User, title string) (err error) {
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("commit changes: %v", err)
|
||||
} else if err = git.Push(localPath, "origin", "master"); err != nil {
|
||||
} else if err = git.Push(localPath, "origin", WikiBranch(localPath)); err != nil {
|
||||
return fmt.Errorf("push: %v", err)
|
||||
}
|
||||
|
||||
|
||||
27
internal/db/wiki_test.go
Normal file
27
internal/db/wiki_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestToWikiPageName(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{input: "Home", want: "Home"},
|
||||
{input: "../../../../tmp/target_file", want: "tmp target_file"},
|
||||
{input: "..\\..\\..\\..\\tmp\\target_file", want: "tmp target_file"},
|
||||
{input: "A/B", want: "A B"},
|
||||
{input: "../pwn", want: "pwn"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
got := ToWikiPageName(test.input)
|
||||
require.Equal(t, test.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ import (
|
||||
"os/user"
|
||||
)
|
||||
|
||||
// IsFile returns true if given path exists as a file (i.e. not a directory).
|
||||
// IsFile returns true if given path exists as a file (i.e. not a directory)
|
||||
// following any symlinks.
|
||||
func IsFile(path string) bool {
|
||||
f, e := os.Stat(path)
|
||||
if e != nil {
|
||||
@@ -18,8 +19,8 @@ func IsFile(path string) bool {
|
||||
return !f.IsDir()
|
||||
}
|
||||
|
||||
// IsDir returns true if given path is a directory, and returns false when it's
|
||||
// a file or does not exist.
|
||||
// IsDir returns true if given path is a directory following any symlinks, and
|
||||
// returns false when it's a file or does not exist.
|
||||
func IsDir(dir string) bool {
|
||||
f, e := os.Stat(dir)
|
||||
if e != nil {
|
||||
@@ -28,7 +29,7 @@ func IsDir(dir string) bool {
|
||||
return f.IsDir()
|
||||
}
|
||||
|
||||
// IsExist returns true if a file or directory exists.
|
||||
// IsExist returns true if a file or directory exists following any symlinks.
|
||||
func IsExist(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil || os.IsExist(err)
|
||||
@@ -36,15 +37,10 @@ func IsExist(path string) bool {
|
||||
|
||||
// IsSymlink returns true if given path is a symbolic link.
|
||||
func IsSymlink(path string) bool {
|
||||
if !IsExist(path) {
|
||||
return false
|
||||
}
|
||||
|
||||
fileInfo, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return fileInfo.Mode()&os.ModeSymlink != 0
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ func reqRepoWriter() macaron.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// reqRepoWriter makes sure the context user has at least admin access to the repository.
|
||||
// reqRepoAdmin makes sure the context user has at least admin access to the repository.
|
||||
func reqRepoAdmin() macaron.Handler {
|
||||
return func(c *context.Context) {
|
||||
if !c.Repo.IsAdmin() {
|
||||
@@ -158,6 +158,16 @@ func reqRepoAdmin() macaron.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// reqRepoOwner makes sure the context user has owner access to the repository.
|
||||
func reqRepoOwner() macaron.Handler {
|
||||
return func(c *context.Context) {
|
||||
if !c.Repo.IsOwner() {
|
||||
c.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mustEnableIssues(c *context.APIContext) {
|
||||
if !c.Repo.Repository.EnableIssues || c.Repo.Repository.EnableExternalTracker {
|
||||
c.NotFound()
|
||||
@@ -250,7 +260,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
|
||||
m.Group("/repos", func() {
|
||||
m.Post("/migrate", bind(form.MigrateRepo{}), repo.Migrate)
|
||||
m.Delete("/:username/:reponame", repoAssignment(), repo.Delete)
|
||||
m.Delete("/:username/:reponame", repoAssignment(), reqRepoOwner(), repo.Delete)
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Group("/hooks", func() {
|
||||
@@ -275,7 +285,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
||||
m.Get("", repo.GetContents)
|
||||
m.Combo("/*").
|
||||
Get(repo.GetContents).
|
||||
Put(bind(repo.PutContentsRequest{}), repo.PutContents)
|
||||
Put(reqRepoWriter(), bind(repo.PutContentsRequest{}), repo.PutContents)
|
||||
})
|
||||
m.Get("/archive/*", repo.GetArchive)
|
||||
m.Group("/git", func() {
|
||||
|
||||
@@ -116,19 +116,28 @@ func listUserRepositories(c *context.APIContext, username string) {
|
||||
return
|
||||
}
|
||||
|
||||
accessibleRepos, err := db.Repos.GetByCollaboratorIDWithAccessMode(c.Req.Context(), user.ID)
|
||||
accessibleReposWithAccessMode, err := db.Repos.GetByCollaboratorIDWithAccessMode(c.Req.Context(), user.ID)
|
||||
if err != nil {
|
||||
c.Error(err, "get repositories accesses by collaborator")
|
||||
return
|
||||
}
|
||||
|
||||
accessibleRepos := make([]*db.Repository, 0, len(accessibleReposWithAccessMode))
|
||||
for repo := range accessibleReposWithAccessMode {
|
||||
accessibleRepos = append(accessibleRepos, repo)
|
||||
}
|
||||
if err = db.RepositoryList(accessibleRepos).LoadAttributes(); err != nil {
|
||||
c.Error(err, "load attributes for accessible repositories")
|
||||
return
|
||||
}
|
||||
|
||||
numOwnRepos := len(ownRepos)
|
||||
repos := make([]*api.Repository, 0, numOwnRepos+len(accessibleRepos))
|
||||
repos := make([]*api.Repository, 0, numOwnRepos+len(accessibleReposWithAccessMode))
|
||||
for _, r := range ownRepos {
|
||||
repos = append(repos, r.APIFormatLegacy(&api.Permission{Admin: true, Push: true, Pull: true}))
|
||||
}
|
||||
|
||||
for repo, access := range accessibleRepos {
|
||||
for repo, access := range accessibleReposWithAccessMode {
|
||||
repos = append(repos,
|
||||
repo.APIFormatLegacy(&api.Permission{
|
||||
Admin: access >= db.AccessModeAdmin,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Code generated by go-mockgen 1.3.7; DO NOT EDIT.
|
||||
// Code generated by go-mockgen 2.1.1; DO NOT EDIT.
|
||||
//
|
||||
// This file was generated by running `go-mockgen` at the root of this repository.
|
||||
// To add additional mocks to this or another package, add a new entry to the
|
||||
@@ -2780,6 +2780,9 @@ type MockTwoFactorsStore struct {
|
||||
// IsEnabledFunc is an instance of a mock function object controlling
|
||||
// the behavior of the method IsEnabled.
|
||||
IsEnabledFunc *TwoFactorsStoreIsEnabledFunc
|
||||
// UseRecoveryCodeFunc is an instance of a mock function object
|
||||
// controlling the behavior of the method UseRecoveryCode.
|
||||
UseRecoveryCodeFunc *TwoFactorsStoreUseRecoveryCodeFunc
|
||||
}
|
||||
|
||||
// NewMockTwoFactorsStore creates a new mock of the TwoFactorsStore
|
||||
@@ -2802,6 +2805,11 @@ func NewMockTwoFactorsStore() *MockTwoFactorsStore {
|
||||
return
|
||||
},
|
||||
},
|
||||
UseRecoveryCodeFunc: &TwoFactorsStoreUseRecoveryCodeFunc{
|
||||
defaultHook: func(context.Context, int64, string) (r0 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2824,6 +2832,11 @@ func NewStrictMockTwoFactorsStore() *MockTwoFactorsStore {
|
||||
panic("unexpected invocation of MockTwoFactorsStore.IsEnabled")
|
||||
},
|
||||
},
|
||||
UseRecoveryCodeFunc: &TwoFactorsStoreUseRecoveryCodeFunc{
|
||||
defaultHook: func(context.Context, int64, string) error {
|
||||
panic("unexpected invocation of MockTwoFactorsStore.UseRecoveryCode")
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2841,6 +2854,9 @@ func NewMockTwoFactorsStoreFrom(i db.TwoFactorsStore) *MockTwoFactorsStore {
|
||||
IsEnabledFunc: &TwoFactorsStoreIsEnabledFunc{
|
||||
defaultHook: i.IsEnabled,
|
||||
},
|
||||
UseRecoveryCodeFunc: &TwoFactorsStoreUseRecoveryCodeFunc{
|
||||
defaultHook: i.UseRecoveryCode,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3168,6 +3184,116 @@ func (c TwoFactorsStoreIsEnabledFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0}
|
||||
}
|
||||
|
||||
// TwoFactorsStoreUseRecoveryCodeFunc describes the behavior when the
|
||||
// UseRecoveryCode method of the parent MockTwoFactorsStore instance is
|
||||
// invoked.
|
||||
type TwoFactorsStoreUseRecoveryCodeFunc struct {
|
||||
defaultHook func(context.Context, int64, string) error
|
||||
hooks []func(context.Context, int64, string) error
|
||||
history []TwoFactorsStoreUseRecoveryCodeFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// UseRecoveryCode delegates to the next hook function in the queue and
|
||||
// stores the parameter and result values of this invocation.
|
||||
func (m *MockTwoFactorsStore) UseRecoveryCode(v0 context.Context, v1 int64, v2 string) error {
|
||||
r0 := m.UseRecoveryCodeFunc.nextHook()(v0, v1, v2)
|
||||
m.UseRecoveryCodeFunc.appendCall(TwoFactorsStoreUseRecoveryCodeFuncCall{v0, v1, v2, r0})
|
||||
return r0
|
||||
}
|
||||
|
||||
// SetDefaultHook sets function that is called when the UseRecoveryCode
|
||||
// method of the parent MockTwoFactorsStore instance is invoked and the hook
|
||||
// queue is empty.
|
||||
func (f *TwoFactorsStoreUseRecoveryCodeFunc) SetDefaultHook(hook func(context.Context, int64, string) error) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||
// UseRecoveryCode method of the parent MockTwoFactorsStore instance invokes
|
||||
// the hook at the front of the queue and discards it. After the queue is
|
||||
// empty, the default hook function is invoked for any future action.
|
||||
func (f *TwoFactorsStoreUseRecoveryCodeFunc) PushHook(hook func(context.Context, int64, string) error) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *TwoFactorsStoreUseRecoveryCodeFunc) SetDefaultReturn(r0 error) {
|
||||
f.SetDefaultHook(func(context.Context, int64, string) error {
|
||||
return r0
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *TwoFactorsStoreUseRecoveryCodeFunc) PushReturn(r0 error) {
|
||||
f.PushHook(func(context.Context, int64, string) error {
|
||||
return r0
|
||||
})
|
||||
}
|
||||
|
||||
func (f *TwoFactorsStoreUseRecoveryCodeFunc) nextHook() func(context.Context, int64, string) error {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
if len(f.hooks) == 0 {
|
||||
return f.defaultHook
|
||||
}
|
||||
|
||||
hook := f.hooks[0]
|
||||
f.hooks = f.hooks[1:]
|
||||
return hook
|
||||
}
|
||||
|
||||
func (f *TwoFactorsStoreUseRecoveryCodeFunc) appendCall(r0 TwoFactorsStoreUseRecoveryCodeFuncCall) {
|
||||
f.mutex.Lock()
|
||||
f.history = append(f.history, r0)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// History returns a sequence of TwoFactorsStoreUseRecoveryCodeFuncCall
|
||||
// objects describing the invocations of this function.
|
||||
func (f *TwoFactorsStoreUseRecoveryCodeFunc) History() []TwoFactorsStoreUseRecoveryCodeFuncCall {
|
||||
f.mutex.Lock()
|
||||
history := make([]TwoFactorsStoreUseRecoveryCodeFuncCall, len(f.history))
|
||||
copy(history, f.history)
|
||||
f.mutex.Unlock()
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// TwoFactorsStoreUseRecoveryCodeFuncCall is an object that describes an
|
||||
// invocation of method UseRecoveryCode on an instance of
|
||||
// MockTwoFactorsStore.
|
||||
type TwoFactorsStoreUseRecoveryCodeFuncCall struct {
|
||||
// Arg0 is the value of the 1st argument passed to this method
|
||||
// invocation.
|
||||
Arg0 context.Context
|
||||
// Arg1 is the value of the 2nd argument passed to this method
|
||||
// invocation.
|
||||
Arg1 int64
|
||||
// Arg2 is the value of the 3rd argument passed to this method
|
||||
// invocation.
|
||||
Arg2 string
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 error
|
||||
}
|
||||
|
||||
// Args returns an interface slice containing the arguments of this
|
||||
// invocation.
|
||||
func (c TwoFactorsStoreUseRecoveryCodeFuncCall) Args() []interface{} {
|
||||
return []interface{}{c.Arg0, c.Arg1, c.Arg2}
|
||||
}
|
||||
|
||||
// Results returns an interface slice containing the results of this
|
||||
// invocation.
|
||||
func (c TwoFactorsStoreUseRecoveryCodeFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0}
|
||||
}
|
||||
|
||||
// MockUsersStore is a mock implementation of the UsersStore interface (from
|
||||
// the package gogs.io/gogs/internal/db) used for unit testing.
|
||||
type MockUsersStore struct {
|
||||
|
||||
@@ -582,13 +582,27 @@ func SettingsGitHooks(c *context.Context) {
|
||||
c.Success(SETTINGS_GITHOOKS)
|
||||
}
|
||||
|
||||
func isValidHookName(name git.HookName) bool {
|
||||
for _, h := range git.ServerSideHooks {
|
||||
if h == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func SettingsGitHooksEdit(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("repo.settings.githooks")
|
||||
c.Data["PageIsSettingsGitHooks"] = true
|
||||
c.Data["RequireSimpleMDE"] = true
|
||||
|
||||
name := c.Params(":name")
|
||||
hook, err := c.Repo.GitRepo.Hook("custom_hooks", git.HookName(name))
|
||||
name := git.HookName(c.Params(":name"))
|
||||
if !isValidHookName(name) {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
hook, err := c.Repo.GitRepo.Hook("custom_hooks", name)
|
||||
if err != nil {
|
||||
c.NotFoundOrError(osutil.NewError(err), "get hook")
|
||||
return
|
||||
@@ -598,8 +612,13 @@ func SettingsGitHooksEdit(c *context.Context) {
|
||||
}
|
||||
|
||||
func SettingsGitHooksEditPost(c *context.Context) {
|
||||
name := c.Params(":name")
|
||||
hook, err := c.Repo.GitRepo.Hook("custom_hooks", git.HookName(name))
|
||||
name := git.HookName(c.Params(":name"))
|
||||
if !isValidHookName(name) {
|
||||
c.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
hook, err := c.Repo.GitRepo.Hook("custom_hooks", name)
|
||||
if err != nil {
|
||||
c.NotFoundOrError(osutil.NewError(err), "get hook")
|
||||
return
|
||||
|
||||
@@ -43,12 +43,13 @@ type PageMeta struct {
|
||||
}
|
||||
|
||||
func renderWikiPage(c *context.Context, isViewPage bool) (*git.Repository, string) {
|
||||
wikiRepo, err := git.Open(c.Repo.Repository.WikiPath())
|
||||
wikiPath := c.Repo.Repository.WikiPath()
|
||||
wikiRepo, err := git.Open(wikiPath)
|
||||
if err != nil {
|
||||
c.Error(err, "open repository")
|
||||
return nil, ""
|
||||
}
|
||||
commit, err := wikiRepo.BranchCommit("master")
|
||||
commit, err := wikiRepo.BranchCommit(db.WikiBranch(wikiPath))
|
||||
if err != nil {
|
||||
c.Error(err, "get branch commit")
|
||||
return nil, ""
|
||||
@@ -124,7 +125,8 @@ func Wiki(c *context.Context) {
|
||||
}
|
||||
|
||||
// Get last change information.
|
||||
commits, err := wikiRepo.Log(git.RefsHeads+"master", git.LogOptions{Path: pageName + ".md"})
|
||||
branch := db.WikiBranch(c.Repo.Repository.WikiPath())
|
||||
commits, err := wikiRepo.Log(git.RefsHeads+branch, git.LogOptions{Path: pageName + ".md"})
|
||||
if err != nil {
|
||||
c.Error(err, "get commits by path")
|
||||
return
|
||||
@@ -143,12 +145,15 @@ func WikiPages(c *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
wikiRepo, err := git.Open(c.Repo.Repository.WikiPath())
|
||||
wikiPath := c.Repo.Repository.WikiPath()
|
||||
wikiRepo, err := git.Open(wikiPath)
|
||||
if err != nil {
|
||||
c.Error(err, "open repository")
|
||||
return
|
||||
}
|
||||
commit, err := wikiRepo.BranchCommit("master")
|
||||
|
||||
branch := db.WikiBranch(wikiPath)
|
||||
commit, err := wikiRepo.BranchCommit(branch)
|
||||
if err != nil {
|
||||
c.Error(err, "get branch commit")
|
||||
return
|
||||
@@ -162,7 +167,7 @@ func WikiPages(c *context.Context) {
|
||||
pages := make([]PageMeta, 0, len(entries))
|
||||
for i := range entries {
|
||||
if entries[i].Type() == git.ObjectBlob && strings.HasSuffix(entries[i].Name(), ".md") {
|
||||
commits, err := wikiRepo.Log(git.RefsHeads+"master", git.LogOptions{Path: entries[i].Name()})
|
||||
commits, err := wikiRepo.Log(git.RefsHeads+branch, git.LogOptions{Path: entries[i].Name()})
|
||||
if err != nil {
|
||||
c.Error(err, "get commits by path")
|
||||
return
|
||||
|
||||
@@ -267,7 +267,7 @@ func LoginTwoFactorRecoveryCodePost(c *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.UseRecoveryCode(userID, c.Query("recovery_code")); err != nil {
|
||||
if err := db.TwoFactors.UseRecoveryCode(c.Req.Context(), userID, c.Query("recovery_code")); err != nil {
|
||||
if db.IsTwoFactorRecoveryCodeNotFound(err) {
|
||||
c.Flash.Error(c.Tr("auth.login_two_factor_invalid_recovery_code"))
|
||||
c.RedirectSubpath("/user/login/two_factor_recovery_code")
|
||||
|
||||
2811
public/plugins/mermaid-11.12.1/mermaid.min.js
vendored
Normal file
2811
public/plugins/mermaid-11.12.1/mermaid.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
3
public/plugins/mermaid-8.14.0/mermaid.min.js
vendored
3
public/plugins/mermaid-8.14.0/mermaid.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -59,7 +59,7 @@
|
||||
<script src="{{AppSubURL}}/plugins/autosize-4.0.2/autosize.min.js"></script>
|
||||
{{end}}
|
||||
{{if .IsMarkdown}}
|
||||
<script src="{{AppSubURL}}/plugins/mermaid-8.14.0/mermaid.min.js"></script>
|
||||
<script src="{{AppSubURL}}/plugins/mermaid-11.12.1/mermaid.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
mermaid.init({startOnLoad: true, noteMargin: 10}, ".language-mermaid");
|
||||
|
||||
Reference in New Issue
Block a user