diff --git a/.drone.yml b/.drone.yml index 4a88427eee..c07179b95d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,452 +1,671 @@ +--- +kind: pipeline +name: testing + +platform: + os: linux + arch: amd64 + workspace: base: /go path: src/code.gitea.io/gitea -pipeline: - fetch-tags: +services: + - name: mysql + pull: default + image: mysql:5.7 + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: test + + - name: mysql8 + pull: default + image: mysql:8.0 + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: testgitea + + - name: pgsql + pull: default + image: postgres:9.5 + environment: + POSTGRES_DB: test + + - name: mssql + pull: default + image: microsoft/mssql-server-linux:latest + environment: + ACCEPT_EULA: Y + MSSQL_PID: Standard + SA_PASSWORD: MwantsaSecurePassword1 + + - name: ldap + pull: default + image: gitea/test-openldap:latest + +steps: + - name: fetch-tags + pull: default + image: docker:git + commands: + - git fetch --tags --force + when: + event: + exclude: + - pull_request + + - name: pre-build + pull: always + image: webhippie/nodejs:latest + commands: + - make css + - make js + + - name: build-without-gcc + pull: always + image: golang:1.11 # this step is kept as the lowest version of golang that we support + environment: + GO111MODULE: on + commands: + - go build -mod=vendor -o gitea_no_gcc # test if build succeeds without the sqlite tag + + - name: build + pull: always + image: golang:1.12 + commands: + - make clean + - make generate + - make golangci-lint + - make revive + - make swagger-check + - make swagger-validate + - make test-vendor + - make build + environment: + TAGS: bindata sqlite sqlite_unlock_notify + + - name: unit-test + pull: always + image: golang:1.12 + commands: + - make unit-test-coverage + environment: + TAGS: bindata sqlite sqlite_unlock_notify + depends_on: + - build + when: + branch: + - master + event: + - push + - pull_request + + - name: release-test + pull: always + image: golang:1.12 + commands: + - make test + environment: + TAGS: bindata sqlite sqlite_unlock_notify + depends_on: + - build + when: + branch: + - "release/*" + event: + - push + - pull_request + + - name: tag-pre-condition + pull: always + image: alpine/git + commands: + - git update-ref refs/heads/tag_test ${DRONE_COMMIT_SHA} + depends_on: + - build + when: + event: + - tag + + - name: tag-test + pull: always + image: golang:1.12 + commands: + - make test + environment: + TAGS: bindata + depends_on: + - tag-pre-condition + when: + event: + - tag + + - name: test-sqlite + pull: always + image: golang:1.12 + commands: + - "curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash" + - apt-get install -y git-lfs + - timeout -s ABRT 20m make test-sqlite-migration + - timeout -s ABRT 20m make test-sqlite + environment: + TAGS: bindata + depends_on: + - build + + - name: test-mysql + pull: always + image: golang:1.12 + commands: + - "curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash" + - apt-get install -y git-lfs + - make test-mysql-migration + - make integration-test-coverage + environment: + TAGS: bindata + TEST_LDAP: 1 + depends_on: + - build + when: + branch: + - master + event: + - push + - pull_request + + - name: tag-test-mysql + pull: always + image: golang:1.12 + commands: + - "curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash" + - apt-get install -y git-lfs + - timeout -s ABRT 20m make test-mysql-migration + - timeout -s ABRT 20m make test-mysql + environment: + TAGS: bindata + TEST_LDAP: 1 + depends_on: + - build + when: + event: + - tag + + - name: test-mysql8 + pull: always + image: golang:1.12 + commands: + - "curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash" + - apt-get install -y git-lfs + - timeout -s ABRT 20m make test-mysql8-migration + - timeout -s ABRT 20m make test-mysql8 + environment: + TAGS: bindata + TEST_LDAP: 1 + depends_on: + - build + + - name: test-pgsql + pull: always + image: golang:1.12 + commands: + - "curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash" + - apt-get install -y git-lfs + - timeout -s ABRT 20m make test-pgsql-migration + - timeout -s ABRT 20m make test-pgsql + environment: + TAGS: bindata + TEST_LDAP: 1 + depends_on: + - build + + - name: test-mssql + pull: always + image: golang:1.12 + commands: + - "curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash" + - apt-get install -y git-lfs + - make test-mssql-migration + - make test-mssql + environment: + TAGS: bindata + TEST_LDAP: 1 + depends_on: + - build + + - name: generate-coverage + pull: always + image: golang:1.12 + commands: + - make coverage + environment: + TAGS: bindata + depends_on: + - unit-test + - test-mysql + when: + branch: + - master + event: + - push + - pull_request + + - name: coverage + pull: always + image: robertstettner/drone-codecov + settings: + files: + - coverage.all + environment: + CODECOV_TOKEN: + from_secret: codecov_token + depends_on: + - generate-coverage + when: + branch: + - master + event: + - push + - pull_request + +--- +kind: pipeline +name: translations + +platform: + os: linux + arch: amd64 + +workspace: + base: /go + path: src/code.gitea.io/gitea + +trigger: + branch: + - master + event: + - push + +steps: + - name: download + pull: always + image: jonasfranz/crowdin + settings: + download: true + export_dir: options/locale/ + ignore_branch: true + project_identifier: gitea + environment: + CROWDIN_KEY: + from_secret: crowdin_key + + - name: update + pull: default + image: alpine:3.10 + commands: + - mv ./options/locale/locale_en-US.ini ./options/ + - "sed -i -e 's/=\"/=/g' -e 's/\"$$//g' ./options/locale/*.ini" + - "sed -i -e 's/\\\\\\\\\"/\"/g' ./options/locale/*.ini" + - mv ./options/locale_en-US.ini ./options/locale/ + + - name: push + pull: always + image: appleboy/drone-git-push + settings: + author_email: "teabot@gitea.io" + author_name: GiteaBot + commit: true + commit_message: "[skip ci] Updated translations via Crowdin" + remote: "git@github.com:go-gitea/gitea.git" + environment: + GIT_PUSH_SSH_KEY: + from_secret: git_push_ssh_key + + - name: upload_translations + pull: always + image: jonasfranz/crowdin + settings: + files: + locale_en-US.ini: options/locale/locale_en-US.ini + ignore_branch: true + project_identifier: gitea + environment: + CROWDIN_KEY: + from_secret: crowdin_key + +--- +kind: pipeline +name: release-master + +platform: + os: linux + arch: amd64 + +workspace: + base: /go + path: src/code.gitea.io/gitea + +trigger: + branch: + - master + - "release/*" + event: + - push + +depends_on: + - testing + - translations + +steps: + - name: fetch-tags + pull: default image: docker:git commands: - git fetch --tags --force - download_translations: - image: jonasfranz/crowdin - pull: true - secrets: [ crowdin_key ] - project_identifier: gitea - ignore_branch: true - download: true - export_dir: options/locale/ - when: - event: [ push ] - branch: [ master ] - - update-translations: - image: alpine:3.7 - commands: - - mv ./options/locale/locale_en-US.ini ./options/ - - sed -i -e 's/="/=/g' -e 's/"$$//g' ./options/locale/*.ini - - sed -i -e 's/\\\\"/"/g' ./options/locale/*.ini - - mv ./options/locale_en-US.ini ./options/locale/ - when: - event: [ push ] - branch: [ master ] - - git_push: - image: appleboy/drone-git-push - pull: true - secrets: [ git_push_ssh_key ] - remote: git@github.com:go-gitea/gitea.git - force: false - commit: true - commit_message: "[skip ci] Updated translations via Crowdin" - author_name: GiteaBot - author_email: teabot@gitea.io - when: - event: [ push ] - branch: [ master ] - - pre-build: - image: webhippie/nodejs:latest - pull: true - commands: - - npm install - - make stylesheets-check - when: - event: [ push, tag, pull_request ] - - build-without-gcc: - image: golang:1.10 # this step is kept as the lowest version of golang that we support - pull: true - commands: - - go build -o gitea_no_gcc # test if build succeeds without the sqlite tag - when: - event: [ push, tag, pull_request ] - - build: - image: golang:1.12 - pull: true - environment: - TAGS: bindata sqlite sqlite_unlock_notify - commands: - - make clean - - make generate - - make vet - - make lint - - make fmt-check - - make swagger-check - - make swagger-validate - - make misspell-check - - make test-vendor - - make build - when: - event: [ push, tag, pull_request ] - - unit-test: - image: golang:1.12 - pull: true - group: test - environment: - TAGS: bindata sqlite sqlite_unlock_notify - commands: - - make unit-test-coverage - when: - event: [ push, pull_request ] - branch: [ master ] - - release-test: - image: golang:1.12 - pull: true - group: test - environment: - TAGS: bindata sqlite sqlite_unlock_notify - commands: - - make test - when: - event: [ push, pull_request ] - branch: [ release/* ] - - tag-test: - image: golang:1.12 - pull: true - group: test - environment: - TAGS: bindata - commands: - - make test - when: - event: [ tag ] - - test-sqlite: - image: golang:1.12 - pull: true - group: test - environment: - TAGS: bindata - commands: - - curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash - - apt-get install -y git-lfs - - (sleep 1200 && (echo 'kill -ABRT $(pidof gitea) $(pidof integrations.sqlite.test)' | sh)) & - - make test-sqlite-migration - - make test-sqlite - when: - event: [ push, tag, pull_request ] - - test-mysql: - image: golang:1.12 - pull: true - group: test - environment: - TAGS: bindata - TEST_LDAP: "1" - commands: - - curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash - - apt-get install -y git-lfs - - make test-mysql-migration - - make integration-test-coverage - when: - event: [ push, pull_request ] - branch: [ master ] - - tag-test-mysql: - image: golang:1.12 - pull: true - group: test - environment: - TAGS: bindata - TEST_LDAP: "1" - commands: - - curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash - - apt-get install -y git-lfs - - (sleep 1200 && (echo 'kill -ABRT $(pidof gitea) $(pidof integrations.test)' | sh)) & - - make test-mysql-migration - - make test-mysql - when: - event: [ tag ] - - test-mysql8: - image: golang:1.11 - pull: true - group: test - environment: - TAGS: bindata - TEST_LDAP: "1" - commands: - - curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash - - apt-get install -y git-lfs - - (sleep 1200 && (echo 'kill -ABRT $(pidof gitea) $(pidof integrations.test)' | sh)) & - - make test-mysql8-migration - - make test-mysql8 - when: - event: [ push, tag, pull_request ] - - test-pgsql: - image: golang:1.12 - pull: true - group: test - environment: - TAGS: bindata - TEST_LDAP: "1" - commands: - - curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash - - apt-get install -y git-lfs - - (sleep 1200 && (echo 'kill -ABRT $(pidof gitea) $(pidof integrations.test)' | sh)) & - - make test-pgsql-migration - - make test-pgsql - when: - event: [ push, tag, pull_request ] - - test-mssql: - image: golang:1.12 - pull: true - group: test - environment: - TAGS: bindata - TEST_LDAP: "1" - commands: - - curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash - - apt-get install -y git-lfs - - make test-mssql-migration - - make test-mssql - when: - event: [ push, tag, pull_request ] - -# bench-sqlite: -# image: golang:1.12 -# pull: true -# group: bench -# commands: -# - make bench-sqlite -# when: -# event: [ tag ] - -# bench-mysql: -# image: golang:1.12 -# pull: true -# group: bench -# commands: -# - make bench-mysql -# when: -# event: [ tag ] - -# bench-mssql: -# image: golang:1.12 -# pull: true -# group: bench -# commands: -# - make bench-mssql -# when: -# event: [ tag ] - -# bench-pgsql: -# image: golang:1.12 -# pull: true -# group: bench -# commands: -# - make bench-pgsql -# when: -# event: [ tag ] - - generate-coverage: - image: golang:1.12 - pull: true - environment: - TAGS: bindata - commands: - - make coverage - when: - event: [ push, pull_request ] - branch: [ master ] - - coverage: - image: robertstettner/drone-codecov - secrets: [ codecov_token ] - files: - - coverage.all - when: - event: [ push, pull_request ] - branch: [ master ] - - static: + - name: static + pull: always image: techknowlogick/xgo:latest - pull: true - environment: - TAGS: bindata sqlite sqlite_unlock_notify commands: - export PATH=$PATH:$GOPATH/bin + - make generate - make release - when: - event: [ push, tag ] + environment: + TAGS: bindata sqlite sqlite_unlock_notify - build-docs: + - name: gpg-sign + pull: always + image: plugins/gpgsign:1 + settings: + detach_sign: true + excludes: + - "dist/release/*.sha256" + files: + - "dist/release/*" + environment: + GPGSIGN_KEY: + from_secret: gpgsign_key + GPGSIGN_PASSPHRASE: + from_secret: gpgsign_passphrase + depends_on: + - static + + - name: release-branch-release + pull: always + image: plugins/s3:1 + settings: + acl: public-read + bucket: releases + endpoint: https://storage.gitea.io + path_style: true + source: "dist/release/*" + strip_prefix: dist/release/ + target: "/gitea/${DRONE_BRANCH##release/v}" + environment: + AWS_ACCESS_KEY_ID: + from_secret: aws_access_key_id + AWS_SECRET_ACCESS_KEY: + from_secret: aws_secret_access_key + depends_on: + - gpg-sign + when: + branch: + - "release/*" + event: + - push + + - name: release + pull: always + image: plugins/s3:1 + settings: + acl: public-read + bucket: releases + endpoint: https://storage.gitea.io + path_style: true + source: "dist/release/*" + strip_prefix: dist/release/ + target: /gitea/master + environment: + AWS_ACCESS_KEY_ID: + from_secret: aws_access_key_id + AWS_SECRET_ACCESS_KEY: + from_secret: aws_secret_access_key + depends_on: + - gpg-sign + when: + branch: + - master + event: + - push + +--- +kind: pipeline +name: release-version + +platform: + os: linux + arch: amd64 + +workspace: + base: /go + path: src/code.gitea.io/gitea + +trigger: + event: + - tag + +depends_on: + - testing + - translations + +steps: + - name: fetch-tags + pull: default + image: docker:git + commands: + - git fetch --tags --force + + - name: static + pull: always + image: techknowlogick/xgo:latest + commands: + - export PATH=$PATH:$GOPATH/bin + - make generate + - make release + environment: + TAGS: bindata sqlite sqlite_unlock_notify + + - name: gpg-sign + pull: always + image: plugins/gpgsign:1 + settings: + detach_sign: true + excludes: + - "dist/release/*.sha256" + files: + - "dist/release/*" + environment: + GPGSIGN_KEY: + from_secret: gpgsign_key + GPGSIGN_PASSPHRASE: + from_secret: gpgsign_passphrase + depends_on: + - static + + - name: release + pull: always + image: plugins/s3:1 + settings: + acl: public-read + bucket: releases + endpoint: https://storage.gitea.io + path_style: true + source: "dist/release/*" + strip_prefix: dist/release/ + target: "/gitea/${DRONE_TAG##v}" + environment: + AWS_ACCESS_KEY_ID: + from_secret: aws_access_key_id + AWS_SECRET_ACCESS_KEY: + from_secret: aws_secret_access_key + depends_on: + - gpg-sign + + - name: github + pull: always + image: plugins/github-release:1 + settings: + files: + - "dist/release/*" + environment: + GITHUB_TOKEN: + from_secret: github_token + depends_on: + - gpg-sign + +--- +kind: pipeline +name: docs + +platform: + os: linux + arch: amd64 + +workspace: + base: /go + path: src/code.gitea.io/gitea + +steps: + - name: build-docs + pull: always image: webhippie/hugo:latest - pull: true commands: - cd docs - make trans-copy - make clean - make build - publish-docs: + - name: publish-docs + pull: always image: lucap/drone-netlify:latest - pull: true - secrets: [ netlify_token ] - site_id: d2260bae-7861-4c02-8646-8f6440b12672 - path: docs/public/ + settings: + path: docs/public/ + site_id: d2260bae-7861-4c02-8646-8f6440b12672 + environment: + NETLIFY_TOKEN: + from_secret: netlify_token when: - event: [ push ] - branch: [ master ] + branch: + - master + event: + - push - docker-dryrun: - image: plugins/docker:17.12 - pull: true - repo: gitea/gitea - cache_from: gitea/gitea - dry_run: true +--- +kind: pipeline +name: docker + +platform: + os: linux + arch: amd64 + +workspace: + base: /go + path: src/code.gitea.io/gitea + +steps: + - name: fetch-tags + pull: default + image: docker:git + commands: + - git fetch --tags --force when: - event: [ pull_request ] + event: + exclude: + - pull_request - release-docker: - image: plugins/docker:17.12 - pull: true - secrets: [ docker_username, docker_password ] - repo: gitea/gitea - tags: [ '${DRONE_BRANCH##release/v}' ] - cache_from: gitea/gitea + - name: dryrun + pull: always + image: plugins/docker:18.09 + settings: + cache_from: gitea/gitea + dry_run: true + repo: gitea/gitea when: - event: [ push ] - branch: [ release/* ] + event: + - pull_request - docker: - image: plugins/docker:17.12 - secrets: [ docker_username, docker_password ] - pull: true - repo: gitea/gitea - cache_from: gitea/gitea - default_tags: true + - name: release + pull: always + image: plugins/docker:18.09 + settings: + cache_from: gitea/gitea + repo: gitea/gitea + tags: + - "${DRONE_BRANCH##release/v}" + environment: + DOCKER_PASSWORD: + from_secret: docker_password + DOCKER_USERNAME: + from_secret: docker_username + depends_on: + - dryrun when: - event: [ push, tag ] + branch: + - "release/*" + event: + - push - gpg-sign: - image: plugins/gpgsign:1 - pull: true - secrets: [ gpgsign_key, gpgsign_passphrase ] - detach_sign: true - files: - - dist/release/* - excludes: - - dist/release/*.sha256 + - name: latest + pull: always + image: plugins/docker:18.09 + settings: + cache_from: gitea/gitea + default_tags: true + repo: gitea/gitea + environment: + DOCKER_PASSWORD: + from_secret: docker_password + DOCKER_USERNAME: + from_secret: docker_username + depends_on: + - dryrun when: - event: [ push, tag ] + branch: + - master + event: + - push + - tag - tag-release: - image: plugins/s3:1 - pull: true - secrets: [ aws_access_key_id, aws_secret_access_key ] - bucket: releases - acl: public-read - endpoint: https://storage.gitea.io - path_style: true - strip_prefix: dist/release/ - source: dist/release/* - target: /gitea/${DRONE_TAG##v} - when: - event: [ tag ] +--- +kind: pipeline +name: notify - release-branch-release: - image: plugins/s3:1 - pull: true - secrets: [ aws_access_key_id, aws_secret_access_key ] - bucket: releases - acl: public-read - endpoint: https://storage.gitea.io - path_style: true - strip_prefix: dist/release/ - source: dist/release/* - target: /gitea/${DRONE_BRANCH##release/v} - when: - event: [ push ] - branch: [ release/* ] +platform: + os: linux + arch: amd64 - release: - image: plugins/s3:1 - pull: true - secrets: [ aws_access_key_id, aws_secret_access_key ] - bucket: releases - acl: public-read - endpoint: https://storage.gitea.io - path_style: true - strip_prefix: dist/release/ - source: dist/release/* - target: /gitea/master - when: - event: [ push ] - branch: [ master ] +workspace: + base: /go + path: src/code.gitea.io/gitea - github: - image: plugins/github-release:1 - pull: true - secrets: [ github_token ] - files: - - dist/release/* - when: - event: [ tag ] +when: + status: + - success + - failure - upload_translations: - image: jonasfranz/crowdin - pull: true - secrets: [ crowdin_key ] - project_identifier: gitea - ignore_branch: true - download: false - files: - locale_en-US.ini: options/locale/locale_en-US.ini - when: - event: [ push ] - branch: [ master ] +depends_on: + - testing + - translations + - release-version + - release-master + - docker + - docs - discord: +steps: + - name: discord + pull: always image: appleboy/drone-discord:1.0.0 - pull: true - secrets: [ discord_webhook_id, discord_webhook_token ] - when: - event: [ push, tag, pull_request ] - status: [ changed, failure ] - -services: - mysql: - image: mysql:5.7 environment: - - MYSQL_DATABASE=test - - MYSQL_ALLOW_EMPTY_PASSWORD=yes - when: - event: [ push, tag, pull_request ] - - mysql8: - image: mysql:8.0 - environment: - - MYSQL_DATABASE=test - - MYSQL_ALLOW_EMPTY_PASSWORD=yes - - MYSQL_DATABASE=testgitea - when: - event: [ push, tag, pull_request ] - - pgsql: - image: postgres:9.5 - environment: - - POSTGRES_DB=test - when: - event: [ push, tag, pull_request ] - - mssql: - image: microsoft/mssql-server-linux:latest - environment: - - ACCEPT_EULA=Y - - SA_PASSWORD=MwantsaSecurePassword1 - - MSSQL_PID=Standard - when: - event: [ push, tag, pull_request ] - - ldap: - image: gitea/test-openldap:latest - when: - event: [ push, tag, pull_request ] + DISCORD_WEBHOOK_ID: + from_secret: discord_webhook_id + DISCORD_WEBHOOK_TOKEN: + from_secret: discord_webhook_token diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000000..41cad889b1 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,25 @@ +root: true + +extends: + - eslint:recommended + +parserOptions: + ecmaVersion: 2015 + +env: + browser: true + jquery: true + es6: true + +globals: + Clipboard: false + CodeMirror: false + emojify: false + SimpleMDE: false + Vue: false + Dropzone: false + u2fApi: false + hljs: false + +rules: + no-unused-vars: [error, {args: all, argsIgnorePattern: ^_, varsIgnorePattern: ^_, ignoreRestSiblings: true}] diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..1447a6ea32 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +open_collective: gitea diff --git a/.gitignore b/.gitignore index a66bf9cff6..fa6cbb454b 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,7 @@ coverage.all /node_modules /modules/indexer/issues/indexers routers/repo/authorized_keys +/yarn.lock # Snapcraft snap/.snapcraft/ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000000..82d0e46694 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,97 @@ +linters: + enable: + - gosimple + - deadcode + - typecheck + - govet + - errcheck + - staticcheck + - unused + - structcheck + - varcheck + - golint + - dupl + #- gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time. + - gofmt + - misspell + - gocritic + enable-all: false + disable-all: true + fast: false + +linters-settings: + gocritic: + disabled-checks: + - ifElseChain + - singleCaseSwitch # Every time this occured in the code, there was no other way. + +issues: + exclude-rules: + # Exclude some linters from running on tests files. + - path: _test\.go + linters: + - gocyclo + - errcheck + - dupl + - gosec + - unparam + - staticcheck + - path: models/migrations/v + linters: + - gocyclo + - errcheck + - dupl + - gosec + - linters: + - dupl + text: "webhook" + - linters: + - gocritic + text: "`ID' should not be capitalized" + - path: modules/templates/helper.go + linters: + - gocritic + - linters: + - unused + - deadcode + text: "swagger" + - path: contrib/pr/checkout.go + linters: + - errcheck + - path: models/issue.go + linters: + - errcheck + - path: models/migrations/ + linters: + - errcheck + - path: modules/log/ + linters: + - errcheck + - path: routers/routes/routes.go + linters: + - dupl + - path: routers/repo/view.go + linters: + - dupl + - path: models/migrations/ + linters: + - unused + - linters: + - staticcheck + text: "argument x is overwritten before first use" + - path: modules/httplib/httplib.go + linters: + - staticcheck + # Enabling this would require refactoring the methods and how they are called. + - path: models/issue_comment_list.go + linters: + - dupl + # "Destroy" is misspelled in github.com/go-macaron/session/session.go:213 so it's not our responsability to fix it + - path: modules/session/virtual.go + linters: + - misspell + text: '`Destory` is a misspelling of `Destroy`' + - path: modules/session/memory.go + linters: + - misspell + text: '`Destory` is a misspelling of `Destroy`' diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..cffe8cdef1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +save-exact=true diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 0000000000..a3eb6c3fb5 --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,11 @@ +extends: stylelint-config-standard + +rules: + block-closing-brace-empty-line-before: null + color-hex-length: null + comment-empty-line-before: null + declaration-empty-line-before: null + indentation: 4 + no-descending-specificity: null + rule-empty-line-before: null + selector-pseudo-element-colon-notation: null diff --git a/CHANGELOG.md b/CHANGELOG.md index 361ec20546..790263d641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,349 @@ This changelog goes through all the changes that have been made in each release without substantial changes to our git log; to see the highlights of what has been added to each release, please refer to the [blog](https://blog.gitea.io). +## [1.9.0-RC1](https://github.com/go-gitea/gitea/releases/tag/v1.9.0-rc1) - 2019-07-06 +* BREAKING + * Better logging (#6038) (#6095) +* FEATURE + * Content API for Creating, Updating, Deleting Files (#6314) + * Enable tls-alpn-01: Use certmanager provided TLSConfig for LetsEncrypt (#7229) + * Add command to convert mysql database from utf8 to utf8mb4 (#7144) + * Fixes #2738 - Adds the /git/tags API endpoint (#7138) + * Compare branches, commits and tags with each other (#6991) + * Show Pull Request button or status of latest PR in branch list (#6990) + * Repository avatars (#6986) + * Show git-notes (#6984) + * Add commit statuses reports on pull request view (#6845) + * Number of commits ahead/behind in branch overview (#6695) + * Add CLI commands to manage LDAP authentication source (#6681) + * Add support for MS Teams webhooks (#6632) + * OAuth2 Grant UI (#6625) + * Add SUBJECT_PREFIX mailer config option (#6605) + * Include custom configuration file in dump (#6516) + * Add API for manipulating Git hooks (#6436) + * Improve migrations to support migrating milestones/labels/issues/comments/pullrequests (#6290) + * Add option to blame files (#5721) + * Implement Default Webhooks (#4299) + * Telegram webhook (#4227) +* BUGFIXES + * Correctly adjust mirror url (#6593) + * Handle early git version's lack of get-url (#7065) + * Fix icon position in issue view (#7354) + * Cut timeline length with last element on issue view (#7355) + * Fix mirror repository webhooks (#7366) + * Fix api route for hooks (#7346) + * Fix bug conflict between SyncReleasesWithTags and InsertReleases (#7337) + * Fix pull view ui merge section (#7335) + * Fix 7303 - remove unnessesary buttons on archived repos (#7326) + * Fix topic bar to allow prefixes (#7325) + * Fixes #7152 - Allow create/update/delete message to be empty, use default message (#7324) + * Fixes #7238 - Annotated tag commit ID incorrect (#7321) + * Dark theme fixes (#7319) + * Gitea own dark codemirror theme (#7317) + * Fixes #7292 - API File Contents bug (#7301) + * Fix API link header (#7298) + * Fix extra newlines when copying from diff in Firefox (#7288) + * Make diff line-marker non-selectable (#7279) + * Fix Submodule dection in subdir (#7275) + * Fix error log when loading issues caused by a xorm bug (#7271) + * Add .fa icon margin like .octicon (#7258) + * Fix hljs unintenionally highlighting commit links (#7244) + * Only check and config git on web subcommand but not others (#7236) + * Fix migration panic when Head.User is not exist (#7226) + * Only warn on errors in deleting LFS orphaned files during repo deletion (#7213) + * Fix duplicated file on pull request conflicted files (#7211) + * Allow colon between fixing word and issue (#7207) + * Fix overflow issues in repo (#7190) + * API error cleanup (#7186) + * Add error for fork already existing (#7185) + * Fixes diff on merged pull requests (#7171) + * If milestone id is zero don't get it from database (#7169) + * Fix pusher name via ssh push (#7167) + * Fix database lock when use random repository fallback image (#7166) + * Various fixes for issue mail notifications (#7165) + * Allow archived repos to be (un)starred and (un)watched (#7163) + * Fix GCArgs load from ini (#7156) + * Detect noreply email address as user (#7133) + * Avoid arbitrary format strings upon calling fail() function (#7112) + * Validate External Tracker URL Format (#7089) + * Repository avatar fallback configuration (#7087) + * Fix #732: Add LFS objects to base repository on merging (#7082) + * Install page - Handle invalid administrator username better (#7060) + * Workaround for posting single comments in split diff view (#7052) + * Fix possbile mysql invalid connnection error (#7051) + * Fix charset was not saved after installation finished (#7048) + * Handle insecure and ports in go get (#7041) + * Avoid bad database state after failed migration (#7040) + * Fix wrong init dependency on markup extensions (#7038) + * Fix default for allowing new organization creation for new users (#7017) + * Fix content download and /verify LFS handler expecting wrong content-type (#7015) + * Fix missing repo description when migrating (#7000) + * Fix LFS Locks over SSH (#6999) + * Do not attempt to return blob on submodule (#6996) + * Fix U2F for Chrome >= 74 (#6980) + * Fix index produces problem when issues/pulls deleted (#6973) + * Allow collaborators to view repo owned by private org (#6965) + * Stop running hooks on pr merge (#6963) + * Run hooks on merge/edit and cope with protected branches (#6961) + * Webhook Logs show proper HTTP Method, and allow change HTTP method in form (#6953) + * Stop colorizing log files by default (#6949) + * Rotate serv.log, http.log and hook logs and stop stacktracing in these (#6935) + * Fix plain text overflow line wrap (#6915) + * Fix input size for dependency select (#6913) + * Change drone token name to let users know to use oauth2 (#6912) + * Fix syntax highlight in blame view #6895 (#6909) + * Use AppURL for Oauth user link (#6894) + * Fixes #6881 - API users search fix (#6882) + * Fix 404 when send pull request some situation (#6871) + * Enforce osusergo build tag for releases (#6862) + * Fix 500 when reviewer is deleted with integration tests (#6856) + * Fix v85.go (#6851) + * Make dropTableColumns drop columns on sqlite and constraints on all (#6849) + * Fix double-generation of scratch token (#6832) (#6833) + * When mirroring we should set the remote to mirror (#6824) + * Fix the v78 migration "Drop is_bare" on MSSQL #6707 (#6823) + * Change verbose flag in dump command to avoid colliding with global version flag (#6822) + * Fix #6813: Allow git.GetTree to take both commit and tree names (#6816) + * Remove `seen` map from `getLastCommitForPaths` (#6807) + * Show scrollbar only when needed (#6802) + * Restore IsWindows variable assignment (#6722) (#6790) + * Service worker js is a missing comma (#6788) + * Fix team edit API panic (#6780) + * Set user search base field optional in LDAP (simple auth) edit page (#6779) + * Ignore already existing public keys after ldap sync (#6766) + * Fix pulls broken when fork repository deleted (#6754) + * Fix missing return (#6751) + * Fix new team 500 (#6749) + * OAuth2 token can be used in basic auth (#6747) + * Fix org visibility bug when git cloning (#6743) + * Fix bug when sort repos on org home page login with non-admin (#6741) + * Stricter domain name pattern in email regex (#6739) + * Fix admin template error (#6737) + * Drop is_bare IDX only when it exists for MySQL and MariaDB (#6736) + * UI: Detect and restore encoding and BOM in content (#6727) + * Load issue attributes when editing an issue with API (#6723) + * Fix team members API (#6714) + * Unfortunately MemProvider Init does not actually Init properly (#6692) + * Fix partial reversion of #6657 caused by #6314 (#6685) + * Prevent creating empty sessions (#6677) + * Fixes #6659 - Swagger schemes selection default to page's protocol (#6660) + * Update highlight.js to 9.15.6 (#6658) + * Properly escape on the redirect from the web editor (#6657) + * Fix #6655 - Don't EscapePound .Link as it is already escaped (#6656) + * Use ctx.metas for SHA hash links (#6645) + * Fix wrong GPG expire date (#6643) + * upgrade version of lib/pq to v1.1.0 (#6640) + * Fix forking an empty repository (#6637) + * Fix issuer of OTP URI should be URI-encoded. (#6634) + * Return a UserList from /api/v1/admin/users (#6629) + * Add json tags for oauth2 form (#6627) + * Remove extra slash from twitter card (#6619) + * remove bash requirement in makefile (#6617) + * Fix Open Graph og:image link (#6612) + * Fix cross-compile builds (#6609) + * Change commit summary to full message in API (#6591) + * Fix bug user search API pagesize didn't obey ExplorePagingNum (#6579) + * Prevent server 500 on compare branches with no common history (#6555) + * Properly escape release attachment URL (#6512) + * Delete local branch when repo branch is deleted (#6497) + * Fix bug when user login and want to resend register confirmation email (#6482) + * Fix upload attachments (#6481) + * Avoid multi-clicks in oauth2 login (#6467) + * Hacky fix for alignment of the create-organization dialog (#6455) + * Change order that PostProcess Processors are run (#6445) + * Clean up ref name rules (#6437) + * Fix Hook & HookList in Swagger (#6432) + * Fixed unitTypeCode not being used in accessLevelUnit (#6419) + * Display correct error for invalid mirror interval (#6414) + * Don't Unescape redirect_to cookie value (#6399) + * Fix dump table name error and add some test for dump database (#6394) + * Fix migrations 82 to ignore unsynced tags between database and git data and missing is_archived on repository table (#6387) + * Make sure units of a team are returned (#6379) + * Fix bug manifest.json will not request with cookie so that session will created every request (#6372) + * Disable benchmarking during tag events on DroneIO (#6365) + * Comments list performance optimization (#5305) +* ENHANCEMENT + * Add API Endpoint for Repo Edit (#7006) + * Add state param to milestone listing API (#7131) + * Make captcha and password optional for external accounts (#6606) + * Detect migrating batch size (#7353) + * Fix 7255 - wrap long texts on user profile info (#7333) + * Use commit graph files for listing pages (#7314) + * Add git command line commitgraph support global default true when git version >= 2.18 (#7313) + * Add LFS_START_SERVER option to control git-lfs support (#7281) + * Dark theme markdown fixes (#7260) + * Update go-git to v4.12.0 (#7249) + * Show lfs config on admin panel (#7220) + * Disable same user check for internal SSH (#7215) + * Add LastLogin to the User API (#7196) + * Add missing description of label on API (#7159) + * Use go method to calculate ssh key fingerprint (#7128) + * Enable Rust highlighting (#7125) + * Refactor submodule URL parsing (#7100) + * Change issue mail title. (#7064) + * Use batch insert on migrating repository to make the process faster (#7050) + * Improve github downloader on migrations (#7049) + * When git version >= 2.18, git command could run with git wire protocol version 2 param if enabled (#7047) + * Fix Erlang and Elixir highlight mappings (#7044) + * API Org Visibility (#7028) + * Improve handling of non-square avatars (#7025) + * Bugfix: Align comment label and actions to the right (#7024) + * Change UpdateRepoIndex api to include watchers (#7012) + * Move serv hook functionality & drop GitLogger (#6993) + * Add support of utf8mb4 for mysql (#6992) + * Make webhook http connections resuable (#6976) + * Move xorm logger bridge from log to models so that log module could be a standalone package (#6944) + * Refactor models.NewRepoContext to extract git related codes to modules/git (#6941) + * Remove macaron dependent on models (#6940) + * Add less linter via npx (#6936) + * Remove macaron dependent on modules/log (#6933) + * Remove macaron dependent on models/mail.go (#6931) + * Clean less files (#6921) + * Fix code overflow (#6914) + * Style orgs list in user profile (#6911) + * Improve description of branch protection (fix #6886) (#6906) + * Move sdk structs to modules/structs (#6905) + * update sdk to latest (#6903) + * Escape the commit message on issues update and title in telegram hook (#6901) + * SearchRepositoryByName improvements and unification (#6897) + * Change the color of issues/pulls list, merged is purple and closed is red (#6874) + * Refactor table width to have more info shown in file list (#6867) + * Monitor all git commands; move blame to git package and replace git as a variable (#6864) + * Fix config ui error about cache ttl (#6861) + * Improve localization of git activity stats (#6848) + * Generate access token in admin cli (#6847) + * Update github.com/urfave/cli to version 1.2.0 (#6838) + * Rename LFS_JWT_SECRET cli option to include OAUTH2 as well (#6826) + * internal/ssh: ignore env command totally (#6825) + * Allow Recaptcha service url to be configured (#6820) + * update github.com/mcuadros/go-version to v0.0.0-20190308113854-92cdf37c5b75 (#6815) + * Use modules/git for git commands (#6775) + * Add GET requests to webhook (#6771) + * Move PushUpdate dependency from models to repofiles (#6763) + * Tweak tab text and icon colors (#6760) + * Ignore non-standard refs in git push (#6758) + * Disable web preview for telegram webhook (#6719) + * Show full name if DEFAULT_SHOW_FULL_NAME setting enabled (#6710) + * Reorder file actions (#6706) + * README WordPress the code is overflowing #6679 (#6696) + * Improve issue reference on commit (#6694) + * Handle redirects for git clone commands (#6688) + * Fix one performance/correctness regression in #6478 found on Rails repository. (#6686) + * API OTP Context (#6674) + * Remove local clones & make hooks run on merge/edit/upload (#6672) + * Bump github.com/stretchr/testify from 1.2.2 to 1.3.0 (#6663) + * Bump gopkg.in/src-d/go-git.v4 from 4.8.0 to 4.10.0 (#6662) + * Fix dropdown icon padding (#6651) + * Add more title attributes on shortened names (#6647) + * Update UI for topics labels on projects (#6639) + * Trace Logging on Permission Denied & ColorFormat (#6618) + * Add .gpg url (match github behaviour) (#6610) + * Support for custom GITEA_CUSTOM env var in docker(#6608) + * Show "delete branch" button on closed pull requests (#6570) (#6601) + * Add option to disable refresh token invalidation (#6584) + * Fix new repo dropdown alignment (#6583) + * Fix mail notification when close/reopen issue (#6581) + * Pre-calculate the absolute path of git (#6575) + * Minor CSS cleanup for the navbar (#6553) + * Render SHA1 links as code blocks (#6546) + * Add username flag in create-user command (#6534) + * Unifies pagination template usage (#6531) (#6533) + * Fixes pagination width on mobile view (#5711) (#6532) + * Improve SHA1 link detection (#6526) + * Fixes #6446 - Sort team members and team's repositories (#6525) + * Use stricter boundaries for auto-link detection (#6522) + * Use regular line-height on frontpage entries (#6518) + * Fixes #6514 - New Pull Request on files and pulls pages the same (#6515) + * Make distinction between DisplayName and Username in email templates (#6495) + * Add X-Auto-Response-Suppress header to outgoing messages (#6492) + * Cleaned permission checks for API -> site admin can now do anything (#6483) + * Support search operators for commits search (#6479) + * Improve listing performance by using go-git (#6478) + * Fix repo sub_menu font color in arc-green (#6477) + * Show last commit status in pull request lists (#6465) + * Add signatures to webhooks (#6428) + * Optimize all images in public/img (#6427) + * Add golangci (#6418) + * Make "Ghost" not link to 404 page (#6410) + * Include more variables on admin/config page (#6378) + * Markdown: enable some more extensions (#6362) + * Include repo name in page title tag (#6343) + * Show locale string on timestamp (#6324) + * Handle CORS requests (#6289) + * Improve issue autolinks (#6273) + * Migration Tweaks (#6260) + * Add title attributes to all items in the repo list viewer (#6258) + * Issue indexer queue redis support (#6218) + * Add bio field for user (#6113) + * Make the version within makefile overwriteable (#6080) + * Updates to API 404 responses (#6077) + * Use Go1.11 module (#5743) + * UX + Security current user password reset (#5042) + * Refactor: append, build variable and type switch (#4940) + * Git statistics in Activity tab (#4724) + * Drop the bits argument when generating an ed25519 key (#6504) +* SECURITY + * Shadow the password on cache and session config on admin panel (#7300) +* TESTING + * Exclude pull_request from fetch-tags step, fixes #7108 (#7120) + * Refactor and improve git test (#7086) + * Fix TestSearchRepo by waiting till indexing is done (#7004) + * Add mssql migration tests (needs #6823) (#6852) + * Add tests for Org API (#6731) + * Context.ServerError and NotFound should log from their caller (#6550) +* TRANSLATION + * Add french specific rule for translating plural texts (#6846) +* BUILD + * Update mssql driver to last working version 20180314172330-6a30f4e59a44 (#7306) + * Alpine 3.10 (#7256) + * Use vfsgen instead of go-bindata (#7080) + * remove and disable package-lock (#6969) + * add make targets for js and css, add js linter (#6952) + * Added tags pull step to drone config to show correct version hashes i… (#6836) + * Make CustomPath, CustomConf and AppWorkPath configurable at build (#6631) + * chore: update drone format to 1.0 (#6602) + * Fix race in integration testlogger (#6556) + * Quieter Integration Tests (#6513) + * Drop the docker Makefile from the image (#6507) + * Add make version on gitea version (#6485) + * Fix #6468 - Uses space match and adds newline for all sed flavors (#6473) + * Move code.gitea.io/git to code.gitea.io/gitea/modules/git (#6364) + * Update npm dependencies and various tweaks (#7344) + * Fix updated drone file (#7336) + * Add 'npm' and 'npm-update' make targets and lockfile (#7246) +* DOCS + * Add work path CLI option (#6922) + * Fix logging documentation (#6904) + * Some logging documentation (#6498) + * Fix link to Hacking on Gitea on From-Source doc page (#6471) + * Fix typos in docs command-line examples (#6466) + * Added docker example for backup (#5846) + +## [1.8.3](https://github.com/go-gitea/gitea/releases/tag/v1.8.3) - 2019-06-17 +* BUGFIXES + * Always set userID on LFS authentication (#7224) (Part of #6993) + * Fix LFS Locks over SSH (#6999) (#7223) + * Fix duplicated file on pull request conflicted files (#7211) (#7214) + * Detect noreply email address as user (#7133) (#7195) + * Don't get milestone from DB if ID is zero (#7169) (#7174) + * Allow archived repos to be (un)starred and (un)watched (#7163) (#7168) + * Fix GCArgs load from ini (#7156) (#7157) + +## [1.8.2](https://github.com/go-gitea/gitea/releases/tag/v1.8.2) - 2019-05-29 +* BUGFIXES + * Fix possbile mysql invalid connnection error (#7051) (#7071) + * Handle invalid administrator username on install page (#7060) (#7063) + * Disable arm7 builds (#7037) (#7042) + * Fix default for allowing new organization creation for new users (#7017) (#7034) + * SearchRepositoryByName improvements and unification (#6897) (#7002) + * Fix u2f registrationlist ToRegistrations() method (#6980) (#6982) + * Allow collaborators to view repo owned by private org (#6965) (#6968) + * Use AppURL for Oauth user link (#6894) (#6925) + * Escape the commit message on issues update (#6901) (#6902) + * Fix regression for API users search (#6882) (#6885) + * Handle early git version's lack of get-url (#7065) (#7076) + * Fix wrong init dependency on markup extensions (#7038) (#7074) + ## [1.8.1](https://github.com/go-gitea/gitea/releases/tag/v1.8.1) - 2019-05-08 * BUGFIXES * Fix 404 when sending pull requests in some situations (#6871) (#6873) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 260727f15e..82ab83fed5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,17 +65,17 @@ high-level discussions. ## Testing redux Before submitting a pull request, run all the tests for the whole tree -to make sure your changes don't cause regression elsewhere. +to make sure your changes don't cause regression elsewhere. -Here's how to run the test suite: +Here's how to run the test suite: - Install the correct version of the drone-cli package. As of this writing, the correct drone-cli version is - [0.8.6](https://0-8-0.docs.drone.io/cli-installation/). + [1.1.0](https://docs.drone.io/cli/install/). - Ensure you have enough free disk space. You will need at least 15-20 Gb of free disk space to hold all of the containers drone creates (a default AWS or GCE disk size won't work -- see - [#6243](https://github.com/go-gitea/gitea/issues/6243)). + [#6243](https://github.com/go-gitea/gitea/issues/6243)). - Change into the base directory of your copy of the gitea repository, and run `drone exec --local --build-event pull_request`. @@ -114,7 +114,7 @@ Generally, the go build tools are installed as-needed in the `Makefile`. An exception are the tools to build the CSS and images. - To build CSS: Install [Node.js](https://nodejs.org/en/download/package-manager) at version 8.0 or above - with `npm` and then run `npm install` and `make generate-stylesheets`. + with `npm` and then run `npm install` and `make css`. - To build Images: ImageMagick, inkscape and zopflipng binaries must be available in your `PATH` to run `make generate-images`. @@ -214,7 +214,7 @@ to the maintainers team. If a maintainer is inactive for more than 3 months and forgets to leave the maintainers team, the owners may move him or her from the maintainers team to the advisors team. For security reasons, Maintainers should use 2FA for their accounts and -if possible provide gpg signed commits. +if possible provide gpg signed commits. https://help.github.com/articles/securing-your-account-with-two-factor-authentication-2fa/ https://help.github.com/articles/signing-commits-with-gpg/ @@ -281,7 +281,7 @@ be reviewed by two maintainers and must pass the automatic tests. * Create `-dev` tag as `git tag -s -F release.notes v$vmaj.$vmin.0-dev` and push the tag as `git push origin v$vmaj.$vmin.0-dev`. * When CI has finished building tag then you have to create a new branch named `release/v$vmaj.$vmin` * If it is bugfix version create PR for changelog on branch `release/v$vmaj.$vmin` and wait till it is reviewed and merged. -* Add a tag as `git tag -s -F release.notes v$vmaj.$vmin.$`, release.notes file could be a temporary file to only include the changelog this version which you added to `CHANGELOG.md`. +* Add a tag as `git tag -s -F release.notes v$vmaj.$vmin.$`, release.notes file could be a temporary file to only include the changelog this version which you added to `CHANGELOG.md`. * And then push the tag as `git push origin v$vmaj.$vmin.$`. Drone CI will automatically created a release and upload all the compiled binary. (But currently it didn't add the release notes automatically. Maybe we should fix that.) * If needed send PR for changelog on branch `master`. * Send PR to [blog repository](https://github.com/go-gitea/blog) announcing the release. diff --git a/Dockerfile b/Dockerfile index 1aae5fc6d4..f13fdbe55f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ################################### #Build stage -FROM golang:1.12-alpine3.9 AS build-env +FROM golang:1.12-alpine3.10 AS build-env ARG GITEA_VERSION ARG TAGS="sqlite sqlite_unlock_notify" @@ -18,7 +18,7 @@ WORKDIR ${GOPATH}/src/code.gitea.io/gitea RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \ && make clean generate build -FROM alpine:3.9 +FROM alpine:3.10 LABEL maintainer="maintainers@gitea.io" EXPOSE 22 3000 diff --git a/MAINTAINERS b/MAINTAINERS index 1934acce49..b97a452db6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29,3 +29,4 @@ Andrew Thornton (@zeripath) John Olheiser (@jolheiser) Richard Mahn (@richmahn) Mrsdizzie (@mrsdizzie) +silverwind (@silverwind) diff --git a/Makefile b/Makefile index de625ce243..2c6a6ef72e 100644 --- a/Makefile +++ b/Makefile @@ -97,15 +97,12 @@ vet: .PHONY: generate generate: - @hash go-bindata > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/jteeuwen/go-bindata/go-bindata; \ - fi - $(GO) generate $(PACKAGES) + GO111MODULE=on $(GO) generate -mod=vendor $(PACKAGES) .PHONY: generate-swagger generate-swagger: @hash swagger > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/go-swagger/go-swagger/cmd/swagger; \ + GO111MODULE="on" $(GO) get -u github.com/go-swagger/go-swagger/cmd/swagger@v0.19.0; \ fi swagger generate spec -o './$(SWAGGER_SPEC)' $(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)' @@ -138,6 +135,10 @@ errcheck: .PHONY: lint lint: + @echo 'make lint is depricated. Use "make revive" if you want to use the old lint tool, or "make golangci-lint" to run a complete code check.' + +.PHONY: revive +revive: @hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u github.com/mgechev/revive; \ fi @@ -335,7 +336,7 @@ release-linux: @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ $(GO) get -u src.techknowlogick.com/xgo; \ fi - xgo -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out gitea-$(VERSION) . + xgo -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/mips64le,linux/mips,linux/mipsle' -out gitea-$(VERSION) . ifeq ($(CI),drone) cp /build/* $(DIST)/binaries endif @@ -365,32 +366,58 @@ release-compress: fi cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done; -.PHONY: javascripts -javascripts: public/js/index.js +npm-check: + @hash npm > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + echo "Please install Node.js 8.x or greater with npm"; \ + exit 1; \ + fi; + @hash npx > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + echo "Please install Node.js 8.x or greater with npm"; \ + exit 1; \ + fi; -.IGNORE: public/js/index.js -public/js/index.js: $(JAVASCRIPTS) - cat $< >| $@ +.PHONY: npm +npm: npm-check + npm install --no-save + +.PHONY: npm-update +npm-update: npm-check + npx updates -cu + rm -rf node_modules package-lock.json + npm install --package-lock + +.PHONY: js +js: npm + npx eslint public/js + +.PHONY: css +css: npm + npx stylelint public/less + npx lessc --clean-css="--s0 -b" public/less/index.less public/css/index.css + $(foreach file, $(filter-out public/less/themes/_base.less, $(wildcard public/less/themes/*)),npx lessc --clean-css="--s0 -b" public/less/themes/$(notdir $(file)) > public/css/theme-$(notdir $(call strip-suffix,$(file))).css;) + npx postcss --use autoprefixer --no-map --replace public/css/* -.PHONY: stylesheets-check -stylesheets-check: generate-stylesheets @diff=$$(git diff public/css/*); \ - if [ -n "$$diff" ]; then \ - echo "Please run 'make generate-stylesheets' and commit the result:"; \ + if ([ -n "$$CI" ] && [ -n "$$diff" ]); then \ + echo "Generated files in public/css have changed, please commit the result:"; \ echo "$${diff}"; \ exit 1; \ fi; +.PHONY: javascripts +javascripts: + echo "'make javascripts' is deprecated, please use 'make js'" + $(MAKE) js + +.PHONY: stylesheets-check +stylesheets-check: + echo "'make stylesheets-check' is deprecated, please use 'make css'" + $(MAKE) css + .PHONY: generate-stylesheets generate-stylesheets: - @hash npx > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - echo "Please install npm version 5.2+"; \ - exit 1; \ - fi; - $(eval BROWSERS := "> 1%, last 2 firefox versions, last 2 safari versions, ie 11") - npx lessc --clean-css public/less/index.less public/css/index.css - $(foreach file, $(filter-out public/less/themes/_base.less, $(wildcard public/less/themes/*)),npx lessc --clean-css public/less/themes/$(notdir $(file)) > public/css/theme-$(notdir $(call strip-suffix,$(file))).css;) - $(foreach file, $(wildcard public/css/*),npx postcss --use autoprefixer --autoprefixer.browsers $(BROWSERS) -o $(file) $(file);) + echo "'make generate-stylesheets' is deprecated, please use 'make css'" + $(MAKE) css .PHONY: swagger-ui swagger-ui: @@ -441,3 +468,11 @@ generate-images: .PHONY: pr pr: $(GO) run contrib/pr/checkout.go $(PR) + +.PHONY: golangci-lint +golangci-lint: + @hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + export BINARY="golangci-lint"; \ + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(GOPATH)/bin v1.16.0; \ + fi + golangci-lint run diff --git a/cmd/admin.go b/cmd/admin.go index ecb4eb48a6..4c4d6f9b66 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -131,6 +131,10 @@ var ( Subcommands: []cli.Command{ microcmdAuthAddOauth, microcmdAuthUpdateOauth, + cmdAuthAddLdapBindDn, + cmdAuthUpdateLdapBindDn, + cmdAuthAddLdapSimpleAuth, + cmdAuthUpdateLdapSimpleAuth, microcmdAuthList, microcmdAuthDelete, }, @@ -144,7 +148,7 @@ var ( idFlag = cli.Int64Flag{ Name: "id", - Usage: "ID of OAuth authentication source", + Usage: "ID of authentication source", } microcmdAuthDelete = cli.Command{ @@ -481,7 +485,7 @@ func runUpdateOauth(c *cli.Context) error { } // update custom URL mapping - var customURLMapping *oauth2.CustomURLMapping + var customURLMapping = &oauth2.CustomURLMapping{} if oAuth2Config.CustomURLMapping != nil { customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL diff --git a/cmd/admin_auth_ldap.go b/cmd/admin_auth_ldap.go new file mode 100644 index 0000000000..cce3aa894f --- /dev/null +++ b/cmd/admin_auth_ldap.go @@ -0,0 +1,359 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth/ldap" + + "github.com/urfave/cli" +) + +type ( + authService struct { + initDB func() error + createLoginSource func(loginSource *models.LoginSource) error + updateLoginSource func(loginSource *models.LoginSource) error + getLoginSourceByID func(id int64) (*models.LoginSource, error) + } +) + +var ( + commonLdapCLIFlags = []cli.Flag{ + cli.StringFlag{ + Name: "name", + Usage: "Authentication name.", + }, + cli.BoolFlag{ + Name: "not-active", + Usage: "Deactivate the authentication source.", + }, + cli.StringFlag{ + Name: "security-protocol", + Usage: "Security protocol name.", + }, + cli.BoolFlag{ + Name: "skip-tls-verify", + Usage: "Disable TLS verification.", + }, + cli.StringFlag{ + Name: "host", + Usage: "The address where the LDAP server can be reached.", + }, + cli.IntFlag{ + Name: "port", + Usage: "The port to use when connecting to the LDAP server.", + }, + cli.StringFlag{ + Name: "user-search-base", + Usage: "The LDAP base at which user accounts will be searched for.", + }, + cli.StringFlag{ + Name: "user-filter", + Usage: "An LDAP filter declaring how to find the user record that is attempting to authenticate.", + }, + cli.StringFlag{ + Name: "admin-filter", + Usage: "An LDAP filter specifying if a user should be given administrator privileges.", + }, + cli.StringFlag{ + Name: "username-attribute", + Usage: "The attribute of the user’s LDAP record containing the user name.", + }, + cli.StringFlag{ + Name: "firstname-attribute", + Usage: "The attribute of the user’s LDAP record containing the user’s first name.", + }, + cli.StringFlag{ + Name: "surname-attribute", + Usage: "The attribute of the user’s LDAP record containing the user’s surname.", + }, + cli.StringFlag{ + Name: "email-attribute", + Usage: "The attribute of the user’s LDAP record containing the user’s email address.", + }, + cli.StringFlag{ + Name: "public-ssh-key-attribute", + Usage: "The attribute of the user’s LDAP record containing the user’s public ssh key.", + }, + } + + ldapBindDnCLIFlags = append(commonLdapCLIFlags, + cli.StringFlag{ + Name: "bind-dn", + Usage: "The DN to bind to the LDAP server with when searching for the user.", + }, + cli.StringFlag{ + Name: "bind-password", + Usage: "The password for the Bind DN, if any.", + }, + cli.BoolFlag{ + Name: "attributes-in-bind", + Usage: "Fetch attributes in bind DN context.", + }, + cli.BoolFlag{ + Name: "synchronize-users", + Usage: "Enable user synchronization.", + }, + cli.UintFlag{ + Name: "page-size", + Usage: "Search page size.", + }) + + ldapSimpleAuthCLIFlags = append(commonLdapCLIFlags, + cli.StringFlag{ + Name: "user-dn", + Usage: "The user’s DN.", + }) + + cmdAuthAddLdapBindDn = cli.Command{ + Name: "add-ldap", + Usage: "Add new LDAP (via Bind DN) authentication source", + Action: func(c *cli.Context) error { + return newAuthService().addLdapBindDn(c) + }, + Flags: ldapBindDnCLIFlags, + } + + cmdAuthUpdateLdapBindDn = cli.Command{ + Name: "update-ldap", + Usage: "Update existing LDAP (via Bind DN) authentication source", + Action: func(c *cli.Context) error { + return newAuthService().updateLdapBindDn(c) + }, + Flags: append([]cli.Flag{idFlag}, ldapBindDnCLIFlags...), + } + + cmdAuthAddLdapSimpleAuth = cli.Command{ + Name: "add-ldap-simple", + Usage: "Add new LDAP (simple auth) authentication source", + Action: func(c *cli.Context) error { + return newAuthService().addLdapSimpleAuth(c) + }, + Flags: ldapSimpleAuthCLIFlags, + } + + cmdAuthUpdateLdapSimpleAuth = cli.Command{ + Name: "update-ldap-simple", + Usage: "Update existing LDAP (simple auth) authentication source", + Action: func(c *cli.Context) error { + return newAuthService().updateLdapSimpleAuth(c) + }, + Flags: append([]cli.Flag{idFlag}, ldapSimpleAuthCLIFlags...), + } +) + +// newAuthService creates a service with default functions. +func newAuthService() *authService { + return &authService{ + initDB: initDB, + createLoginSource: models.CreateLoginSource, + updateLoginSource: models.UpdateSource, + getLoginSourceByID: models.GetLoginSourceByID, + } +} + +// parseLoginSource assigns values on loginSource according to command line flags. +func parseLoginSource(c *cli.Context, loginSource *models.LoginSource) { + if c.IsSet("name") { + loginSource.Name = c.String("name") + } + if c.IsSet("not-active") { + loginSource.IsActived = !c.Bool("not-active") + } + if c.IsSet("synchronize-users") { + loginSource.IsSyncEnabled = c.Bool("synchronize-users") + } +} + +// parseLdapConfig assigns values on config according to command line flags. +func parseLdapConfig(c *cli.Context, config *models.LDAPConfig) error { + if c.IsSet("name") { + config.Source.Name = c.String("name") + } + if c.IsSet("host") { + config.Source.Host = c.String("host") + } + if c.IsSet("port") { + config.Source.Port = c.Int("port") + } + if c.IsSet("security-protocol") { + p, ok := findLdapSecurityProtocolByName(c.String("security-protocol")) + if !ok { + return fmt.Errorf("Unknown security protocol name: %s", c.String("security-protocol")) + } + config.Source.SecurityProtocol = p + } + if c.IsSet("skip-tls-verify") { + config.Source.SkipVerify = c.Bool("skip-tls-verify") + } + if c.IsSet("bind-dn") { + config.Source.BindDN = c.String("bind-dn") + } + if c.IsSet("user-dn") { + config.Source.UserDN = c.String("user-dn") + } + if c.IsSet("bind-password") { + config.Source.BindPassword = c.String("bind-password") + } + if c.IsSet("user-search-base") { + config.Source.UserBase = c.String("user-search-base") + } + if c.IsSet("username-attribute") { + config.Source.AttributeUsername = c.String("username-attribute") + } + if c.IsSet("firstname-attribute") { + config.Source.AttributeName = c.String("firstname-attribute") + } + if c.IsSet("surname-attribute") { + config.Source.AttributeSurname = c.String("surname-attribute") + } + if c.IsSet("email-attribute") { + config.Source.AttributeMail = c.String("email-attribute") + } + if c.IsSet("attributes-in-bind") { + config.Source.AttributesInBind = c.Bool("attributes-in-bind") + } + if c.IsSet("public-ssh-key-attribute") { + config.Source.AttributeSSHPublicKey = c.String("public-ssh-key-attribute") + } + if c.IsSet("page-size") { + config.Source.SearchPageSize = uint32(c.Uint("page-size")) + } + if c.IsSet("user-filter") { + config.Source.Filter = c.String("user-filter") + } + if c.IsSet("admin-filter") { + config.Source.AdminFilter = c.String("admin-filter") + } + return nil +} + +// findLdapSecurityProtocolByName finds security protocol by its name ignoring case. +// It returns the value of the security protocol and if it was found. +func findLdapSecurityProtocolByName(name string) (ldap.SecurityProtocol, bool) { + for i, n := range models.SecurityProtocolNames { + if strings.EqualFold(name, n) { + return i, true + } + } + return 0, false +} + +// getLoginSource gets the login source by its id defined in the command line flags. +// It returns an error if the id is not set, does not match any source or if the source is not of expected type. +func (a *authService) getLoginSource(c *cli.Context, loginType models.LoginType) (*models.LoginSource, error) { + if err := argsSet(c, "id"); err != nil { + return nil, err + } + + loginSource, err := a.getLoginSourceByID(c.Int64("id")) + if err != nil { + return nil, err + } + + if loginSource.Type != loginType { + return nil, fmt.Errorf("Invalid authentication type. expected: %s, actual: %s", models.LoginNames[loginType], models.LoginNames[loginSource.Type]) + } + + return loginSource, nil +} + +// addLdapBindDn adds a new LDAP via Bind DN authentication source. +func (a *authService) addLdapBindDn(c *cli.Context) error { + if err := argsSet(c, "name", "security-protocol", "host", "port", "user-search-base", "user-filter", "email-attribute"); err != nil { + return err + } + + if err := a.initDB(); err != nil { + return err + } + + loginSource := &models.LoginSource{ + Type: models.LoginLDAP, + IsActived: true, // active by default + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Enabled: true, // always true + }, + }, + } + + parseLoginSource(c, loginSource) + if err := parseLdapConfig(c, loginSource.LDAP()); err != nil { + return err + } + + return a.createLoginSource(loginSource) +} + +// updateLdapBindDn updates a new LDAP via Bind DN authentication source. +func (a *authService) updateLdapBindDn(c *cli.Context) error { + if err := a.initDB(); err != nil { + return err + } + + loginSource, err := a.getLoginSource(c, models.LoginLDAP) + if err != nil { + return err + } + + parseLoginSource(c, loginSource) + if err := parseLdapConfig(c, loginSource.LDAP()); err != nil { + return err + } + + return a.updateLoginSource(loginSource) +} + +// addLdapSimpleAuth adds a new LDAP (simple auth) authentication source. +func (a *authService) addLdapSimpleAuth(c *cli.Context) error { + if err := argsSet(c, "name", "security-protocol", "host", "port", "user-dn", "user-filter", "email-attribute"); err != nil { + return err + } + + if err := a.initDB(); err != nil { + return err + } + + loginSource := &models.LoginSource{ + Type: models.LoginDLDAP, + IsActived: true, // active by default + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Enabled: true, // always true + }, + }, + } + + parseLoginSource(c, loginSource) + if err := parseLdapConfig(c, loginSource.LDAP()); err != nil { + return err + } + + return a.createLoginSource(loginSource) +} + +// updateLdapBindDn updates a new LDAP (simple auth) authentication source. +func (a *authService) updateLdapSimpleAuth(c *cli.Context) error { + if err := a.initDB(); err != nil { + return err + } + + loginSource, err := a.getLoginSource(c, models.LoginDLDAP) + if err != nil { + return err + } + + parseLoginSource(c, loginSource) + if err := parseLdapConfig(c, loginSource.LDAP()); err != nil { + return err + } + + return a.updateLoginSource(loginSource) +} diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go new file mode 100644 index 0000000000..4af9f167c3 --- /dev/null +++ b/cmd/admin_auth_ldap_test.go @@ -0,0 +1,1350 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "testing" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth/ldap" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" +) + +func TestAddLdapBindDn(t *testing.T) { + // Mock cli functions to do not exit on error + var osExiter = cli.OsExiter + defer func() { cli.OsExiter = osExiter }() + cli.OsExiter = func(code int) {} + + // Test cases + var cases = []struct { + args []string + loginSource *models.LoginSource + errMsg string + }{ + // case 0 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source full", + "--not-active", + "--security-protocol", "ldaps", + "--skip-tls-verify", + "--host", "ldap-bind-server full", + "--port", "9876", + "--user-search-base", "ou=Users,dc=full-domain-bind,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", + "--username-attribute", "uid-bind full", + "--firstname-attribute", "givenName-bind full", + "--surname-attribute", "sn-bind full", + "--email-attribute", "mail-bind full", + "--public-ssh-key-attribute", "publickey-bind full", + "--bind-dn", "cn=readonly,dc=full-domain-bind,dc=org", + "--bind-password", "secret-bind-full", + "--attributes-in-bind", + "--synchronize-users", + "--page-size", "99", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Name: "ldap (via Bind DN) source full", + IsActived: false, + IsSyncEnabled: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (via Bind DN) source full", + Host: "ldap-bind-server full", + Port: 9876, + SecurityProtocol: ldap.SecurityProtocol(1), + SkipVerify: true, + BindDN: "cn=readonly,dc=full-domain-bind,dc=org", + BindPassword: "secret-bind-full", + UserBase: "ou=Users,dc=full-domain-bind,dc=org", + AttributeUsername: "uid-bind full", + AttributeName: "givenName-bind full", + AttributeSurname: "sn-bind full", + AttributeMail: "mail-bind full", + AttributesInBind: true, + AttributeSSHPublicKey: "publickey-bind full", + SearchPageSize: 99, + Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", + AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", + Enabled: true, + }, + }, + }, + }, + // case 1 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source min", + "--security-protocol", "unencrypted", + "--host", "ldap-bind-server min", + "--port", "1234", + "--user-search-base", "ou=Users,dc=min-domain-bind,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=min-domain-bind,dc=org)", + "--email-attribute", "mail-bind min", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Name: "ldap (via Bind DN) source min", + IsActived: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (via Bind DN) source min", + Host: "ldap-bind-server min", + Port: 1234, + SecurityProtocol: ldap.SecurityProtocol(0), + UserBase: "ou=Users,dc=min-domain-bind,dc=org", + AttributeMail: "mail-bind min", + Filter: "(memberOf=cn=user-group,ou=example,dc=min-domain-bind,dc=org)", + Enabled: true, + }, + }, + }, + }, + // case 2 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source", + "--security-protocol", "zzzzz", + "--host", "ldap-server", + "--port", "1234", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + "--email-attribute", "mail", + }, + errMsg: "Unknown security protocol name: zzzzz", + }, + // case 3 + { + args: []string{ + "ldap-test", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "1234", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + "--email-attribute", "mail", + }, + errMsg: "name is not set", + }, + // case 4 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source", + "--host", "ldap-server", + "--port", "1234", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + "--email-attribute", "mail", + }, + errMsg: "security-protocol is not set", + }, + // case 5 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source", + "--security-protocol", "unencrypted", + "--port", "1234", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + "--email-attribute", "mail", + }, + errMsg: "host is not set", + }, + // case 6 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + "--email-attribute", "mail", + }, + errMsg: "port is not set", + }, + // case 7 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "1234", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--email-attribute", "mail", + }, + errMsg: "user-filter is not set", + }, + // case 8 + { + args: []string{ + "ldap-test", + "--name", "ldap (via Bind DN) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "1234", + "--user-search-base", "ou=Users,dc=domain,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + }, + errMsg: "email-attribute is not set", + }, + } + + for n, c := range cases { + // Mock functions. + var createdLoginSource *models.LoginSource + service := &authService{ + initDB: func() error { + return nil + }, + createLoginSource: func(loginSource *models.LoginSource) error { + createdLoginSource = loginSource + return nil + }, + updateLoginSource: func(loginSource *models.LoginSource) error { + assert.FailNow(t, "case %d: should not call updateLoginSource", n) + return nil + }, + getLoginSourceByID: func(id int64) (*models.LoginSource, error) { + assert.FailNow(t, "case %d: should not call getLoginSourceByID", n) + return nil, nil + }, + } + + // Create a copy of command to test + app := cli.NewApp() + app.Flags = cmdAuthAddLdapBindDn.Flags + app.Action = service.addLdapBindDn + + // Run it + err := app.Run(c.args) + if c.errMsg != "" { + assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) + } else { + assert.NoError(t, err, "case %d: should have no errors", n) + assert.Equal(t, c.loginSource, createdLoginSource, "case %d: wrong loginSource", n) + } + } +} + +func TestAddLdapSimpleAuth(t *testing.T) { + // Mock cli functions to do not exit on error + var osExiter = cli.OsExiter + defer func() { cli.OsExiter = osExiter }() + cli.OsExiter = func(code int) {} + + // Test cases + var cases = []struct { + args []string + loginSource *models.LoginSource + errMsg string + }{ + // case 0 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source full", + "--not-active", + "--security-protocol", "starttls", + "--skip-tls-verify", + "--host", "ldap-simple-server full", + "--port", "987", + "--user-search-base", "ou=Users,dc=full-domain-simple,dc=org", + "--user-filter", "(&(objectClass=posixAccount)(full-simple-cn=%s))", + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", + "--username-attribute", "uid-simple full", + "--firstname-attribute", "givenName-simple full", + "--surname-attribute", "sn-simple full", + "--email-attribute", "mail-simple full", + "--public-ssh-key-attribute", "publickey-simple full", + "--user-dn", "cn=%s,ou=Users,dc=full-domain-simple,dc=org", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Name: "ldap (simple auth) source full", + IsActived: false, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (simple auth) source full", + Host: "ldap-simple-server full", + Port: 987, + SecurityProtocol: ldap.SecurityProtocol(2), + SkipVerify: true, + UserDN: "cn=%s,ou=Users,dc=full-domain-simple,dc=org", + UserBase: "ou=Users,dc=full-domain-simple,dc=org", + AttributeUsername: "uid-simple full", + AttributeName: "givenName-simple full", + AttributeSurname: "sn-simple full", + AttributeMail: "mail-simple full", + AttributeSSHPublicKey: "publickey-simple full", + Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))", + AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", + Enabled: true, + }, + }, + }, + }, + // case 1 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source min", + "--security-protocol", "unencrypted", + "--host", "ldap-simple-server min", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(min-simple-cn=%s))", + "--email-attribute", "mail-simple min", + "--user-dn", "cn=%s,ou=Users,dc=min-domain-simple,dc=org", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Name: "ldap (simple auth) source min", + IsActived: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (simple auth) source min", + Host: "ldap-simple-server min", + Port: 123, + SecurityProtocol: ldap.SecurityProtocol(0), + UserDN: "cn=%s,ou=Users,dc=min-domain-simple,dc=org", + AttributeMail: "mail-simple min", + Filter: "(&(objectClass=posixAccount)(min-simple-cn=%s))", + Enabled: true, + }, + }, + }, + }, + // case 2 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--security-protocol", "zzzzz", + "--host", "ldap-server", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--email-attribute", "mail", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "Unknown security protocol name: zzzzz", + }, + // case 3 + { + args: []string{ + "ldap-test", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--email-attribute", "mail", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "name is not set", + }, + // case 4 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--host", "ldap-server", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--email-attribute", "mail", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "security-protocol is not set", + }, + // case 5 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--security-protocol", "unencrypted", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--email-attribute", "mail", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "host is not set", + }, + // case 6 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--email-attribute", "mail", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "port is not set", + }, + // case 7 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "123", + "--email-attribute", "mail", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "user-filter is not set", + }, + // case 8 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + errMsg: "email-attribute is not set", + }, + // case 9 + { + args: []string{ + "ldap-test", + "--name", "ldap (simple auth) source", + "--security-protocol", "unencrypted", + "--host", "ldap-server", + "--port", "123", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + "--email-attribute", "mail", + }, + errMsg: "user-dn is not set", + }, + } + + for n, c := range cases { + // Mock functions. + var createdLoginSource *models.LoginSource + service := &authService{ + initDB: func() error { + return nil + }, + createLoginSource: func(loginSource *models.LoginSource) error { + createdLoginSource = loginSource + return nil + }, + updateLoginSource: func(loginSource *models.LoginSource) error { + assert.FailNow(t, "case %d: should not call updateLoginSource", n) + return nil + }, + getLoginSourceByID: func(id int64) (*models.LoginSource, error) { + assert.FailNow(t, "case %d: should not call getLoginSourceByID", n) + return nil, nil + }, + } + + // Create a copy of command to test + app := cli.NewApp() + app.Flags = cmdAuthAddLdapSimpleAuth.Flags + app.Action = service.addLdapSimpleAuth + + // Run it + err := app.Run(c.args) + if c.errMsg != "" { + assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) + } else { + assert.NoError(t, err, "case %d: should have no errors", n) + assert.Equal(t, c.loginSource, createdLoginSource, "case %d: wrong loginSource", n) + } + } +} + +func TestUpdateLdapBindDn(t *testing.T) { + // Mock cli functions to do not exit on error + var osExiter = cli.OsExiter + defer func() { cli.OsExiter = osExiter }() + cli.OsExiter = func(code int) {} + + // Test cases + var cases = []struct { + args []string + id int64 + existingLoginSource *models.LoginSource + loginSource *models.LoginSource + errMsg string + }{ + // case 0 + { + args: []string{ + "ldap-test", + "--id", "23", + "--name", "ldap (via Bind DN) source full", + "--not-active", + "--security-protocol", "LDAPS", + "--skip-tls-verify", + "--host", "ldap-bind-server full", + "--port", "9876", + "--user-search-base", "ou=Users,dc=full-domain-bind,dc=org", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", + "--username-attribute", "uid-bind full", + "--firstname-attribute", "givenName-bind full", + "--surname-attribute", "sn-bind full", + "--email-attribute", "mail-bind full", + "--public-ssh-key-attribute", "publickey-bind full", + "--bind-dn", "cn=readonly,dc=full-domain-bind,dc=org", + "--bind-password", "secret-bind-full", + "--synchronize-users", + "--page-size", "99", + }, + id: 23, + existingLoginSource: &models.LoginSource{ + Type: models.LoginLDAP, + IsActived: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Enabled: true, + }, + }, + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Name: "ldap (via Bind DN) source full", + IsActived: false, + IsSyncEnabled: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (via Bind DN) source full", + Host: "ldap-bind-server full", + Port: 9876, + SecurityProtocol: ldap.SecurityProtocol(1), + SkipVerify: true, + BindDN: "cn=readonly,dc=full-domain-bind,dc=org", + BindPassword: "secret-bind-full", + UserBase: "ou=Users,dc=full-domain-bind,dc=org", + AttributeUsername: "uid-bind full", + AttributeName: "givenName-bind full", + AttributeSurname: "sn-bind full", + AttributeMail: "mail-bind full", + AttributesInBind: false, + AttributeSSHPublicKey: "publickey-bind full", + SearchPageSize: 99, + Filter: "(memberOf=cn=user-group,ou=example,dc=full-domain-bind,dc=org)", + AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-bind,dc=org)", + Enabled: true, + }, + }, + }, + }, + // case 1 + { + args: []string{ + "ldap-test", + "--id", "1", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + }, + // case 2 + { + args: []string{ + "ldap-test", + "--id", "1", + "--name", "ldap (via Bind DN) source", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Name: "ldap (via Bind DN) source", + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (via Bind DN) source", + }, + }, + }, + }, + // case 3 + { + args: []string{ + "ldap-test", + "--id", "1", + "--not-active", + }, + existingLoginSource: &models.LoginSource{ + Type: models.LoginLDAP, + IsActived: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + IsActived: false, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + }, + // case 4 + { + args: []string{ + "ldap-test", + "--id", "1", + "--security-protocol", "LDAPS", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + SecurityProtocol: ldap.SecurityProtocol(1), + }, + }, + }, + }, + // case 5 + { + args: []string{ + "ldap-test", + "--id", "1", + "--skip-tls-verify", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + SkipVerify: true, + }, + }, + }, + }, + // case 6 + { + args: []string{ + "ldap-test", + "--id", "1", + "--host", "ldap-server", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Host: "ldap-server", + }, + }, + }, + }, + // case 7 + { + args: []string{ + "ldap-test", + "--id", "1", + "--port", "389", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Port: 389, + }, + }, + }, + }, + // case 8 + { + args: []string{ + "ldap-test", + "--id", "1", + "--user-search-base", "ou=Users,dc=domain,dc=org", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + UserBase: "ou=Users,dc=domain,dc=org", + }, + }, + }, + }, + // case 9 + { + args: []string{ + "ldap-test", + "--id", "1", + "--user-filter", "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Filter: "(memberOf=cn=user-group,ou=example,dc=domain,dc=org)", + }, + }, + }, + }, + // case 10 + { + args: []string{ + "ldap-test", + "--id", "1", + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", + }, + }, + }, + }, + // case 11 + { + args: []string{ + "ldap-test", + "--id", "1", + "--username-attribute", "uid", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeUsername: "uid", + }, + }, + }, + }, + // case 12 + { + args: []string{ + "ldap-test", + "--id", "1", + "--firstname-attribute", "givenName", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeName: "givenName", + }, + }, + }, + }, + // case 13 + { + args: []string{ + "ldap-test", + "--id", "1", + "--surname-attribute", "sn", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeSurname: "sn", + }, + }, + }, + }, + // case 14 + { + args: []string{ + "ldap-test", + "--id", "1", + "--email-attribute", "mail", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeMail: "mail", + }, + }, + }, + }, + // case 15 + { + args: []string{ + "ldap-test", + "--id", "1", + "--attributes-in-bind", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributesInBind: true, + }, + }, + }, + }, + // case 16 + { + args: []string{ + "ldap-test", + "--id", "1", + "--public-ssh-key-attribute", "publickey", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeSSHPublicKey: "publickey", + }, + }, + }, + }, + // case 17 + { + args: []string{ + "ldap-test", + "--id", "1", + "--bind-dn", "cn=readonly,dc=domain,dc=org", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + BindDN: "cn=readonly,dc=domain,dc=org", + }, + }, + }, + }, + // case 18 + { + args: []string{ + "ldap-test", + "--id", "1", + "--bind-password", "secret", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + BindPassword: "secret", + }, + }, + }, + }, + // case 19 + { + args: []string{ + "ldap-test", + "--id", "1", + "--synchronize-users", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + IsSyncEnabled: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + }, + // case 20 + { + args: []string{ + "ldap-test", + "--id", "1", + "--page-size", "12", + }, + loginSource: &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + SearchPageSize: 12, + }, + }, + }, + }, + // case 21 + { + args: []string{ + "ldap-test", + "--id", "1", + "--security-protocol", "xxxxx", + }, + errMsg: "Unknown security protocol name: xxxxx", + }, + // case 22 + { + args: []string{ + "ldap-test", + }, + errMsg: "id is not set", + }, + // case 23 + { + args: []string{ + "ldap-test", + "--id", "1", + }, + existingLoginSource: &models.LoginSource{ + Type: models.LoginOAuth2, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + errMsg: "Invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2", + }, + } + + for n, c := range cases { + // Mock functions. + var updatedLoginSource *models.LoginSource + service := &authService{ + initDB: func() error { + return nil + }, + createLoginSource: func(loginSource *models.LoginSource) error { + assert.FailNow(t, "case %d: should not call createLoginSource", n) + return nil + }, + updateLoginSource: func(loginSource *models.LoginSource) error { + updatedLoginSource = loginSource + return nil + }, + getLoginSourceByID: func(id int64) (*models.LoginSource, error) { + if c.id != 0 { + assert.Equal(t, c.id, id, "case %d: wrong id", n) + } + if c.existingLoginSource != nil { + return c.existingLoginSource, nil + } + return &models.LoginSource{ + Type: models.LoginLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, nil + }, + } + + // Create a copy of command to test + app := cli.NewApp() + app.Flags = cmdAuthUpdateLdapBindDn.Flags + app.Action = service.updateLdapBindDn + + // Run it + err := app.Run(c.args) + if c.errMsg != "" { + assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) + } else { + assert.NoError(t, err, "case %d: should have no errors", n) + assert.Equal(t, c.loginSource, updatedLoginSource, "case %d: wrong loginSource", n) + } + } +} + +func TestUpdateLdapSimpleAuth(t *testing.T) { + // Mock cli functions to do not exit on error + var osExiter = cli.OsExiter + defer func() { cli.OsExiter = osExiter }() + cli.OsExiter = func(code int) {} + + // Test cases + var cases = []struct { + args []string + id int64 + existingLoginSource *models.LoginSource + loginSource *models.LoginSource + errMsg string + }{ + // case 0 + { + args: []string{ + "ldap-test", + "--id", "7", + "--name", "ldap (simple auth) source full", + "--not-active", + "--security-protocol", "starttls", + "--skip-tls-verify", + "--host", "ldap-simple-server full", + "--port", "987", + "--user-search-base", "ou=Users,dc=full-domain-simple,dc=org", + "--user-filter", "(&(objectClass=posixAccount)(full-simple-cn=%s))", + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", + "--username-attribute", "uid-simple full", + "--firstname-attribute", "givenName-simple full", + "--surname-attribute", "sn-simple full", + "--email-attribute", "mail-simple full", + "--public-ssh-key-attribute", "publickey-simple full", + "--user-dn", "cn=%s,ou=Users,dc=full-domain-simple,dc=org", + }, + id: 7, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Name: "ldap (simple auth) source full", + IsActived: false, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (simple auth) source full", + Host: "ldap-simple-server full", + Port: 987, + SecurityProtocol: ldap.SecurityProtocol(2), + SkipVerify: true, + UserDN: "cn=%s,ou=Users,dc=full-domain-simple,dc=org", + UserBase: "ou=Users,dc=full-domain-simple,dc=org", + AttributeUsername: "uid-simple full", + AttributeName: "givenName-simple full", + AttributeSurname: "sn-simple full", + AttributeMail: "mail-simple full", + AttributeSSHPublicKey: "publickey-simple full", + Filter: "(&(objectClass=posixAccount)(full-simple-cn=%s))", + AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=full-domain-simple,dc=org)", + }, + }, + }, + }, + // case 1 + { + args: []string{ + "ldap-test", + "--id", "1", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + }, + // case 2 + { + args: []string{ + "ldap-test", + "--id", "1", + "--name", "ldap (simple auth) source", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Name: "ldap (simple auth) source", + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Name: "ldap (simple auth) source", + }, + }, + }, + }, + // case 3 + { + args: []string{ + "ldap-test", + "--id", "1", + "--not-active", + }, + existingLoginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + IsActived: true, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + IsActived: false, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + }, + // case 4 + { + args: []string{ + "ldap-test", + "--id", "1", + "--security-protocol", "starttls", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + SecurityProtocol: ldap.SecurityProtocol(2), + }, + }, + }, + }, + // case 5 + { + args: []string{ + "ldap-test", + "--id", "1", + "--skip-tls-verify", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + SkipVerify: true, + }, + }, + }, + }, + // case 6 + { + args: []string{ + "ldap-test", + "--id", "1", + "--host", "ldap-server", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Host: "ldap-server", + }, + }, + }, + }, + // case 7 + { + args: []string{ + "ldap-test", + "--id", "1", + "--port", "987", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Port: 987, + }, + }, + }, + }, + // case 8 + { + args: []string{ + "ldap-test", + "--id", "1", + "--user-search-base", "ou=Users,dc=domain,dc=org", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + UserBase: "ou=Users,dc=domain,dc=org", + }, + }, + }, + }, + // case 9 + { + args: []string{ + "ldap-test", + "--id", "1", + "--user-filter", "(&(objectClass=posixAccount)(cn=%s))", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + Filter: "(&(objectClass=posixAccount)(cn=%s))", + }, + }, + }, + }, + // case 10 + { + args: []string{ + "ldap-test", + "--id", "1", + "--admin-filter", "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AdminFilter: "(memberOf=cn=admin-group,ou=example,dc=domain,dc=org)", + }, + }, + }, + }, + // case 11 + { + args: []string{ + "ldap-test", + "--id", "1", + "--username-attribute", "uid", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeUsername: "uid", + }, + }, + }, + }, + // case 12 + { + args: []string{ + "ldap-test", + "--id", "1", + "--firstname-attribute", "givenName", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeName: "givenName", + }, + }, + }, + }, + // case 13 + { + args: []string{ + "ldap-test", + "--id", "1", + "--surname-attribute", "sn", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeSurname: "sn", + }, + }, + }, + }, + // case 14 + { + args: []string{ + "ldap-test", + "--id", "1", + "--email-attribute", "mail", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeMail: "mail", + }, + }, + }, + }, + // case 15 + { + args: []string{ + "ldap-test", + "--id", "1", + "--public-ssh-key-attribute", "publickey", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + AttributeSSHPublicKey: "publickey", + }, + }, + }, + }, + // case 16 + { + args: []string{ + "ldap-test", + "--id", "1", + "--user-dn", "cn=%s,ou=Users,dc=domain,dc=org", + }, + loginSource: &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{ + UserDN: "cn=%s,ou=Users,dc=domain,dc=org", + }, + }, + }, + }, + // case 17 + { + args: []string{ + "ldap-test", + "--id", "1", + "--security-protocol", "xxxxx", + }, + errMsg: "Unknown security protocol name: xxxxx", + }, + // case 18 + { + args: []string{ + "ldap-test", + }, + errMsg: "id is not set", + }, + // case 19 + { + args: []string{ + "ldap-test", + "--id", "1", + }, + existingLoginSource: &models.LoginSource{ + Type: models.LoginPAM, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, + errMsg: "Invalid authentication type. expected: LDAP (simple auth), actual: PAM", + }, + } + + for n, c := range cases { + // Mock functions. + var updatedLoginSource *models.LoginSource + service := &authService{ + initDB: func() error { + return nil + }, + createLoginSource: func(loginSource *models.LoginSource) error { + assert.FailNow(t, "case %d: should not call createLoginSource", n) + return nil + }, + updateLoginSource: func(loginSource *models.LoginSource) error { + updatedLoginSource = loginSource + return nil + }, + getLoginSourceByID: func(id int64) (*models.LoginSource, error) { + if c.id != 0 { + assert.Equal(t, c.id, id, "case %d: wrong id", n) + } + if c.existingLoginSource != nil { + return c.existingLoginSource, nil + } + return &models.LoginSource{ + Type: models.LoginDLDAP, + Cfg: &models.LDAPConfig{ + Source: &ldap.Source{}, + }, + }, nil + }, + } + + // Create a copy of command to test + app := cli.NewApp() + app.Flags = cmdAuthUpdateLdapSimpleAuth.Flags + app.Action = service.updateLdapSimpleAuth + + // Run it + err := app.Run(c.args) + if c.errMsg != "" { + assert.EqualError(t, err, c.errMsg, "case %d: error should match", n) + } else { + assert.NoError(t, err, "case %d: should have no errors", n) + assert.Equal(t, c.loginSource, updatedLoginSource, "case %d: wrong loginSource", n) + } + } +} diff --git a/cmd/cert.go b/cmd/cert.go index 46473c0042..b62319f808 100644 --- a/cmd/cert.go +++ b/cmd/cert.go @@ -170,17 +170,28 @@ func runCert(c *cli.Context) error { if err != nil { log.Fatalf("Failed to open cert.pem for writing: %v", err) } - pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - certOut.Close() + err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + if err != nil { + log.Fatalf("Failed to encode certificate: %v", err) + } + err = certOut.Close() + if err != nil { + log.Fatalf("Failed to write cert: %v", err) + } log.Println("Written cert.pem") keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { log.Fatalf("Failed to open key.pem for writing: %v", err) } - pem.Encode(keyOut, pemBlockForKey(priv)) - keyOut.Close() + err = pem.Encode(keyOut, pemBlockForKey(priv)) + if err != nil { + log.Fatalf("Failed to encode key: %v", err) + } + err = keyOut.Close() + if err != nil { + log.Fatalf("Failed to write key: %v", err) + } log.Println("Written key.pem") - return nil } diff --git a/cmd/convert.go b/cmd/convert.go new file mode 100644 index 0000000000..cb0510c722 --- /dev/null +++ b/cmd/convert.go @@ -0,0 +1,49 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "github.com/urfave/cli" +) + +// CmdConvert represents the available convert sub-command. +var CmdConvert = cli.Command{ + Name: "convert", + Usage: "Convert the database", + Description: "A command to convert an existing MySQL database from utf8 to utf8mb4", + Action: runConvert, +} + +func runConvert(ctx *cli.Context) error { + if err := initDB(); err != nil { + return err + } + + log.Trace("AppPath: %s", setting.AppPath) + log.Trace("AppWorkPath: %s", setting.AppWorkPath) + log.Trace("Custom path: %s", setting.CustomPath) + log.Trace("Log path: %s", setting.LogRootPath) + models.LoadConfigs() + + if models.DbCfg.Type != "mysql" { + fmt.Println("This command can only be used with a MySQL database") + return nil + } + + if err := models.ConvertUtf8ToUtf8mb4(); err != nil { + log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err) + return err + } + + fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4") + + return nil +} diff --git a/cmd/hook.go b/cmd/hook.go index 46f97d5542..ca876f02a3 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -8,15 +8,14 @@ import ( "bufio" "bytes" "fmt" + "net/http" "os" "strconv" "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" - "code.gitea.io/gitea/modules/util" "github.com/urfave/cli" ) @@ -62,12 +61,11 @@ func runHookPreReceive(c *cli.Context) error { setup("hooks/pre-receive.log") // the environment setted on serv command - repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64) isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true") username := os.Getenv(models.EnvRepoUsername) reponame := os.Getenv(models.EnvRepoName) - userIDStr := os.Getenv(models.EnvPusherID) - repoPath := models.RepoPath(username, reponame) + userID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64) + prID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchPRID), 10, 64) buf := bytes.NewBuffer(nil) scanner := bufio.NewScanner(os.Stdin) @@ -89,34 +87,22 @@ func runHookPreReceive(c *cli.Context) error { newCommitID := string(fields[1]) refFullName := string(fields[2]) - branchName := strings.TrimPrefix(refFullName, git.BranchPrefix) - protectBranch, err := private.GetProtectedBranchBy(repoID, branchName) - if err != nil { - fail("Internal error", fmt.Sprintf("retrieve protected branches information failed: %v", err)) - } - - if protectBranch != nil && protectBranch.IsProtected() { - // check and deletion - if newCommitID == git.EmptySHA { - fail(fmt.Sprintf("branch %s is protected from deletion", branchName), "") - } - - // detect force push - if git.EmptySHA != oldCommitID { - output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDir(repoPath) - if err != nil { - fail("Internal error", "Fail to detect force push: %v", err) - } else if len(output) > 0 { - fail(fmt.Sprintf("branch %s is protected from force push", branchName), "") - } - } - - userID, _ := strconv.ParseInt(userIDStr, 10, 64) - canPush, err := private.CanUserPush(protectBranch.ID, userID) - if err != nil { - fail("Internal error", "Fail to detect user can push: %v", err) - } else if !canPush { - fail(fmt.Sprintf("protected branch %s can not be pushed to", branchName), "") + // If the ref is a branch, check if it's protected + if strings.HasPrefix(refFullName, git.BranchPrefix) { + statusCode, msg := private.HookPreReceive(username, reponame, private.HookOptions{ + OldCommitID: oldCommitID, + NewCommitID: newCommitID, + RefFullName: refFullName, + UserID: userID, + GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories), + GitObjectDirectory: os.Getenv(private.GitObjectDirectory), + ProtectedBranchID: prID, + }) + switch statusCode { + case http.StatusInternalServerError: + fail("Internal Server Error", msg) + case http.StatusForbidden: + fail(msg, "") } } } @@ -142,7 +128,6 @@ func runHookPostReceive(c *cli.Context) error { setup("hooks/post-receive.log") // the environment setted on serv command - repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64) repoUser := os.Getenv(models.EnvRepoUsername) isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true") repoName := os.Getenv(models.EnvRepoName) @@ -169,58 +154,31 @@ func runHookPostReceive(c *cli.Context) error { newCommitID := string(fields[1]) refFullName := string(fields[2]) - if err := private.PushUpdate(models.PushUpdateOptions{ - RefFullName: refFullName, - OldCommitID: oldCommitID, - NewCommitID: newCommitID, - PusherID: pusherID, - PusherName: pusherName, - RepoUserName: repoUser, - RepoName: repoName, - }); err != nil { - log.GitLogger.Error("Update: %v", err) + res, err := private.HookPostReceive(repoUser, repoName, private.HookOptions{ + OldCommitID: oldCommitID, + NewCommitID: newCommitID, + RefFullName: refFullName, + UserID: pusherID, + UserName: pusherName, + }) + + if res == nil { + fail("Internal Server Error", err) } - if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) { - branch := strings.TrimPrefix(refFullName, git.BranchPrefix) - repo, pullRequestAllowed, err := private.GetRepository(repoID) - if err != nil { - log.GitLogger.Error("get repo: %v", err) - break - } - if !pullRequestAllowed { - break - } - - baseRepo := repo - if repo.IsFork { - baseRepo = repo.BaseRepo - } - - if !repo.IsFork && branch == baseRepo.DefaultBranch { - break - } - - pr, err := private.ActivePullRequest(baseRepo.ID, repo.ID, baseRepo.DefaultBranch, branch) - if err != nil { - log.GitLogger.Error("get active pr: %v", err) - break - } - - fmt.Fprintln(os.Stderr, "") - if pr == nil { - if repo.IsFork { - branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch) - } - fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", branch) - fmt.Fprintf(os.Stderr, " %s/compare/%s...%s\n", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)) - } else { - fmt.Fprint(os.Stderr, "Visit the existing pull request:\n") - fmt.Fprintf(os.Stderr, " %s/pulls/%d\n", baseRepo.HTMLURL(), pr.Index) - } - fmt.Fprintln(os.Stderr, "") + if res["message"] == false { + continue } + fmt.Fprintln(os.Stderr, "") + if res["create"] == true { + fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", res["branch"]) + fmt.Fprintf(os.Stderr, " %s\n", res["url"]) + } else { + fmt.Fprint(os.Stderr, "Visit the existing pull request:\n") + fmt.Fprintf(os.Stderr, " %s\n", res["url"]) + } + fmt.Fprintln(os.Stderr, "") } return nil diff --git a/cmd/serv.go b/cmd/serv.go index a30e02e7a2..32dd8cbd3e 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -8,14 +8,15 @@ package cmd import ( "encoding/json" "fmt" + "net/http" + "net/url" "os" "os/exec" - "path/filepath" + "strconv" "strings" "time" "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/pprof" "code.gitea.io/gitea/modules/private" @@ -23,12 +24,10 @@ import ( "github.com/Unknwon/com" "github.com/dgrijalva/jwt-go" - version "github.com/mcuadros/go-version" "github.com/urfave/cli" ) const ( - accessDenied = "Repository does not exist or you do not have access" lfsAuthenticateVerb = "git-lfs-authenticate" ) @@ -45,30 +44,9 @@ var CmdServ = cli.Command{ }, } -func checkLFSVersion() { - if setting.LFS.StartServer { - //Disable LFS client hooks if installed for the current OS user - //Needs at least git v2.1.2 - binVersion, err := git.BinVersion() - if err != nil { - fail(fmt.Sprintf("Error retrieving git version: %v", err), fmt.Sprintf("Error retrieving git version: %v", err)) - } - - if !version.Compare(binVersion, "2.1.2", ">=") { - setting.LFS.StartServer = false - println("LFS server support needs at least Git v2.1.2, disabled") - } else { - git.GlobalCommandArgs = append(git.GlobalCommandArgs, "-c", "filter.lfs.required=", - "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=") - } - } -} - func setup(logPath string) { - log.DelLogger("console") + _ = log.DelLogger("console") setting.NewContext() - checkLFSVersion() - log.NewGitLogger(filepath.Join(setting.LogRootPath, logPath)) } func parseCmd(cmd string) (string, string) { @@ -95,15 +73,14 @@ func fail(userMessage, logMessage string, args ...interface{}) { if !setting.ProdMode { fmt.Fprintf(os.Stderr, logMessage+"\n", args...) } - log.GitLogger.Fatal(logMessage, args...) return } - log.GitLogger.Close() os.Exit(1) } func runServ(c *cli.Context) error { + // FIXME: This needs to internationalised setup("serv.log") if setting.SSH.Disabled { @@ -112,13 +89,29 @@ func runServ(c *cli.Context) error { } if len(c.Args()) < 1 { - cli.ShowSubcommandHelp(c) + if err := cli.ShowSubcommandHelp(c); err != nil { + fmt.Printf("error showing subcommand help: %v\n", err) + } return nil } + keys := strings.Split(c.Args()[0], "-") + if len(keys) != 2 || keys[0] != "key" { + fail("Key ID format error", "Invalid key argument: %s", c.Args()[0]) + } + keyID := com.StrTo(keys[1]).MustInt64() + cmd := os.Getenv("SSH_ORIGINAL_COMMAND") if len(cmd) == 0 { - println("Hi there, You've successfully authenticated, but Gitea does not provide shell access.") + key, user, err := private.ServNoCommand(keyID) + if err != nil { + fail("Internal error", "Failed to check provided key: %v", err) + } + if key.Type == models.KeyTypeDeploy { + println("Hi there! You've successfully authenticated with the deploy key named " + key.Name + ", but Gitea does not provide shell access.") + } else { + println("Hi there: " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Gitea does not provide shell access.") + } println("If this is unexpected, please log in with password and setup Gitea under another user.") return nil } @@ -152,41 +145,19 @@ func runServ(c *cli.Context) error { fail("Error while trying to create PPROF_DATA_PATH", "Error while trying to create PPROF_DATA_PATH: %v", err) } - stopCPUProfiler := pprof.DumpCPUProfileForUsername(setting.PprofDataPath, username) + stopCPUProfiler, err := pprof.DumpCPUProfileForUsername(setting.PprofDataPath, username) + if err != nil { + fail("Internal Server Error", "Unable to start CPU profile: %v", err) + } defer func() { stopCPUProfiler() - pprof.DumpMemProfileForUsername(setting.PprofDataPath, username) + err := pprof.DumpMemProfileForUsername(setting.PprofDataPath, username) + if err != nil { + fail("Internal Server Error", "Unable to dump Mem Profile: %v", err) + } }() } - var ( - isWiki bool - unitType = models.UnitTypeCode - unitName = "code" - ) - if strings.HasSuffix(reponame, ".wiki") { - isWiki = true - unitType = models.UnitTypeWiki - unitName = "wiki" - reponame = reponame[:len(reponame)-5] - } - - os.Setenv(models.EnvRepoUsername, username) - if isWiki { - os.Setenv(models.EnvRepoIsWiki, "true") - } else { - os.Setenv(models.EnvRepoIsWiki, "false") - } - os.Setenv(models.EnvRepoName, reponame) - - repo, err := private.GetRepositoryByOwnerAndName(username, reponame) - if err != nil { - if strings.Contains(err.Error(), "Failed to get repository: repository does not exist") { - fail(accessDenied, "Repository does not exist: %s/%s", username, reponame) - } - fail("Internal error", "Failed to get repository: %v", err) - } - requestedMode, has := allowedCommands[verb] if !has { fail("Unknown git command", "Unknown git command %s", verb) @@ -202,97 +173,37 @@ func runServ(c *cli.Context) error { } } - // Prohibit push to mirror repositories. - if requestedMode > models.AccessModeRead && repo.IsMirror { - fail("mirror repository is read-only", "") - } - - // Allow anonymous clone for public repositories. - var ( - keyID int64 - user *models.User - ) - if requestedMode == models.AccessModeWrite || repo.IsPrivate || setting.Service.RequireSignInView { - keys := strings.Split(c.Args()[0], "-") - if len(keys) != 2 { - fail("Key ID format error", "Invalid key argument: %s", c.Args()[0]) - } - - key, err := private.GetPublicKeyByID(com.StrTo(keys[1]).MustInt64()) - if err != nil { - fail("Invalid key ID", "Invalid key ID[%s]: %v", c.Args()[0], err) - } - keyID = key.ID - - // Check deploy key or user key. - if key.Type == models.KeyTypeDeploy { - // Now we have to get the deploy key for this repo - deployKey, err := private.GetDeployKey(key.ID, repo.ID) - if err != nil { - fail("Key access denied", "Failed to access internal api: [key_id: %d, repo_id: %d]", key.ID, repo.ID) - } - - if deployKey == nil { - fail("Key access denied", "Deploy key access denied: [key_id: %d, repo_id: %d]", key.ID, repo.ID) - } - - if deployKey.Mode < requestedMode { - fail("Key permission denied", "Cannot push with read-only deployment key: %d to repo_id: %d", key.ID, repo.ID) - } - - // Update deploy key activity. - if err = private.UpdateDeployKeyUpdated(key.ID, repo.ID); err != nil { - fail("Internal error", "UpdateDeployKey: %v", err) - } - - // FIXME: Deploy keys aren't really the owner of the repo pushing changes - // however we don't have good way of representing deploy keys in hook.go - // so for now use the owner - os.Setenv(models.EnvPusherName, username) - os.Setenv(models.EnvPusherID, fmt.Sprintf("%d", repo.OwnerID)) - } else { - user, err = private.GetUserByKeyID(key.ID) - if err != nil { - fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err) - } - - if !user.IsActive || user.ProhibitLogin { - fail("Your account is not active or has been disabled by Administrator", - "User %s is disabled and have no access to repository %s", - user.Name, repoPath) - } - - mode, err := private.CheckUnitUser(user.ID, repo.ID, user.IsAdmin, unitType) - if err != nil { - fail("Internal error", "Failed to check access: %v", err) - } else if *mode < requestedMode { - clientMessage := accessDenied - if *mode >= models.AccessModeRead { - clientMessage = "You do not have sufficient authorization for this action" - } - fail(clientMessage, - "User %s does not have level %v access to repository %s's "+unitName, - user.Name, requestedMode, repoPath) - } - - os.Setenv(models.EnvPusherName, user.Name) - os.Setenv(models.EnvPusherID, fmt.Sprintf("%d", user.ID)) - } + results, err := private.ServCommand(keyID, username, reponame, requestedMode, verb, lfsVerb) + if err != nil { + if private.IsErrServCommand(err) { + errServCommand := err.(private.ErrServCommand) + if errServCommand.StatusCode != http.StatusInternalServerError { + fail("Unauthorized", "%s", errServCommand.Error()) + } else { + fail("Internal Server Error", "%s", errServCommand.Error()) + } + } + fail("Internal Server Error", "%s", err.Error()) } + os.Setenv(models.EnvRepoIsWiki, strconv.FormatBool(results.IsWiki)) + os.Setenv(models.EnvRepoName, results.RepoName) + os.Setenv(models.EnvRepoUsername, results.OwnerName) + os.Setenv(models.EnvPusherName, results.UserName) + os.Setenv(models.EnvPusherID, strconv.FormatInt(results.UserID, 10)) + os.Setenv(models.ProtectedBranchRepoID, strconv.FormatInt(results.RepoID, 10)) + os.Setenv(models.ProtectedBranchPRID, fmt.Sprintf("%d", 0)) //LFS token authentication if verb == lfsAuthenticateVerb { - url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, username, repo.Name) + url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName)) now := time.Now() claims := jwt.MapClaims{ - "repo": repo.ID, + "repo": results.RepoID, "op": lfsVerb, "exp": now.Add(setting.LFS.HTTPAuthExpiry).Unix(), "nbf": now.Unix(), - } - if user != nil { - claims["user"] = user.ID + "user": results.UserID, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) @@ -313,7 +224,6 @@ func runServ(c *cli.Context) error { if err != nil { fail("Internal error", "Failed to encode LFS json response: %v", err) } - return nil } @@ -329,13 +239,6 @@ func runServ(c *cli.Context) error { } else { gitcmd = exec.Command(verb, repoPath) } - if isWiki { - if err = private.InitWiki(repo.ID); err != nil { - fail("Internal error", "Failed to init wiki repo: %v", err) - } - } - - os.Setenv(models.ProtectedBranchRepoID, fmt.Sprintf("%d", repo.ID)) gitcmd.Dir = setting.RepoRootPath gitcmd.Stdout = os.Stdout @@ -346,9 +249,9 @@ func runServ(c *cli.Context) error { } // Update user key activity. - if keyID > 0 { - if err = private.UpdatePublicKeyUpdated(keyID); err != nil { - fail("Internal error", "UpdatePublicKey: %v", err) + if results.KeyID > 0 { + if err = private.UpdatePublicKeyInRepo(results.KeyID, results.RepoID); err != nil { + fail("Internal error", "UpdatePublicKeyInRepo: %v", err) } } diff --git a/cmd/web.go b/cmd/web.go index 6da6ec942e..d8bcba76d1 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -5,7 +5,6 @@ package cmd import ( - "crypto/tls" "fmt" "net" "net/http" @@ -15,7 +14,6 @@ import ( "strings" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/markup/external" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers/routes" @@ -83,11 +81,9 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler) } }() server := &http.Server{ - Addr: listenAddr, - Handler: m, - TLSConfig: &tls.Config{ - GetCertificate: certManager.GetCertificate, - }, + Addr: listenAddr, + Handler: m, + TLSConfig: certManager.TLSConfig(), } return server.ListenAndServeTLS("", "") } @@ -111,8 +107,6 @@ func runWeb(ctx *cli.Context) error { routers.GlobalInit() - external.RegisterParsers() - m := routes.NewMacaron() routes.RegisterRoutes(m) @@ -181,11 +175,16 @@ func runWeb(ctx *cli.Context) error { } err = runHTTPS(listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m)) case setting.FCGI: - listener, err := net.Listen("tcp", listenAddr) + var listener net.Listener + listener, err = net.Listen("tcp", listenAddr) if err != nil { log.Fatal("Failed to bind %s: %v", listenAddr, err) } - defer listener.Close() + defer func() { + if err := listener.Close(); err != nil { + log.Fatal("Failed to stop server: %v", err) + } + }() err = fcgi.Serve(listener, context2.ClearHandler(m)) case setting.UnixSocket: if err := os.Remove(listenAddr); err != nil && !os.IsNotExist(err) { diff --git a/contrib/fhs-compliant-script/gitea b/contrib/fhs-compliant-script/gitea new file mode 100755 index 0000000000..28ce651aab --- /dev/null +++ b/contrib/fhs-compliant-script/gitea @@ -0,0 +1,42 @@ +#!/bin/bash + +######################################################################## +# This script some defaults for gitea to run in a FHS compliant manner # +######################################################################## + +# It assumes that you place this script as gitea in /usr/bin +# +# And place the original in /usr/lib/gitea with working files in /var/lib/gitea +# and main configuration in /etc/gitea/app.ini +GITEA="/usr/lib/gitea/gitea" +WORK_DIR="/var/lib/gitea" +APP_INI="/etc/gitea/app.ini" + +APP_INI_SET="" +for i in "$@"; do + case "$i" in + "-c") + APP_INI_SET=1 + ;; + "-c="*) + APP_INI_SET=1 + ;; + "--config") + APP_INI_SET=1 + ;; + "--config="*) + APP_INI_SET=1 + ;; + *) + ;; + esac +done + +if [ -z "$APP_INI_SET" ]; then + CONF_ARG="-c \"$APP_INI\"" +fi + +# Provide FHS compliant defaults to +GITEA_WORK_DIR="${GITEA_WORK_DIR:-$WORK_DIR}" "$GITEA" $CONF_ARG "$@" + + diff --git a/contrib/pr/checkout.go b/contrib/pr/checkout.go index 607a503189..4b39c8e9f2 100644 --- a/contrib/pr/checkout.go +++ b/contrib/pr/checkout.go @@ -20,9 +20,13 @@ import ( "strconv" "time" + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/external" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers/routes" + "github.com/Unknwon/com" "github.com/go-xorm/xorm" context2 "github.com/gorilla/context" @@ -30,9 +34,6 @@ import ( "gopkg.in/src-d/go-git.v4/config" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/testfixtures.v2" - - "code.gitea.io/gitea/models" - "code.gitea.io/gitea/modules/setting" ) var codeFilePath = "contrib/pr/checkout.go" @@ -43,7 +44,7 @@ func runPR() { if err != nil { log.Fatal(err) } - setting.SetCustomPathAndConf("", "") + setting.SetCustomPathAndConf("", "", "") setting.NewContext() setting.RepoRootPath, err = ioutil.TempDir(os.TempDir(), "repos") @@ -90,8 +91,7 @@ func runPR() { routers.NewServices() //x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared") - var helper testfixtures.Helper - helper = &testfixtures.SQLite{} + var helper testfixtures.Helper = &testfixtures.SQLite{} models.NewEngine(func(_ *xorm.Engine) error { return nil }) @@ -113,6 +113,7 @@ func runPR() { log.Printf("[PR] Setting up router\n") //routers.GlobalInit() external.RegisterParsers() + markup.Init() m := routes.NewMacaron() routes.RegisterRoutes(m) diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index 6f7844962b..e44cc90a4b 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -74,6 +74,23 @@ WORK_IN_PROGRESS_PREFIXES=WIP:,[WIP] ; List of reasons why a Pull Request or Issue can be locked LOCK_REASONS=Too heated,Off-topic,Resolved,Spam +[cors] +; More information about CORS can be found here: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#The_HTTP_response_headers +; enable cors headers (disabled by default) +ENABLED=false +; scheme of allowed requests +SCHEME=http +; list of requesting domains that are allowed +ALLOW_DOMAIN=* +; allow subdomains of headers listed above to request +ALLOW_SUBDOMAIN=false +; list of methods allowed to request +METHODS=GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS +; max time to cache response +MAX_AGE=10m +; allow request with credentials +ALLOW_CREDENTIALS=false + [ui] ; Number of repositories that are displayed on one explore page EXPLORE_PAGING_NUM = 20 @@ -243,6 +260,9 @@ PASSWD = ; For Postgres, either "disable" (default), "require", or "verify-full" ; For MySQL, either "false" (default), "true", or "skip-verify" SSL_MODE = disable +; For MySQL only, either "utf8" or "utf8mb4", default is "utf8". +; NOTICE: for "utf8mb4" you must use MySQL InnoDB > 5.6. Gitea is unable to check this. +CHARSET = utf8 ; For "sqlite3" and "tidb", use an absolute path when you start gitea as service PATH = data/gitea.db ; For "sqlite3" only. Query timeout @@ -299,6 +319,8 @@ MIN_PASSWORD_LENGTH = 6 IMPORT_LOCAL_PATHS = false ; Set to true to prevent all users (including admin) from creating custom git hooks DISABLE_GIT_HOOKS = false +; Password Hash algorithm, either "pbkdf2", "argon2", "scrypt" or "bcrypt" +PASSWORD_HASH_ALGO = pbkdf2 [openid] ; @@ -484,10 +506,18 @@ SESSION_LIFE_TIME = 86400 [picture] AVATAR_UPLOAD_PATH = data/avatars -; Max Width and Height of uploaded avatars. This is to limit the amount of RAM -; used when resizing the image. +REPOSITORY_AVATAR_UPLOAD_PATH = data/repo-avatars +; How Gitea deals with missing repository avatars +; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used +REPOSITORY_AVATAR_FALLBACK = none +REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png +; Max Width and Height of uploaded avatars. +; This is to limit the amount of RAM used when resizing the image. AVATAR_MAX_WIDTH = 4096 AVATAR_MAX_HEIGHT = 3072 +; Maximum alloved file size for uploaded avatars. +; This is to limit the amount of RAM used when resizing the image. +AVATAR_MAX_FILE_SIZE = 1048576 ; Chinese users can choose "duoshuo" ; or a custom avatar source, like: http://cn.gravatar.com/avatar/ GRAVATAR_SOURCE = gravatar @@ -640,6 +670,8 @@ SCHEDULE = @every 24h UPDATE_EXISTING = true [git] +; The path of git executable. If empty, Gitea searches through the PATH environment. +PATH = ; Disables highlight of added and removed changes DISABLE_DIFF_HIGHLIGHT = false ; Max number of lines allowed in a single file in diff view @@ -651,6 +683,8 @@ MAX_GIT_DIFF_FILES = 100 ; Arguments for command 'git gc', e.g. "--aggressive --auto" ; see more on http://git-scm.com/docs/git-gc/ GC_ARGS = +; If use git wire protocol version 2 when git version >= 2.18, default is true, set to false when you always want git wire protocol version 1 +EnableAutoGitWireProtocol = true ; Operation timeout in seconds [git.timeout] @@ -681,7 +715,7 @@ DEFAULT_MAX_BLOB_SIZE = 10485760 [oauth2] ; Enables OAuth2 provider -ENABLED = true +ENABLE = true ; Lifetime of an OAuth2 access token in seconds ACCESS_TOKEN_EXPIRATION_TIME=3600 ; Lifetime of an OAuth2 access token in hours diff --git a/docker/root/etc/profile.d/gitea.sh b/docker/root/etc/profile.d/gitea.sh deleted file mode 100755 index 41afd4cfb8..0000000000 --- a/docker/root/etc/profile.d/gitea.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -export GITEA_CUSTOM=/data/gitea diff --git a/docker/root/etc/s6/gitea/setup b/docker/root/etc/s6/gitea/setup index 2b0fb6c37b..c4fbf5d65e 100755 --- a/docker/root/etc/s6/gitea/setup +++ b/docker/root/etc/s6/gitea/setup @@ -6,12 +6,16 @@ if [ ! -d /data/git/.ssh ]; then fi if [ ! -f /data/git/.ssh/environment ]; then - echo "GITEA_CUSTOM=/data/gitea" >| /data/git/.ssh/environment + echo "GITEA_CUSTOM=$GITEA_CUSTOM" >| /data/git/.ssh/environment chmod 600 /data/git/.ssh/environment + +elif ! grep -q "^GITEA_CUSTOM=$GITEA_CUSTOM$" /data/git/.ssh/environment; then + sed -i /^GITEA_CUSTOM=/d /data/git/.ssh/environment + echo "GITEA_CUSTOM=$GITEA_CUSTOM" >> /data/git/.ssh/environment fi -if [ ! -f /data/gitea/conf/app.ini ]; then - mkdir -p /data/gitea/conf +if [ ! -f ${GITEA_CUSTOM}/conf/app.ini ]; then + mkdir -p ${GITEA_CUSTOM}/conf # Set INSTALL_LOCK to true only if SECRET_KEY is not empty and # INSTALL_LOCK is empty @@ -27,6 +31,7 @@ if [ ! -f /data/gitea/conf/app.ini ]; then ROOT_URL=${ROOT_URL:-""} \ DISABLE_SSH=${DISABLE_SSH:-"false"} \ SSH_PORT=${SSH_PORT:-"22"} \ + LFS_START_SERVER=${LFS_START_SERVER:-"false"} \ DB_TYPE=${DB_TYPE:-"sqlite3"} \ DB_HOST=${DB_HOST:-"localhost:3306"} \ DB_NAME=${DB_NAME:-"gitea"} \ @@ -36,7 +41,9 @@ if [ ! -f /data/gitea/conf/app.ini ]; then DISABLE_REGISTRATION=${DISABLE_REGISTRATION:-"false"} \ REQUIRE_SIGNIN_VIEW=${REQUIRE_SIGNIN_VIEW:-"false"} \ SECRET_KEY=${SECRET_KEY:-""} \ - envsubst < /etc/templates/app.ini > /data/gitea/conf/app.ini + envsubst < /etc/templates/app.ini > ${GITEA_CUSTOM}/conf/app.ini + + chown ${USER}:git ${GITEA_CUSTOM}/conf/app.ini fi # only chown if current owner is not already the gitea ${USER}. No recursive check to save time diff --git a/docker/root/etc/s6/openssh/setup b/docker/root/etc/s6/openssh/setup index f8ef816a95..10d195b74f 100755 --- a/docker/root/etc/s6/openssh/setup +++ b/docker/root/etc/s6/openssh/setup @@ -24,6 +24,13 @@ if [ ! -f /data/ssh/ssh_host_ecdsa_key ]; then ssh-keygen -t ecdsa -b 256 -f /data/ssh/ssh_host_ecdsa_key -N "" > /dev/null fi +if [ -d /etc/ssh ]; then + SSH_PORT=${SSH_PORT:-"22"} \ + envsubst < /etc/templates/sshd_config > /etc/ssh/sshd_config + + chmod 0644 /etc/ssh/sshd_config +fi + chown root:root /data/ssh/* chmod 0700 /data/ssh chmod 0600 /data/ssh/* diff --git a/docker/root/etc/templates/app.ini b/docker/root/etc/templates/app.ini index 589271b4a0..212cd854d3 100644 --- a/docker/root/etc/templates/app.ini +++ b/docker/root/etc/templates/app.ini @@ -17,6 +17,7 @@ HTTP_PORT = $HTTP_PORT ROOT_URL = $ROOT_URL DISABLE_SSH = $DISABLE_SSH SSH_PORT = $SSH_PORT +LFS_START_SERVER = $LFS_START_SERVER LFS_CONTENT_PATH = /data/git/lfs [database] @@ -35,6 +36,7 @@ PROVIDER_CONFIG = /data/gitea/sessions [picture] AVATAR_UPLOAD_PATH = /data/gitea/avatars +REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars [attachment] PATH = /data/gitea/attachments diff --git a/docker/root/etc/ssh/sshd_config b/docker/root/etc/templates/sshd_config similarity index 97% rename from docker/root/etc/ssh/sshd_config rename to docker/root/etc/templates/sshd_config index 6af082c419..ba92e236e1 100644 --- a/docker/root/etc/ssh/sshd_config +++ b/docker/root/etc/templates/sshd_config @@ -1,4 +1,4 @@ -Port 22 +Port ${SSH_PORT} Protocol 2 AddressFamily any diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 87a92eb60d..61905f8ad8 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -15,8 +15,8 @@ menu: # Configuration Cheat Sheet -This is a cheat sheet for the Gitea configuration file. It contains most settings -that can configured as well as their default values. +This is a cheat sheet for the Gitea configuration file. It contains most of the settings +that can be configured as well as their default values. Any changes to the Gitea configuration file should be made in `custom/conf/app.ini` or any corresponding location. When installing from a distribution, this will @@ -76,6 +76,16 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `LOCK_REASONS`: **Too heated,Off-topic,Resolved,Spam**: A list of reasons why a Pull Request or Issue can be locked +## CORS (`cors`) + +- `ENABLED`: **false**: enable cors headers (disabled by default) +- `SCHEME`: **http**: scheme of allowed requests +- `ALLOW_DOMAIN`: **\***: list of requesting domains that are allowed +- `ALLOW_SUBDOMAIN`: **false**: allow subdomains of headers listed above to request +- `METHODS`: **GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS**: list of methods allowed to request +- `MAX_AGE`: **10m**: max time to cache response +- `ALLOW_CREDENTIALS`: **false**: allow request with credentials + ## UI (`ui`) - `EXPLORE_PAGING_NUM`: **20**: Number of repositories that are shown in one explore page. @@ -150,6 +160,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `USER`: **root**: Database username. - `PASSWD`: **\**: Database user password. Use \`your password\` for quoting if you use special characters in the password. - `SSL_MODE`: **disable**: For PostgreSQL and MySQL only. +- `CHARSET`: **utf8**: For MySQL only, either "utf8" or "utf8mb4", default is "utf8". NOTICE: for "utf8mb4" you must use MySQL InnoDB > 5.6. Gitea is unable to check this. - `PATH`: **data/gitea.db**: For SQLite3 only, the database file path. - `LOG_SQL`: **true**: Log the executed SQL. - `DB_RETRIES`: **10**: How many ORM init / DB connect attempts allowed. @@ -184,6 +195,9 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `DISABLE_GIT_HOOKS`: **false**: Set to `true` to prevent all users (including admin) from creating custom git hooks. - `IMPORT_LOCAL_PATHS`: **false**: Set to `false` to prevent all users (including admin) from importing local path on server. +- `INTERNAL_TOKEN`: **\**: Secret used to validate communication within Gitea binary. +- `INTERNAL_TOKEN_URI`: ****: Instead of defining internal token in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: `file:/etc/gitea/internal_token`) +- `PASSWORD_HASH_ALGO`: **pbkdf2**: The hash algorithm to use \[pbkdf2, argon2, scrypt, bcrypt\]. ## OpenID (`openid`) @@ -203,6 +217,9 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. Requires `Mailer` to be enabled. - `DISABLE_REGISTRATION`: **false**: Disable registration, after which only admin can create accounts for users. +- `REQUIRE_EXTERNAL_REGISTRATION_PASSWORD`: **false**: Enable this to force externally created + accounts (via GitHub, OpenID Connect, etc) to create a password. Warning: enabling this will + decrease security, so you should only enable it if you know what you're doing. - `REQUIRE_SIGNIN_VIEW`: **false**: Enable this to force users to log in to view any page. - `ENABLE_NOTIFY_MAIL`: **false**: Enable this to send e-mail to watchers of a repository when something happens, like creating issues. Requires `Mailer` to be enabled. @@ -212,6 +229,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `ENABLE_REVERSE_PROXY_EMAIL`: **false**: Enable this to allow to auto-registration with a provided email rather than a generated email. - `ENABLE_CAPTCHA`: **false**: Enable this to use captcha validation for registration. +- `REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA`: **false**: Enable this to force captcha validation + even for External Accounts (i.e. GitHub, OpenID Connect, etc). You must `ENABLE_CAPTCHA` also. - `CAPTCHA_TYPE`: **image**: \[image, recaptcha\] - `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha. - `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha. @@ -279,7 +298,16 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `DISABLE_GRAVATAR`: **false**: Enable this to use local avatars only. - `ENABLE_FEDERATED_AVATAR`: **false**: Enable support for federated avatars (see [http://www.libravatar.org](http://www.libravatar.org)). -- `AVATAR_UPLOAD_PATH`: **data/avatars**: Path to store local and cached files. +- `AVATAR_UPLOAD_PATH`: **data/avatars**: Path to store user avatar image files. +- `REPOSITORY_AVATAR_UPLOAD_PATH`: **data/repo-avatars**: Path to store repository avatar image files. +- `REPOSITORY_AVATAR_FALLBACK`: **none**: How Gitea deals with missing repository avatars + - none = no avatar will be displayed + - random = random avatar will be generated + - image = default image will be used (which is set in `REPOSITORY_AVATAR_DEFAULT_IMAGE`) +- `REPOSITORY_AVATAR_FALLBACK_IMAGE`: **/img/repo_default.png**: Image used as default repository avatar (if `REPOSITORY_AVATAR_FALLBACK` is set to image and none was uploaded) +- `AVATAR_MAX_WIDTH`: **4096**: Maximum avatar image width in pixels. +- `AVATAR_MAX_HEIGHT`: **3072**: Maximum avatar image height in pixels. +- `AVATAR_MAX_FILE_SIZE`: **1048576** (1Mb): Maximum avatar image file size in bytes. ## Attachment (`attachment`) @@ -324,7 +352,7 @@ NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false` ### Console log mode (`log.console`, `log.console.*`, or `MODE=console`) -- For the console logger `COLORIZE` will default to `true` if not on windows. +- For the console logger `COLORIZE` will default to `true` if not on windows or the terminal is determined to be able to color. - `STDERR`: **false**: Use Stderr instead of Stdout. ### File log mode (`log.file`, `log.file.*` or `MODE=file`) @@ -334,7 +362,6 @@ NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false` - `MAX_SIZE_SHIFT`: **28**: Maximum size shift of a single file, 28 represents 256Mb. - `DAILY_ROTATE`: **true**: Rotate logs daily. - `MAX_DAYS`: **7**: Delete the log file after n days -- NB: `COLORIZE`: will default to `true` if not on windows. - `COMPRESS`: **true**: Compress old log files by default with gzip - `COMPRESSION_LEVEL`: **-1**: Compression level @@ -382,10 +409,12 @@ NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false` ## Git (`git`) +- `PATH`: **""**: The path of git executable. If empty, Gitea searches through the PATH environment. - `MAX_GIT_DIFF_LINES`: **100**: Max number of lines allowed of a single file in diff view. - `MAX_GIT_DIFF_LINE_CHARACTERS`: **5000**: Max character count per line highlighted in diff view. - `MAX_GIT_DIFF_FILES`: **100**: Max number of files shown in diff view. - `GC_ARGS`: **\**: Arguments for command `git gc`, e.g. `--aggressive --auto`. See more on http://git-scm.com/docs/git-gc/ +- `ENABLE_AUTO_GIT_WIRE_PROTOCOL`: **true**: If use git wire protocol version 2 when git version >= 2.18, default is true, set to false when you always want git wire protocol version 1 ## Git - Timeout settings (`git.timeout`) - `DEFAUlT`: **360**: Git operations default timeout seconds. @@ -397,7 +426,7 @@ NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false` ## Metrics (`metrics`) -- `ENABLED`: **false**: Enables /metrics endpoint for prometheus. +- `ENABLED`: **false**: Enables /metrics endpoint for prometheus. - `TOKEN`: **\**: You need to specify the token, if you want to include in the authorization the metrics . The same token need to be used in prometheus parameters `bearer_token` or `bearer_token_file`. ## API (`api`) @@ -410,7 +439,7 @@ NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false` ## OAuth2 (`oauth2`) -- `ENABLED`: **true**: Enables OAuth2 provider. +- `ENABLE`: **true**: Enables OAuth2 provider. - `ACCESS_TOKEN_EXPIRATION_TIME`: **3600**: Lifetime of an OAuth2 access token in seconds - `REFRESH_TOKEN_EXPIRATION_TIME`: **730**: Lifetime of an OAuth2 access token in hours - `INVALIDATE_REFRESH_TOKEN`: **false**: Check if refresh token got already used diff --git a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md index 021233f2d2..b9a16dd844 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md +++ b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md @@ -78,7 +78,8 @@ menu: - `NAME`: 数据库名称。 - `USER`: 数据库用户名。 - `PASSWD`: 数据库用户密码。 -- `SSL_MODE`: PostgreSQL数据库是否启用SSL模式。 +- `SSL_MODE`: MySQL 或 PostgreSQL数据库是否启用SSL模式。 +- `CHARSET`: **utf8**: 仅当数据库为 MySQL 时有效, 可以为 "utf8" 或 "utf8mb4"。注意:如果使用 "utf8mb4",你的 MySQL InnoDB 版本必须在 5.6 以上。 - `PATH`: Tidb 或者 SQLite3 数据文件存放路径。 - `LOG_SQL`: **true**: 显示生成的SQL,默认为真。 @@ -209,6 +210,7 @@ menu: - `CLONE`: **300**: 内部仓库间克隆的超时时间,单位秒 - `PULL`: **300**: 内部仓库间拉取的超时时间,单位秒 - `GC`: **60**: git仓库GC的超时时间,单位秒 +- `ENABLE_AUTO_GIT_WIRE_PROTOCOL`: **true**: 是否根据 Git Wire Protocol协议支持情况自动切换版本,当 git 版本在 2.18 及以上时会自动切换到版本2。为 `false` 则不切换。 ## API (`api`) diff --git a/docs/content/doc/advanced/customizing-gitea.en-us.md b/docs/content/doc/advanced/customizing-gitea.en-us.md index 69cf58b3bf..460ea61eac 100644 --- a/docs/content/doc/advanced/customizing-gitea.en-us.md +++ b/docs/content/doc/advanced/customizing-gitea.en-us.md @@ -98,6 +98,20 @@ Apart from `extra_links.tmpl` and `extra_tabs.tmpl`, there are other useful temp - `body_outer_post.tmpl`, before the bottom `