@@ -10,6 +10,7 @@ parserOptions: | |||
plugins: | |||
- "@eslint-community/eslint-plugin-eslint-comments" | |||
- "@stylistic/eslint-plugin-js" | |||
- eslint-plugin-array-func | |||
- eslint-plugin-i | |||
- eslint-plugin-jquery | |||
@@ -114,11 +115,74 @@ rules: | |||
"@eslint-community/eslint-comments/no-unused-enable": [2] | |||
"@eslint-community/eslint-comments/no-use": [0] | |||
"@eslint-community/eslint-comments/require-description": [0] | |||
"@stylistic/js/array-bracket-newline": [0] | |||
"@stylistic/js/array-bracket-spacing": [2, never] | |||
"@stylistic/js/array-element-newline": [0] | |||
"@stylistic/js/arrow-parens": [2, always] | |||
"@stylistic/js/arrow-spacing": [2, {before: true, after: true}] | |||
"@stylistic/js/block-spacing": [0] | |||
"@stylistic/js/brace-style": [2, 1tbs, {allowSingleLine: true}] | |||
"@stylistic/js/comma-dangle": [2, only-multiline] | |||
"@stylistic/js/comma-spacing": [2, {before: false, after: true}] | |||
"@stylistic/js/comma-style": [2, last] | |||
"@stylistic/js/computed-property-spacing": [2, never] | |||
"@stylistic/js/dot-location": [2, property] | |||
"@stylistic/js/eol-last": [2] | |||
"@stylistic/js/func-call-spacing": [2, never] | |||
"@stylistic/js/function-call-argument-newline": [0] | |||
"@stylistic/js/function-paren-newline": [0] | |||
"@stylistic/js/generator-star-spacing": [0] | |||
"@stylistic/js/implicit-arrow-linebreak": [0] | |||
"@stylistic/js/indent": [2, 2, {ignoreComments: true, SwitchCase: 1}] | |||
"@stylistic/js/key-spacing": [2] | |||
"@stylistic/js/keyword-spacing": [2] | |||
"@stylistic/js/linebreak-style": [2, unix] | |||
"@stylistic/js/lines-around-comment": [0] | |||
"@stylistic/js/lines-between-class-members": [0] | |||
"@stylistic/js/max-len": [0] | |||
"@stylistic/js/max-statements-per-line": [0] | |||
"@stylistic/js/multiline-ternary": [0] | |||
"@stylistic/js/new-parens": [2] | |||
"@stylistic/js/newline-per-chained-call": [0] | |||
"@stylistic/js/no-confusing-arrow": [0] | |||
"@stylistic/js/no-extra-parens": [0] | |||
"@stylistic/js/no-extra-semi": [2] | |||
"@stylistic/js/no-floating-decimal": [0] | |||
"@stylistic/js/no-mixed-operators": [0] | |||
"@stylistic/js/no-mixed-spaces-and-tabs": [2] | |||
"@stylistic/js/no-multi-spaces": [2, {ignoreEOLComments: true, exceptions: {Property: true}}] | |||
"@stylistic/js/no-multiple-empty-lines": [2, {max: 1, maxEOF: 0, maxBOF: 0}] | |||
"@stylistic/js/no-tabs": [2] | |||
"@stylistic/js/no-trailing-spaces": [2] | |||
"@stylistic/js/no-whitespace-before-property": [2] | |||
"@stylistic/js/nonblock-statement-body-position": [2] | |||
"@stylistic/js/object-curly-newline": [0] | |||
"@stylistic/js/object-curly-spacing": [2, never] | |||
"@stylistic/js/object-property-newline": [0] | |||
"@stylistic/js/one-var-declaration-per-line": [0] | |||
"@stylistic/js/operator-linebreak": [2, after] | |||
"@stylistic/js/padded-blocks": [2, never] | |||
"@stylistic/js/padding-line-between-statements": [0] | |||
"@stylistic/js/quote-props": [0] | |||
"@stylistic/js/quotes": [2, single, {avoidEscape: true, allowTemplateLiterals: true}] | |||
"@stylistic/js/rest-spread-spacing": [2, never] | |||
"@stylistic/js/semi": [2, always, {omitLastInOneLineBlock: true}] | |||
"@stylistic/js/semi-spacing": [2, {before: false, after: true}] | |||
"@stylistic/js/semi-style": [2, last] | |||
"@stylistic/js/space-before-blocks": [2, always] | |||
"@stylistic/js/space-before-function-paren": [0] | |||
"@stylistic/js/space-in-parens": [2, never] | |||
"@stylistic/js/space-infix-ops": [2] | |||
"@stylistic/js/space-unary-ops": [2] | |||
"@stylistic/js/spaced-comment": [2, always] | |||
"@stylistic/js/switch-colon-spacing": [2] | |||
"@stylistic/js/template-curly-spacing": [2, never] | |||
"@stylistic/js/template-tag-spacing": [2, never] | |||
"@stylistic/js/wrap-iife": [2, inside] | |||
"@stylistic/js/wrap-regex": [0] | |||
"@stylistic/js/yield-star-spacing": [2, after] | |||
accessor-pairs: [2] | |||
array-bracket-newline: [0] | |||
array-bracket-spacing: [2, never] | |||
array-callback-return: [2, {checkForEach: true}] | |||
array-element-newline: [0] | |||
array-func/avoid-reverse: [2] | |||
array-func/from-map: [2] | |||
array-func/no-unnecessary-this-arg: [2] | |||
@@ -126,18 +190,11 @@ rules: | |||
array-func/prefer-flat-map: [0] # handled by unicorn/prefer-array-flat-map | |||
array-func/prefer-flat: [0] # handled by unicorn/prefer-array-flat | |||
arrow-body-style: [0] | |||
arrow-parens: [2, always] | |||
arrow-spacing: [2, {before: true, after: true}] | |||
block-scoped-var: [2] | |||
brace-style: [2, 1tbs, {allowSingleLine: true}] | |||
camelcase: [0] | |||
capitalized-comments: [0] | |||
class-methods-use-this: [0] | |||
comma-dangle: [2, only-multiline] | |||
comma-spacing: [2, {before: false, after: true}] | |||
comma-style: [2, last] | |||
complexity: [0] | |||
computed-property-spacing: [2, never] | |||
consistent-return: [0] | |||
consistent-this: [0] | |||
constructor-super: [2] | |||
@@ -145,25 +202,18 @@ rules: | |||
default-case-last: [2] | |||
default-case: [0] | |||
default-param-last: [0] | |||
dot-location: [2, property] | |||
dot-notation: [0] | |||
eol-last: [2] | |||
eqeqeq: [2] | |||
for-direction: [2] | |||
func-call-spacing: [2, never] | |||
func-name-matching: [2] | |||
func-names: [0] | |||
func-style: [0] | |||
function-call-argument-newline: [0] | |||
function-paren-newline: [0] | |||
generator-star-spacing: [0] | |||
getter-return: [2] | |||
grouped-accessor-pairs: [2] | |||
guard-for-in: [0] | |||
id-blacklist: [0] | |||
id-length: [0] | |||
id-match: [0] | |||
implicit-arrow-linebreak: [0] | |||
i/consistent-type-specifier-style: [0] | |||
i/default: [0] | |||
i/dynamic-import-chunkname: [0] | |||
@@ -207,7 +257,6 @@ rules: | |||
i/order: [0] | |||
i/prefer-default-export: [0] | |||
i/unambiguous: [0] | |||
indent: [2, 2, {SwitchCase: 1}] | |||
init-declarations: [0] | |||
jquery/no-ajax-events: [2] | |||
jquery/no-ajax: [0] | |||
@@ -258,27 +307,17 @@ rules: | |||
jquery/no-val: [0] | |||
jquery/no-when: [2] | |||
jquery/no-wrap: [2] | |||
key-spacing: [2] | |||
keyword-spacing: [2] | |||
line-comment-position: [0] | |||
linebreak-style: [2, unix] | |||
lines-around-comment: [0] | |||
lines-between-class-members: [0] | |||
logical-assignment-operators: [0] | |||
max-classes-per-file: [0] | |||
max-depth: [0] | |||
max-len: [0] | |||
max-lines-per-function: [0] | |||
max-lines: [0] | |||
max-nested-callbacks: [0] | |||
max-params: [0] | |||
max-statements-per-line: [0] | |||
max-statements: [0] | |||
multiline-comment-style: [2, separate-lines] | |||
multiline-ternary: [0] | |||
new-cap: [0] | |||
new-parens: [2] | |||
newline-per-chained-call: [0] | |||
no-alert: [0] | |||
no-array-constructor: [2] | |||
no-async-promise-executor: [0] | |||
@@ -290,7 +329,6 @@ rules: | |||
no-class-assign: [2] | |||
no-compare-neg-zero: [2] | |||
no-cond-assign: [2, except-parens] | |||
no-confusing-arrow: [0] | |||
no-console: [1, {allow: [debug, info, warn, error]}] | |||
no-const-assign: [2] | |||
no-constant-binary-expression: [2] | |||
@@ -320,10 +358,7 @@ rules: | |||
no-extra-bind: [2] | |||
no-extra-boolean-cast: [2] | |||
no-extra-label: [0] | |||
no-extra-parens: [0] | |||
no-extra-semi: [2] | |||
no-fallthrough: [2] | |||
no-floating-decimal: [0] | |||
no-func-assign: [2] | |||
no-global-assign: [2] | |||
no-implicit-coercion: [2] | |||
@@ -437,10 +472,7 @@ rules: | |||
no-loss-of-precision: [2] | |||
no-magic-numbers: [0] | |||
no-misleading-character-class: [2] | |||
no-mixed-operators: [0] | |||
no-mixed-spaces-and-tabs: [2] | |||
no-multi-assign: [0] | |||
no-multi-spaces: [2, {ignoreEOLComments: true, exceptions: {Property: true}}] | |||
no-multi-str: [2] | |||
no-negated-condition: [0] | |||
no-nested-ternary: [0] | |||
@@ -474,12 +506,10 @@ rules: | |||
no-shadow-restricted-names: [2] | |||
no-shadow: [0] | |||
no-sparse-arrays: [2] | |||
no-tabs: [2] | |||
no-template-curly-in-string: [2] | |||
no-ternary: [0] | |||
no-this-before-super: [2] | |||
no-throw-literal: [2] | |||
no-trailing-spaces: [2] | |||
no-undef-init: [2] | |||
no-undef: [2, {typeof: true}] | |||
no-undefined: [0] | |||
@@ -509,18 +539,12 @@ rules: | |||
no-var: [2] | |||
no-void: [2] | |||
no-warning-comments: [0] | |||
no-whitespace-before-property: [2] | |||
no-with: [0] # handled by no-restricted-syntax | |||
nonblock-statement-body-position: [2] | |||
object-curly-newline: [0] | |||
object-curly-spacing: [2, never] | |||
object-shorthand: [2, always] | |||
one-var-declaration-per-line: [0] | |||
one-var: [0] | |||
operator-assignment: [2, always] | |||
operator-linebreak: [2, after] | |||
padded-blocks: [2, never] | |||
padding-line-between-statements: [0] | |||
prefer-arrow-callback: [2, {allowNamedFunctions: true, allowUnboundThis: true}] | |||
prefer-const: [2, {destructuring: all, ignoreReadBeforeAssign: true}] | |||
prefer-destructuring: [0] | |||
@@ -534,7 +558,6 @@ rules: | |||
prefer-rest-params: [2] | |||
prefer-spread: [2] | |||
prefer-template: [2] | |||
quote-props: [0] | |||
quotes: [2, single, {avoidEscape: true, allowTemplateLiterals: true}] | |||
radix: [2, as-needed] | |||
regexp/confusing-quantifier: [2] | |||
@@ -620,10 +643,6 @@ rules: | |||
require-await: [0] | |||
require-unicode-regexp: [0] | |||
require-yield: [2] | |||
rest-spread-spacing: [2, never] | |||
semi-spacing: [2, {before: false, after: true}] | |||
semi-style: [2, last] | |||
semi: [2, always, {omitLastInOneLineBlock: true}] | |||
sonarjs/cognitive-complexity: [0] | |||
sonarjs/elseif-without-else: [0] | |||
sonarjs/max-switch-cases: [0] | |||
@@ -659,16 +678,8 @@ rules: | |||
sort-imports: [0] | |||
sort-keys: [0] | |||
sort-vars: [0] | |||
space-before-blocks: [2, always] | |||
space-in-parens: [2, never] | |||
space-infix-ops: [2] | |||
space-unary-ops: [2] | |||
spaced-comment: [2, always] | |||
strict: [0] | |||
switch-colon-spacing: [2] | |||
symbol-description: [2] | |||
template-curly-spacing: [2, never] | |||
template-tag-spacing: [2, never] | |||
unicode-bom: [2, never] | |||
unicorn/better-regex: [0] | |||
unicorn/catch-error-name: [0] | |||
@@ -807,7 +818,4 @@ rules: | |||
wc/no-typos: [2] | |||
wc/require-listener-teardown: [2] | |||
wc/tag-name-matches-class: [2] | |||
wrap-iife: [2, inside] | |||
wrap-regex: [0] | |||
yield-star-spacing: [2, after] | |||
yoda: [2, never] |
@@ -29,7 +29,6 @@ linters: | |||
fast: false | |||
run: | |||
go: "1.21" | |||
timeout: 10m | |||
skip-dirs: | |||
- node_modules | |||
@@ -75,7 +74,6 @@ linters-settings: | |||
- name: modifies-value-receiver | |||
gofumpt: | |||
extra-rules: true | |||
lang-version: "1.21" | |||
depguard: | |||
rules: | |||
main: |
@@ -875,7 +875,7 @@ node_modules: package-lock.json | |||
@touch node_modules | |||
.venv: poetry.lock | |||
poetry install | |||
poetry install --no-root | |||
@touch .venv | |||
.PHONY: update | |||
@@ -892,7 +892,7 @@ update-js: node-check | node_modules | |||
update-py: node-check | node_modules | |||
npx updates -u -f pyproject.toml | |||
rm -rf .venv poetry.lock | |||
poetry install | |||
poetry install --no-root | |||
@touch .venv | |||
.PHONY: fomantic |
@@ -62,7 +62,7 @@ func runListAuth(c *cli.Context) error { | |||
return err | |||
} | |||
authSources, err := auth_model.Sources(ctx) | |||
authSources, err := auth_model.FindSources(ctx, auth_model.FindSourcesOptions{}) | |||
if err != nil { | |||
return err | |||
} |
@@ -47,24 +47,28 @@ func main() { | |||
on the configuration cheat sheet.` | |||
app.Flags = []cli.Flag{ | |||
&cli.StringFlag{ | |||
Name: "custom-path, C", | |||
Value: setting.CustomPath, | |||
Usage: "Custom path file path", | |||
Name: "custom-path", | |||
Aliases: []string{"C"}, | |||
Value: setting.CustomPath, | |||
Usage: "Custom path file path", | |||
}, | |||
&cli.StringFlag{ | |||
Name: "config, c", | |||
Value: setting.CustomConf, | |||
Usage: "Custom configuration file path", | |||
Name: "config", | |||
Aliases: []string{"c"}, | |||
Value: setting.CustomConf, | |||
Usage: "Custom configuration file path", | |||
}, | |||
&cli.StringFlag{ | |||
Name: "work-path, w", | |||
Value: setting.AppWorkPath, | |||
Usage: "Set the gitea working path", | |||
Name: "work-path", | |||
Aliases: []string{"w"}, | |||
Value: setting.AppWorkPath, | |||
Usage: "Set the gitea working path", | |||
}, | |||
&cli.StringFlag{ | |||
Name: "out, o", | |||
Value: "", | |||
Usage: "Destination file to write to", | |||
Name: "out", | |||
Aliases: []string{"o"}, | |||
Value: "", | |||
Usage: "Destination file to write to", | |||
}, | |||
} | |||
app.Action = runEnvironmentToIni |
@@ -1221,6 +1221,9 @@ LEVEL = Info | |||
;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png | |||
;REACTIONS = +1, -1, laugh, hooray, confused, heart, rocket, eyes | |||
;; | |||
;; Change the number of users that are displayed in reactions tooltip (triggered by mouse hover). | |||
;REACTION_MAX_USER_NUM = 10 | |||
;; | |||
;; Additional Emojis not defined in the utf8 standard | |||
;; By default we support gitea (:gitea:), to add more copy them to public/assets/img/emoji/emoji_name.png and add it to this config. | |||
;; Dont mistake it for Reactions. | |||
@@ -1235,6 +1238,10 @@ LEVEL = Info | |||
;; Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used. | |||
;; A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic). | |||
;ONLY_SHOW_RELEVANT_REPOS = false | |||
;; | |||
;; Change the sort type of the explore pages. | |||
;; Default is "recentupdate", but you also have "alphabetically", "reverselastlogin", "newest", "oldest". | |||
;EXPLORE_PAGING_DEFAULT_SORT = recentupdate | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
@@ -223,13 +223,15 @@ The following configuration set `Content-Type: application/vnd.android.package-a | |||
- `REACTIONS`: All available reactions users can choose on issues/prs and comments | |||
Values can be emoji alias (:smile:) or a unicode emoji. | |||
For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png | |||
- `REACTION_MAX_USER_NUM`: **10**: Change the number of users that are displayed in reactions tooltip (triggered by mouse hover). | |||
- `CUSTOM_EMOJIS`: **gitea, codeberg, gitlab, git, github, gogs**: Additional Emojis not defined in the utf8 standard. | |||
By default, we support Gitea (:gitea:), to add more copy them to public/assets/img/emoji/emoji_name.png and | |||
add it to this config. | |||
- `DEFAULT_SHOW_FULL_NAME`: **false**: Whether the full name of the users should be shown where possible. If the full name isn't set, the username will be used. | |||
- `SEARCH_REPO_DESCRIPTION`: **true**: Whether to search within description at repository search on explore page. | |||
- `ONLY_SHOW_RELEVANT_REPOS`: **false** Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used. | |||
- `ONLY_SHOW_RELEVANT_REPOS`: **false**: Whether to only show relevant repos on the explore page when no keyword is specified and default sorting is used. | |||
A repo is considered irrelevant if it's a fork or if it has no metadata (no description, no icon, no topic). | |||
- `EXPLORE_PAGING_DEFAULT_SORT`: **recentupdate**: Change the sort type of the explore pages. Valid values are "recentupdate", "alphabetically", "reverselastlogin", "newest" and "oldest" | |||
### UI - Admin (`ui.admin`) | |||
@@ -130,34 +130,3 @@ More details about the `[actions].DEFAULT_ACTIONS_URL` configuration can be foun | |||
Context availability is not checked, so you can use the env context on more places. | |||
See [Context availability](https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability). | |||
## Known issues | |||
### `docker/build-push-action@v4` | |||
See [act_runner#119](https://gitea.com/gitea/act_runner/issues/119#issuecomment-738294). | |||
`ACTIONS_RUNTIME_TOKEN` is a random string in Gitea Actions, not a JWT. | |||
But the `docker/build-push-action@v4` tries to parse the token as JWT and doesn't handle the error, so the job fails. | |||
There are two workarounds: | |||
Set the `ACTIONS_RUNTIME_TOKEN` to empty manually, like: | |||
``` yml | |||
- name: Build and push | |||
uses: docker/build-push-action@v4 | |||
env: | |||
ACTIONS_RUNTIME_TOKEN: '' | |||
with: | |||
... | |||
``` | |||
The bug has been fixed in a newer [commit](https://gitea.com/docker/build-push-action/commit/d8823bfaed2a82c6f5d4799a2f8e86173c461aba?style=split&whitespace=show-all#diff-1af9a5bdf96ddff3a2f3427ed520b7005e9564ad), but it has not been released. So you could use the latest version by specifying the branch name, like: | |||
``` yml | |||
- name: Build and push | |||
uses: docker/build-push-action@master | |||
with: | |||
... | |||
``` |
@@ -132,34 +132,3 @@ Gitea Actions目前不支持此功能。 | |||
不检查上下文可用性,因此您可以在更多地方使用env上下文。 | |||
请参阅[上下文可用性](https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability)。 | |||
## 已知问题 | |||
### `docker/build-push-action@v4` | |||
请参阅[act_runner#119](https://gitea.com/gitea/act_runner/issues/119#issuecomment-738294)。 | |||
`ACTIONS_RUNTIME_TOKEN`在Gitea Actions中是一个随机字符串,而不是JWT。 | |||
但是`DOCKER/BUILD-PUSH-ACTION@V4尝试将令牌解析为JWT,并且不处理错误,因此Job失败。 | |||
有两种解决方法: | |||
手动将`ACTIONS_RUNTIME_TOKEN`设置为空字符串,例如: | |||
``` yml | |||
- name: Build and push | |||
uses: docker/build-push-action@v4 | |||
env: | |||
ACTIONS_RUNTIME_TOKEN: '' | |||
with: | |||
... | |||
``` | |||
该问题已在较新的[提交](https://gitea.com/docker/build-push-action/commit/d8823bfaed2a82c6f5d4799a2f8e86173c461aba?style=split&whitespace=show-all#diff-1af9a5bdf96ddff3a2f3427ed520b7005e9564ad)中修复,但尚未发布。因此,您可以通过指定分支名称来使用最新版本,例如: | |||
``` yml | |||
- name: Build and push | |||
uses: docker/build-push-action@master | |||
with: | |||
... | |||
``` |
@@ -15,6 +15,7 @@ menu: | |||
# Profile READMEs | |||
To display a Markdown file in your Gitea profile page, simply create a repository named `.profile` and add a new file called `README.md`. Gitea will automatically display the contents of the file on your profile, above your repositories. | |||
To display a Markdown file in your Gitea user or organization profile page, create a repository named `.profile` and add a new file named `README.md` to it. | |||
Gitea will automatically display the contents of the file on your profile, in a new "Overview" above your repositories. | |||
Making the `.profile` repository private will hide the Profile README. |
@@ -36,7 +36,7 @@ require ( | |||
github.com/ethantkoenig/rupture v1.0.1 | |||
github.com/felixge/fgprof v0.9.3 | |||
github.com/fsnotify/fsnotify v1.6.0 | |||
github.com/gliderlabs/ssh v0.3.5 | |||
github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46 | |||
github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b | |||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 | |||
github.com/go-chi/chi/v5 v5.0.10 |
@@ -329,8 +329,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4 | |||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= | |||
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE= | |||
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= | |||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= | |||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= | |||
github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46 h1:fYiA820jw7wmAvdXrHwMItxjJkra7dT9y8yiXhtzb94= | |||
github.com/gliderlabs/ssh v0.3.6-0.20230927171611-ece6c7995e46/go.mod h1:i/TCLcdiX9Up/vs+Rp8c3yMbqp2Y4Y7Nh9uzGFCa5pM= | |||
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= | |||
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= | |||
github.com/go-ap/activitypub v0.0.0-20231003111253-1fba3772399b h1:VLD6IPBDkqEsOZ+EfLO6MayuHycZ0cv4BStTlRoZduo= | |||
@@ -1237,7 +1237,6 @@ golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qx | |||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | |||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= | |||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | |||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= | |||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= | |||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | |||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= | |||
@@ -1337,9 +1336,7 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBc | |||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
@@ -1353,7 +1350,6 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | |||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= | |||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | |||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | |||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | |||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= | |||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= | |||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= |
@@ -266,3 +266,27 @@ func CreateRunner(ctx context.Context, t *ActionRunner) error { | |||
_, err := db.GetEngine(ctx).Insert(t) | |||
return err | |||
} | |||
func CountRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { | |||
// Only affect action runners were a owner ID is set, as actions runners | |||
// could also be created on a repository. | |||
return db.GetEngine(ctx).Table("action_runner"). | |||
Join("LEFT", "user", "`action_runner`.owner_id = `user`.id"). | |||
Where("`action_runner`.owner_id != ?", 0). | |||
And(builder.IsNull{"`user`.id"}). | |||
Count(new(ActionRunner)) | |||
} | |||
func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { | |||
subQuery := builder.Select("`action_runner`.id"). | |||
From("`action_runner`"). | |||
Join("LEFT", "user", "`action_runner`.owner_id = `user`.id"). | |||
Where(builder.Neq{"`action_runner`.owner_id": 0}). | |||
And(builder.IsNull{"`user`.id"}) | |||
b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`") | |||
res, err := db.GetEngine(ctx).Exec(b) | |||
if err != nil { | |||
return 0, err | |||
} | |||
return res.RowsAffected() | |||
} |
@@ -102,7 +102,7 @@ func GetStatistic(ctx context.Context) (stats Statistic) { | |||
stats.Counter.Follow, _ = e.Count(new(user_model.Follow)) | |||
stats.Counter.Mirror, _ = e.Count(new(repo_model.Mirror)) | |||
stats.Counter.Release, _ = e.Count(new(repo_model.Release)) | |||
stats.Counter.AuthSource = auth.CountSources(ctx) | |||
stats.Counter.AuthSource = auth.CountSources(ctx, auth.FindSourcesOptions{}) | |||
stats.Counter.Webhook, _ = e.Count(new(webhook.Webhook)) | |||
stats.Counter.Milestone, _ = e.Count(new(issues_model.Milestone)) | |||
stats.Counter.Label, _ = e.Count(new(issues_model.Label)) |
@@ -631,15 +631,6 @@ func (err ErrOAuthApplicationNotFound) Unwrap() error { | |||
return util.ErrNotExist | |||
} | |||
// GetActiveOAuth2ProviderSources returns all actived LoginOAuth2 sources | |||
func GetActiveOAuth2ProviderSources(ctx context.Context) ([]*Source, error) { | |||
sources := make([]*Source, 0, 1) | |||
if err := db.GetEngine(ctx).Where("is_active = ? and type = ?", true, OAuth2).Find(&sources); err != nil { | |||
return nil, err | |||
} | |||
return sources, nil | |||
} | |||
// GetActiveOAuth2SourceByName returns a OAuth2 AuthSource based on the given name | |||
func GetActiveOAuth2SourceByName(ctx context.Context, name string) (*Source, error) { | |||
authSource := new(Source) |
@@ -14,6 +14,7 @@ import ( | |||
"code.gitea.io/gitea/modules/timeutil" | |||
"code.gitea.io/gitea/modules/util" | |||
"xorm.io/builder" | |||
"xorm.io/xorm" | |||
"xorm.io/xorm/convert" | |||
) | |||
@@ -233,44 +234,33 @@ func CreateSource(ctx context.Context, source *Source) error { | |||
err = registerableSource.RegisterSource() | |||
if err != nil { | |||
// remove the AuthSource in case of errors while registering configuration | |||
if _, err := db.GetEngine(ctx).Delete(source); err != nil { | |||
if _, err := db.GetEngine(ctx).ID(source.ID).Delete(new(Source)); err != nil { | |||
log.Error("CreateSource: Error while wrapOpenIDConnectInitializeError: %v", err) | |||
} | |||
} | |||
return err | |||
} | |||
// Sources returns a slice of all login sources found in DB. | |||
func Sources(ctx context.Context) ([]*Source, error) { | |||
auths := make([]*Source, 0, 6) | |||
return auths, db.GetEngine(ctx).Find(&auths) | |||
type FindSourcesOptions struct { | |||
IsActive util.OptionalBool | |||
LoginType Type | |||
} | |||
// SourcesByType returns all sources of the specified type | |||
func SourcesByType(ctx context.Context, loginType Type) ([]*Source, error) { | |||
sources := make([]*Source, 0, 1) | |||
if err := db.GetEngine(ctx).Where("type = ?", loginType).Find(&sources); err != nil { | |||
return nil, err | |||
func (opts FindSourcesOptions) ToConds() builder.Cond { | |||
conds := builder.NewCond() | |||
if !opts.IsActive.IsNone() { | |||
conds = conds.And(builder.Eq{"is_active": opts.IsActive.IsTrue()}) | |||
} | |||
return sources, nil | |||
} | |||
// AllActiveSources returns all active sources | |||
func AllActiveSources(ctx context.Context) ([]*Source, error) { | |||
sources := make([]*Source, 0, 5) | |||
if err := db.GetEngine(ctx).Where("is_active = ?", true).Find(&sources); err != nil { | |||
return nil, err | |||
if opts.LoginType != NoType { | |||
conds = conds.And(builder.Eq{"`type`": opts.LoginType}) | |||
} | |||
return sources, nil | |||
return conds | |||
} | |||
// ActiveSources returns all active sources of the specified type | |||
func ActiveSources(ctx context.Context, tp Type) ([]*Source, error) { | |||
sources := make([]*Source, 0, 1) | |||
if err := db.GetEngine(ctx).Where("is_active = ? and type = ?", true, tp).Find(&sources); err != nil { | |||
return nil, err | |||
} | |||
return sources, nil | |||
// FindSources returns a slice of login sources found in DB according to given conditions. | |||
func FindSources(ctx context.Context, opts FindSourcesOptions) ([]*Source, error) { | |||
auths := make([]*Source, 0, 6) | |||
return auths, db.GetEngine(ctx).Where(opts.ToConds()).Find(&auths) | |||
} | |||
// IsSSPIEnabled returns true if there is at least one activated login | |||
@@ -279,7 +269,10 @@ func IsSSPIEnabled(ctx context.Context) bool { | |||
if !db.HasEngine { | |||
return false | |||
} | |||
sources, err := ActiveSources(ctx, SSPI) | |||
sources, err := FindSources(ctx, FindSourcesOptions{ | |||
IsActive: util.OptionalBoolTrue, | |||
LoginType: SSPI, | |||
}) | |||
if err != nil { | |||
log.Error("ActiveSources: %v", err) | |||
return false | |||
@@ -354,8 +347,8 @@ func UpdateSource(ctx context.Context, source *Source) error { | |||
} | |||
// CountSources returns number of login sources. | |||
func CountSources(ctx context.Context) int64 { | |||
count, _ := db.GetEngine(ctx).Count(new(Source)) | |||
func CountSources(ctx context.Context, opts FindSourcesOptions) int64 { | |||
count, _ := db.GetEngine(ctx).Where(opts.ToConds()).Count(new(Source)) | |||
return count | |||
} | |||
@@ -142,22 +142,14 @@ type Issue struct { | |||
} | |||
var ( | |||
issueTasksPat *regexp.Regexp | |||
issueTasksDonePat *regexp.Regexp | |||
) | |||
const ( | |||
issueTasksRegexpStr = `(^\s*[-*]\s\[[\sxX]\]\s.)|(\n\s*[-*]\s\[[\sxX]\]\s.)` | |||
issueTasksDoneRegexpStr = `(^\s*[-*]\s\[[xX]\]\s.)|(\n\s*[-*]\s\[[xX]\]\s.)` | |||
issueTasksPat = regexp.MustCompile(`(^\s*[-*]\s\[[\sxX]\]\s.)|(\n\s*[-*]\s\[[\sxX]\]\s.)`) | |||
issueTasksDonePat = regexp.MustCompile(`(^\s*[-*]\s\[[xX]\]\s.)|(\n\s*[-*]\s\[[xX]\]\s.)`) | |||
) | |||
// IssueIndex represents the issue index table | |||
type IssueIndex db.ResourceIndex | |||
func init() { | |||
issueTasksPat = regexp.MustCompile(issueTasksRegexpStr) | |||
issueTasksDonePat = regexp.MustCompile(issueTasksDoneRegexpStr) | |||
db.RegisterModel(new(Issue)) | |||
db.RegisterModel(new(IssueIndex)) | |||
} |
@@ -897,6 +897,16 @@ func DeleteReview(ctx context.Context, r *Review) error { | |||
return err | |||
} | |||
opts = FindCommentsOptions{ | |||
Type: CommentTypeDismissReview, | |||
IssueID: r.IssueID, | |||
ReviewID: r.ID, | |||
} | |||
if _, err := sess.Where(opts.ToConds()).Delete(new(Comment)); err != nil { | |||
return err | |||
} | |||
if _, err := sess.ID(r.ID).Delete(new(Review)); err != nil { | |||
return err | |||
} |
@@ -8,6 +8,7 @@ import ( | |||
"code.gitea.io/gitea/models/db" | |||
issues_model "code.gitea.io/gitea/models/issues" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
"code.gitea.io/gitea/models/unittest" | |||
user_model "code.gitea.io/gitea/models/user" | |||
@@ -258,3 +259,32 @@ func TestDeleteReview(t *testing.T) { | |||
assert.NoError(t, err) | |||
assert.True(t, review1.Official) | |||
} | |||
func TestDeleteDismissedReview(t *testing.T) { | |||
assert.NoError(t, unittest.PrepareTestDatabase()) | |||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) | |||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) | |||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}) | |||
review, err := issues_model.CreateReview(db.DefaultContext, issues_model.CreateReviewOptions{ | |||
Content: "reject", | |||
Type: issues_model.ReviewTypeReject, | |||
Official: false, | |||
Issue: issue, | |||
Reviewer: user, | |||
}) | |||
assert.NoError(t, err) | |||
assert.NoError(t, issues_model.DismissReview(db.DefaultContext, review, true)) | |||
comment, err := issues_model.CreateComment(db.DefaultContext, &issues_model.CreateCommentOptions{ | |||
Type: issues_model.CommentTypeDismissReview, | |||
Doer: user, | |||
Repo: repo, | |||
Issue: issue, | |||
ReviewID: review.ID, | |||
Content: "dismiss", | |||
}) | |||
assert.NoError(t, err) | |||
unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID}) | |||
assert.NoError(t, issues_model.DeleteReview(db.DefaultContext, review)) | |||
unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: comment.ID}) | |||
} |
@@ -14,12 +14,11 @@ import ( | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
) | |||
func removeOrgUser(ctx context.Context, orgID, userID int64) error { | |||
// RemoveOrgUser removes user from given organization. | |||
func RemoveOrgUser(ctx context.Context, orgID, userID int64) error { | |||
ou := new(organization.OrgUser) | |||
sess := db.GetEngine(ctx) | |||
has, err := sess. | |||
has, err := db.GetEngine(ctx). | |||
Where("uid=?", userID). | |||
And("org_id=?", orgID). | |||
Get(ou) | |||
@@ -52,7 +51,13 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error { | |||
} | |||
} | |||
if _, err := sess.ID(ou.ID).Delete(ou); err != nil { | |||
ctx, committer, err := db.TxContext(ctx) | |||
if err != nil { | |||
return err | |||
} | |||
defer committer.Close() | |||
if _, err := db.GetEngine(ctx).ID(ou.ID).Delete(ou); err != nil { | |||
return err | |||
} else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members=num_members-1 WHERE id=?", orgID); err != nil { | |||
return err | |||
@@ -74,7 +79,7 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error { | |||
} | |||
if len(repoIDs) > 0 { | |||
if _, err = sess. | |||
if _, err = db.GetEngine(ctx). | |||
Where("user_id = ?", userID). | |||
In("repo_id", repoIDs). | |||
Delete(new(access_model.Access)); err != nil { | |||
@@ -93,18 +98,5 @@ func removeOrgUser(ctx context.Context, orgID, userID int64) error { | |||
} | |||
} | |||
return nil | |||
} | |||
// RemoveOrgUser removes user from given organization. | |||
func RemoveOrgUser(ctx context.Context, orgID, userID int64) error { | |||
ctx, committer, err := db.TxContext(ctx) | |||
if err != nil { | |||
return err | |||
} | |||
defer committer.Close() | |||
if err := removeOrgUser(ctx, orgID, userID); err != nil { | |||
return err | |||
} | |||
return committer.Commit() | |||
} |
@@ -502,7 +502,7 @@ func removeInvalidOrgUser(ctx context.Context, userID, orgID int64) error { | |||
}); err != nil { | |||
return err | |||
} else if count == 0 { | |||
return removeOrgUser(ctx, orgID, userID) | |||
return RemoveOrgUser(ctx, orgID, userID) | |||
} | |||
return nil | |||
} |
@@ -11,7 +11,6 @@ import ( | |||
"net/url" | |||
"strings" | |||
"code.gitea.io/gitea/models/auth" | |||
repo_model "code.gitea.io/gitea/models/repo" | |||
"code.gitea.io/gitea/models/unit" | |||
user_model "code.gitea.io/gitea/models/user" | |||
@@ -211,32 +210,6 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) { | |||
} | |||
} | |||
// CheckForOTP validates OTP | |||
func (ctx *APIContext) CheckForOTP() { | |||
if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) { | |||
return // Skip 2FA | |||
} | |||
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP") | |||
twofa, err := auth.GetTwoFactorByUID(ctx, ctx.Doer.ID) | |||
if err != nil { | |||
if auth.IsErrTwoFactorNotEnrolled(err) { | |||
return // No 2FA enrollment for this user | |||
} | |||
ctx.Error(http.StatusInternalServerError, "GetTwoFactorByUID", err) | |||
return | |||
} | |||
ok, err := twofa.ValidateTOTP(otpHeader) | |||
if err != nil { | |||
ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err) | |||
return | |||
} | |||
if !ok { | |||
ctx.Error(http.StatusUnauthorized, "", nil) | |||
return | |||
} | |||
} | |||
// APIContexter returns apicontext as middleware | |||
func APIContexter() func(http.Handler) http.Handler { | |||
return func(next http.Handler) http.Handler { |
@@ -850,7 +850,7 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string { | |||
return getRefNameFromPath(ctx, repo, path, func(s string) bool { | |||
b, exist, err := git_model.FindRenamedBranch(ctx, repo.Repository.ID, s) | |||
if err != nil { | |||
log.Error("FindRenamedBranch", err) | |||
log.Error("FindRenamedBranch: %v", err) | |||
return false | |||
} | |||
@@ -6,6 +6,7 @@ package doctor | |||
import ( | |||
"context" | |||
actions_model "code.gitea.io/gitea/models/actions" | |||
activities_model "code.gitea.io/gitea/models/activities" | |||
"code.gitea.io/gitea/models/db" | |||
issues_model "code.gitea.io/gitea/models/issues" | |||
@@ -151,6 +152,12 @@ func checkDBConsistency(ctx context.Context, logger log.Logger, autofix bool) er | |||
Fixer: activities_model.FixActionCreatedUnixString, | |||
FixedMessage: "Set to zero", | |||
}, | |||
{ | |||
Name: "Action Runners without existing owner", | |||
Counter: actions_model.CountRunnersWithoutBelongingOwner, | |||
Fixer: actions_model.FixRunnersWithoutBelongingOwner, | |||
FixedMessage: "Removed", | |||
}, | |||
} | |||
// TODO: function to recalc all counters |
@@ -66,7 +66,7 @@ var ( | |||
// well as the HTML5 spec: | |||
// http://spec.commonmark.org/0.28/#email-address | |||
// https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail) | |||
emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|\\.(\\s|$))") | |||
emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|;|,|\\?|!|\\.(\\s|$))") | |||
// blackfriday extensions create IDs like fn:user-content-footnote | |||
blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`) |
@@ -264,6 +264,18 @@ func TestRender_email(t *testing.T) { | |||
"send email to info@gitea.co.uk.", | |||
`<p>send email to <a href="mailto:info@gitea.co.uk" rel="nofollow">info@gitea.co.uk</a>.</p>`) | |||
test( | |||
`j.doe@example.com, | |||
j.doe@example.com. | |||
j.doe@example.com; | |||
j.doe@example.com? | |||
j.doe@example.com!`, | |||
`<p><a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>,<br/> | |||
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>.<br/> | |||
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>;<br/> | |||
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>?<br/> | |||
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>!</p>`) | |||
// Test that should *not* be turned into email links | |||
test( | |||
"\"info@gitea.com\"", |
@@ -158,7 +158,7 @@ func (r *Writer) WriteRegularLink(l org.RegularLink) { | |||
case "image": | |||
if l.Description == nil { | |||
imageSrc := getMediaURL(link) | |||
fmt.Fprintf(r, `<img src="%s" alt="%s" title="%s" />`, imageSrc, link, link) | |||
fmt.Fprintf(r, `<img src="%s" alt="%s" />`, imageSrc, link) | |||
} else { | |||
description := strings.TrimPrefix(org.String(l.Description...), "file:") | |||
imageSrc := getMediaURL([]byte(description)) | |||
@@ -167,18 +167,18 @@ func (r *Writer) WriteRegularLink(l org.RegularLink) { | |||
case "video": | |||
if l.Description == nil { | |||
imageSrc := getMediaURL(link) | |||
fmt.Fprintf(r, `<video src="%s" title="%s">%s</video>`, imageSrc, link, link) | |||
fmt.Fprintf(r, `<video src="%s">%s</video>`, imageSrc, link) | |||
} else { | |||
description := strings.TrimPrefix(org.String(l.Description...), "file:") | |||
videoSrc := getMediaURL([]byte(description)) | |||
fmt.Fprintf(r, `<a href="%s"><video src="%s" title="%s"></video></a>`, link, videoSrc, videoSrc) | |||
fmt.Fprintf(r, `<a href="%s"><video src="%s">%s</video></a>`, link, videoSrc, videoSrc) | |||
} | |||
default: | |||
description := string(link) | |||
if l.Description != nil { | |||
description = r.WriteNodesAsString(l.Description...) | |||
} | |||
fmt.Fprintf(r, `<a href="%s" title="%s">%s</a>`, link, description, description) | |||
fmt.Fprintf(r, `<a href="%s">%s</a>`, link, description) | |||
} | |||
} | |||
@@ -34,12 +34,12 @@ func TestRender_StandardLinks(t *testing.T) { | |||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) | |||
} | |||
googleRendered := "<p><a href=\"https://google.com/\" title=\"https://google.com/\">https://google.com/</a></p>" | |||
test("[[https://google.com/]]", googleRendered) | |||
test("[[https://google.com/]]", | |||
`<p><a href="https://google.com/">https://google.com/</a></p>`) | |||
lnk := util.URLJoin(AppSubURL, "WikiPage") | |||
test("[[WikiPage][WikiPage]]", | |||
"<p><a href=\""+lnk+"\" title=\"WikiPage\">WikiPage</a></p>") | |||
`<p><a href="`+lnk+`">WikiPage</a></p>`) | |||
} | |||
func TestRender_Media(t *testing.T) { | |||
@@ -59,19 +59,23 @@ func TestRender_Media(t *testing.T) { | |||
result := util.URLJoin(AppSubURL, url) | |||
test("[[file:"+url+"]]", | |||
"<p><img src=\""+result+"\" alt=\""+result+"\" title=\""+result+"\" /></p>") | |||
`<p><img src="`+result+`" alt="`+result+`" /></p>`) | |||
// With description. | |||
test("[[https://example.com][https://example.com/example.svg]]", | |||
`<p><a href="https://example.com"><img src="https://example.com/example.svg" alt="https://example.com/example.svg" /></a></p>`) | |||
test("[[https://example.com][pre https://example.com/example.svg post]]", | |||
`<p><a href="https://example.com">pre <img src="https://example.com/example.svg" alt="https://example.com/example.svg" /> post</a></p>`) | |||
test("[[https://example.com][https://example.com/example.mp4]]", | |||
`<p><a href="https://example.com"><video src="https://example.com/example.mp4" title="https://example.com/example.mp4"></video></a></p>`) | |||
`<p><a href="https://example.com"><video src="https://example.com/example.mp4">https://example.com/example.mp4</video></a></p>`) | |||
test("[[https://example.com][pre https://example.com/example.mp4 post]]", | |||
`<p><a href="https://example.com">pre <video src="https://example.com/example.mp4">https://example.com/example.mp4</video> post</a></p>`) | |||
// Without description. | |||
test("[[https://example.com/example.svg]]", | |||
`<p><img src="https://example.com/example.svg" alt="https://example.com/example.svg" title="https://example.com/example.svg" /></p>`) | |||
`<p><img src="https://example.com/example.svg" alt="https://example.com/example.svg" /></p>`) | |||
test("[[https://example.com/example.mp4]]", | |||
`<p><video src="https://example.com/example.mp4" title="https://example.com/example.mp4">https://example.com/example.mp4</video></p>`) | |||
`<p><video src="https://example.com/example.mp4">https://example.com/example.mp4</video></p>`) | |||
} | |||
func TestRender_Source(t *testing.T) { |
@@ -33,6 +33,7 @@ var UI = struct { | |||
CustomEmojisMap map[string]string `ini:"-"` | |||
SearchRepoDescription bool | |||
OnlyShowRelevantRepos bool | |||
ExploreDefaultSort string `ini:"EXPLORE_PAGING_DEFAULT_SORT"` | |||
Notification struct { | |||
MinTimeout time.Duration |
@@ -17,7 +17,6 @@ import ( | |||
"os" | |||
"os/exec" | |||
"path/filepath" | |||
"reflect" | |||
"strconv" | |||
"strings" | |||
"sync" | |||
@@ -165,10 +164,6 @@ func sessionHandler(session ssh.Session) { | |||
} | |||
func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { | |||
// FIXME: the "ssh.Context" is not thread-safe, so db operations should use the immutable parent "Context" | |||
// TODO: Remove after https://github.com/gliderlabs/ssh/pull/211 | |||
parentCtx := reflect.ValueOf(ctx).Elem().FieldByName("Context").Interface().(context.Context) | |||
if log.IsDebug() { // <- FingerprintSHA256 is kinda expensive so only calculate it if necessary | |||
log.Debug("Handle Public Key: Fingerprint: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr()) | |||
} | |||
@@ -200,7 +195,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { | |||
// look for the exact principal | |||
principalLoop: | |||
for _, principal := range cert.ValidPrincipals { | |||
pkey, err := asymkey_model.SearchPublicKeyByContentExact(parentCtx, principal) | |||
pkey, err := asymkey_model.SearchPublicKeyByContentExact(ctx, principal) | |||
if err != nil { | |||
if asymkey_model.IsErrKeyNotExist(err) { | |||
log.Debug("Principal Rejected: %s Unknown Principal: %s", ctx.RemoteAddr(), principal) | |||
@@ -257,7 +252,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { | |||
log.Debug("Handle Public Key: %s Fingerprint: %s is not a certificate", ctx.RemoteAddr(), gossh.FingerprintSHA256(key)) | |||
} | |||
pkey, err := asymkey_model.SearchPublicKeyByContent(parentCtx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key)))) | |||
pkey, err := asymkey_model.SearchPublicKeyByContent(ctx, strings.TrimSpace(string(gossh.MarshalAuthorizedKey(key)))) | |||
if err != nil { | |||
if asymkey_model.IsErrKeyNotExist(err) { | |||
log.Warn("Unknown public key: %s from %s", gossh.FingerprintSHA256(key), ctx.RemoteAddr()) |
@@ -1786,6 +1786,8 @@ pulls.status_checks_failure = Some checks failed | |||
pulls.status_checks_error = Some checks reported errors | |||
pulls.status_checks_requested = Required | |||
pulls.status_checks_details = Details | |||
pulls.status_checks_hide_all = Hide all checks | |||
pulls.status_checks_show_all = Show all checks | |||
pulls.update_branch = Update branch by merge | |||
pulls.update_branch_rebase = Update branch by rebase | |||
pulls.update_branch_success = Branch update was successful |
@@ -77,20 +77,24 @@ milestones=Míľniky | |||
ok=OK | |||
cancel=Zrušiť | |||
retry=Opakovať | |||
save=Uložiť | |||
add=Pridať | |||
add_all=Pridať všetko | |||
remove=Odstrániť | |||
remove_all=Odstrániť všetko | |||
remove_label_str=Odstrániť položku „%s“ | |||
edit=Upraviť | |||
enabled=Povolené | |||
copy=Kopírovať | |||
copy_url=Kopírovať URL | |||
copy_content=Kopírovať obsah | |||
copy_branch=Kopírovať meno vetvy | |||
copy_success=Skopírované! | |||
copy_error=Kopírovanie zlyhalo | |||
copy_type_unsupported=Tento typ súboru nie je možné skopírovať | |||
write=Zapísať | |||
preview=Náhľad |
@@ -5,8 +5,8 @@ | |||
}, | |||
"dependencies": { | |||
"@citation-js/core": "0.7.1", | |||
"@citation-js/plugin-bibtex": "0.7.1", | |||
"@citation-js/plugin-csl": "0.7.1", | |||
"@citation-js/plugin-bibtex": "0.7.2", | |||
"@citation-js/plugin-csl": "0.7.2", | |||
"@citation-js/plugin-software-formats": "0.6.1", | |||
"@claviska/jquery-minicolors": "2.3.6", | |||
"@github/markdown-toolbar-element": "2.2.1", | |||
@@ -17,7 +17,7 @@ | |||
"@webcomponents/custom-elements": "1.6.0", | |||
"add-asset-webpack-plugin": "2.0.1", | |||
"ansi_up": "6.0.2", | |||
"asciinema-player": "3.6.2", | |||
"asciinema-player": "3.6.3", | |||
"clippie": "4.0.6", | |||
"css-loader": "6.8.1", | |||
"dropzone": "6.0.0-beta.2", | |||
@@ -29,7 +29,7 @@ | |||
"katex": "0.16.9", | |||
"license-checker-webpack-plugin": "0.2.1", | |||
"lightningcss-loader": "2.1.0", | |||
"mermaid": "10.5.0", | |||
"mermaid": "10.6.0", | |||
"mini-css-extract-plugin": "2.7.6", | |||
"minimatch": "9.0.3", | |||
"monaco-editor": "0.44.0", | |||
@@ -37,49 +37,50 @@ | |||
"pdfobject": "2.2.12", | |||
"pretty-ms": "8.0.0", | |||
"sortablejs": "1.15.0", | |||
"swagger-ui-dist": "5.9.0", | |||
"swagger-ui-dist": "5.9.1", | |||
"throttle-debounce": "5.0.0", | |||
"tinycolor2": "1.6.0", | |||
"tippy.js": "6.3.7", | |||
"toastify-js": "1.12.0", | |||
"tributejs": "5.1.3", | |||
"uint8-to-base64": "0.2.0", | |||
"vue": "3.3.4", | |||
"vue": "3.3.7", | |||
"vue-bar-graph": "2.0.0", | |||
"vue-loader": "17.3.0", | |||
"vue-loader": "17.3.1", | |||
"vue3-calendar-heatmap": "2.0.5", | |||
"webpack": "5.88.2", | |||
"webpack": "5.89.0", | |||
"webpack-cli": "5.1.4", | |||
"wrap-ansi": "8.1.0" | |||
"wrap-ansi": "9.0.0" | |||
}, | |||
"devDependencies": { | |||
"@eslint-community/eslint-plugin-eslint-comments": "4.1.0", | |||
"@playwright/test": "1.38.1", | |||
"@playwright/test": "1.39.0", | |||
"@stoplight/spectral-cli": "6.11.0", | |||
"@stylistic/eslint-plugin-js": "1.0.0", | |||
"@vitejs/plugin-vue": "4.4.0", | |||
"eslint": "8.51.0", | |||
"eslint": "8.53.0", | |||
"eslint-plugin-array-func": "4.0.0", | |||
"eslint-plugin-i": "2.28.1", | |||
"eslint-plugin-i": "2.29.0", | |||
"eslint-plugin-jquery": "1.5.1", | |||
"eslint-plugin-no-jquery": "2.7.0", | |||
"eslint-plugin-no-use-extend-native": "0.5.0", | |||
"eslint-plugin-regexp": "2.0.0", | |||
"eslint-plugin-sonarjs": "0.21.0", | |||
"eslint-plugin-unicorn": "48.0.1", | |||
"eslint-plugin-vitest": "0.3.2", | |||
"eslint-plugin-regexp": "2.1.1", | |||
"eslint-plugin-sonarjs": "0.23.0", | |||
"eslint-plugin-unicorn": "49.0.0", | |||
"eslint-plugin-vitest": "0.3.9", | |||
"eslint-plugin-vitest-globals": "1.4.0", | |||
"eslint-plugin-vue": "9.17.0", | |||
"eslint-plugin-vue": "9.18.1", | |||
"eslint-plugin-vue-scoped-css": "2.5.1", | |||
"eslint-plugin-wc": "2.0.4", | |||
"jsdom": "22.1.0", | |||
"markdownlint-cli": "0.37.0", | |||
"postcss-html": "1.5.0", | |||
"stylelint": "15.10.3", | |||
"stylelint": "15.11.0", | |||
"stylelint-declaration-block-no-ignored-properties": "2.7.0", | |||
"stylelint-declaration-strict-value": "1.9.2", | |||
"stylelint-stylistic": "0.4.3", | |||
"svgo": "3.0.2", | |||
"updates": "15.0.2", | |||
"updates": "15.0.4", | |||
"vite-string-plugin": "1.1.2", | |||
"vitest": "0.34.6" | |||
}, |
@@ -1,4 +1,4 @@ | |||
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. | |||
# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. | |||
[[package]] | |||
name = "click" |
@@ -316,10 +316,6 @@ func reqToken() func(ctx *context.APIContext) { | |||
return | |||
} | |||
if ctx.IsBasicAuth { | |||
ctx.CheckForOTP() | |||
return | |||
} | |||
if ctx.IsSigned { | |||
return | |||
} | |||
@@ -344,7 +340,6 @@ func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) { | |||
ctx.Error(http.StatusUnauthorized, "reqBasicAuth", "auth required") | |||
return | |||
} | |||
ctx.CheckForOTP() | |||
} | |||
} | |||
@@ -701,12 +696,6 @@ func bind[T any](_ T) any { | |||
} | |||
} | |||
// The OAuth2 plugin is expected to be executed first, as it must ignore the user id stored | |||
// in the session (if there is a user id stored in session other plugins might return the user | |||
// object for that id). | |||
// | |||
// The Session plugin is expected to be executed second, in order to skip authentication | |||
// for users that have already signed in. | |||
func buildAuthGroup() *auth.Group { | |||
group := auth.NewGroup( | |||
&auth.OAuth2{}, | |||
@@ -786,31 +775,6 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIC | |||
}) | |||
return | |||
} | |||
if ctx.IsSigned && ctx.IsBasicAuth { | |||
if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) { | |||
return // Skip 2FA | |||
} | |||
twofa, err := auth_model.GetTwoFactorByUID(ctx, ctx.Doer.ID) | |||
if err != nil { | |||
if auth_model.IsErrTwoFactorNotEnrolled(err) { | |||
return // No 2FA enrollment for this user | |||
} | |||
ctx.InternalServerError(err) | |||
return | |||
} | |||
otpHeader := ctx.Req.Header.Get("X-Gitea-OTP") | |||
ok, err := twofa.ValidateTOTP(otpHeader) | |||
if err != nil { | |||
ctx.InternalServerError(err) | |||
return | |||
} | |||
if !ok { | |||
ctx.JSON(http.StatusForbidden, map[string]string{ | |||
"message": "Only signed in user is allowed to call APIs.", | |||
}) | |||
return | |||
} | |||
} | |||
} | |||
if options.AdminRequired { |
@@ -48,13 +48,13 @@ func Authentications(ctx *context.Context) { | |||
ctx.Data["PageIsAdminAuthentications"] = true | |||
var err error | |||
ctx.Data["Sources"], err = auth.Sources(ctx) | |||
ctx.Data["Sources"], err = auth.FindSources(ctx, auth.FindSourcesOptions{}) | |||
if err != nil { | |||
ctx.ServerError("auth.Sources", err) | |||
return | |||
} | |||
ctx.Data["Total"] = auth.CountSources(ctx) | |||
ctx.Data["Total"] = auth.CountSources(ctx, auth.FindSourcesOptions{}) | |||
ctx.HTML(http.StatusOK, tplAuths) | |||
} | |||
@@ -99,7 +99,7 @@ func NewAuthSource(ctx *context.Context) { | |||
ctx.Data["AuthSources"] = authSources | |||
ctx.Data["SecurityProtocols"] = securityProtocols | |||
ctx.Data["SMTPAuths"] = smtp.Authenticators | |||
oauth2providers := oauth2.GetOAuth2Providers() | |||
oauth2providers := oauth2.GetSupportedOAuth2Providers() | |||
ctx.Data["OAuth2Providers"] = oauth2providers | |||
ctx.Data["SSPIAutoCreateUsers"] = true | |||
@@ -242,7 +242,7 @@ func NewAuthSourcePost(ctx *context.Context) { | |||
ctx.Data["AuthSources"] = authSources | |||
ctx.Data["SecurityProtocols"] = securityProtocols | |||
ctx.Data["SMTPAuths"] = smtp.Authenticators | |||
oauth2providers := oauth2.GetOAuth2Providers() | |||
oauth2providers := oauth2.GetSupportedOAuth2Providers() | |||
ctx.Data["OAuth2Providers"] = oauth2providers | |||
ctx.Data["SSPIAutoCreateUsers"] = true | |||
@@ -284,7 +284,7 @@ func NewAuthSourcePost(ctx *context.Context) { | |||
ctx.RenderWithErr(err.Error(), tplAuthNew, form) | |||
return | |||
} | |||
existing, err := auth.SourcesByType(ctx, auth.SSPI) | |||
existing, err := auth.FindSources(ctx, auth.FindSourcesOptions{LoginType: auth.SSPI}) | |||
if err != nil || len(existing) > 0 { | |||
ctx.Data["Err_Type"] = true | |||
ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_of_type_exist"), tplAuthNew, form) | |||
@@ -334,7 +334,7 @@ func EditAuthSource(ctx *context.Context) { | |||
ctx.Data["SecurityProtocols"] = securityProtocols | |||
ctx.Data["SMTPAuths"] = smtp.Authenticators | |||
oauth2providers := oauth2.GetOAuth2Providers() | |||
oauth2providers := oauth2.GetSupportedOAuth2Providers() | |||
ctx.Data["OAuth2Providers"] = oauth2providers | |||
source, err := auth.GetSourceByID(ctx, ctx.ParamsInt64(":authid")) | |||
@@ -368,7 +368,7 @@ func EditAuthSourcePost(ctx *context.Context) { | |||
ctx.Data["PageIsAdminAuthentications"] = true | |||
ctx.Data["SMTPAuths"] = smtp.Authenticators | |||
oauth2providers := oauth2.GetOAuth2Providers() | |||
oauth2providers := oauth2.GetSupportedOAuth2Providers() | |||
ctx.Data["OAuth2Providers"] = oauth2providers | |||
source, err := auth.GetSourceByID(ctx, ctx.ParamsInt64(":authid")) |
@@ -24,7 +24,7 @@ func Organizations(ctx *context.Context) { | |||
ctx.Data["PageIsAdminOrganizations"] = true | |||
if ctx.FormString("sort") == "" { | |||
ctx.SetFormString("sort", explore.UserSearchDefaultAdminSort) | |||
ctx.SetFormString("sort", UserSearchDefaultAdminSort) | |||
} | |||
explore.RenderUserSearch(ctx, &user_model.SearchUserOptions{ |
@@ -37,6 +37,9 @@ const ( | |||
tplUserEdit base.TplName = "admin/user/edit" | |||
) | |||
// UserSearchDefaultAdminSort is the default sort type for admin view | |||
const UserSearchDefaultAdminSort = "alphabetically" | |||
// Users show all the users | |||
func Users(ctx *context.Context) { | |||
ctx.Data["Title"] = ctx.Tr("admin.users") | |||
@@ -56,7 +59,7 @@ func Users(ctx *context.Context) { | |||
sortType := ctx.FormString("sort") | |||
if sortType == "" { | |||
sortType = explore.UserSearchDefaultAdminSort | |||
sortType = UserSearchDefaultAdminSort | |||
ctx.SetFormString("sort", sortType) | |||
} | |||
ctx.PageData["adminUserListSearchForm"] = map[string]any{ | |||
@@ -90,7 +93,9 @@ func NewUser(ctx *context.Context) { | |||
ctx.Data["login_type"] = "0-0" | |||
sources, err := auth.Sources(ctx) | |||
sources, err := auth.FindSources(ctx, auth.FindSourcesOptions{ | |||
IsActive: util.OptionalBoolTrue, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("auth.Sources", err) | |||
return | |||
@@ -109,7 +114,9 @@ func NewUserPost(ctx *context.Context) { | |||
ctx.Data["DefaultUserVisibilityMode"] = setting.Service.DefaultUserVisibilityMode | |||
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() | |||
sources, err := auth.Sources(ctx) | |||
sources, err := auth.FindSources(ctx, auth.FindSourcesOptions{ | |||
IsActive: util.OptionalBoolTrue, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("auth.Sources", err) | |||
return | |||
@@ -230,7 +237,7 @@ func prepareUserInfo(ctx *context.Context) *user_model.User { | |||
ctx.Data["LoginSource"] = &auth.Source{} | |||
} | |||
sources, err := auth.Sources(ctx) | |||
sources, err := auth.FindSources(ctx, auth.FindSourcesOptions{}) | |||
if err != nil { | |||
ctx.ServerError("auth.Sources", err) | |||
return nil |
@@ -160,12 +160,11 @@ func SignIn(ctx *context.Context) { | |||
return | |||
} | |||
orderedOAuth2Names, oauth2Providers, err := oauth2.GetActiveOAuth2Providers(ctx) | |||
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue) | |||
if err != nil { | |||
ctx.ServerError("UserSignIn", err) | |||
return | |||
} | |||
ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names | |||
ctx.Data["OAuth2Providers"] = oauth2Providers | |||
ctx.Data["Title"] = ctx.Tr("sign_in") | |||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" | |||
@@ -184,12 +183,11 @@ func SignIn(ctx *context.Context) { | |||
func SignInPost(ctx *context.Context) { | |||
ctx.Data["Title"] = ctx.Tr("sign_in") | |||
orderedOAuth2Names, oauth2Providers, err := oauth2.GetActiveOAuth2Providers(ctx) | |||
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue) | |||
if err != nil { | |||
ctx.ServerError("UserSignIn", err) | |||
return | |||
} | |||
ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names | |||
ctx.Data["OAuth2Providers"] = oauth2Providers | |||
ctx.Data["Title"] = ctx.Tr("sign_in") | |||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" | |||
@@ -408,13 +406,12 @@ func SignUp(ctx *context.Context) { | |||
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up" | |||
orderedOAuth2Names, oauth2Providers, err := oauth2.GetActiveOAuth2Providers(ctx) | |||
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue) | |||
if err != nil { | |||
ctx.ServerError("UserSignUp", err) | |||
return | |||
} | |||
ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names | |||
ctx.Data["OAuth2Providers"] = oauth2Providers | |||
context.SetCaptchaData(ctx) | |||
@@ -438,13 +435,12 @@ func SignUpPost(ctx *context.Context) { | |||
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/sign_up" | |||
orderedOAuth2Names, oauth2Providers, err := oauth2.GetActiveOAuth2Providers(ctx) | |||
oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, util.OptionalBoolTrue) | |||
if err != nil { | |||
ctx.ServerError("UserSignUp", err) | |||
return | |||
} | |||
ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names | |||
ctx.Data["OAuth2Providers"] = oauth2Providers | |||
context.SetCaptchaData(ctx) | |||
@@ -25,7 +25,7 @@ func Organizations(ctx *context.Context) { | |||
} | |||
if ctx.FormString("sort") == "" { | |||
ctx.SetFormString("sort", UserSearchDefaultSortType) | |||
ctx.SetFormString("sort", setting.UI.ExploreDefaultSort) | |||
} | |||
RenderUserSearch(ctx, &user_model.SearchUserOptions{ |
@@ -57,8 +57,13 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { | |||
orderBy db.SearchOrderBy | |||
) | |||
ctx.Data["SortType"] = ctx.FormString("sort") | |||
switch ctx.FormString("sort") { | |||
sortOrder := ctx.FormString("sort") | |||
if sortOrder == "" { | |||
sortOrder = setting.UI.ExploreDefaultSort | |||
} | |||
ctx.Data["SortType"] = sortOrder | |||
switch sortOrder { | |||
case "newest": | |||
orderBy = db.SearchOrderByNewest | |||
case "oldest": |
@@ -23,12 +23,6 @@ const ( | |||
tplExploreUsers base.TplName = "explore/users" | |||
) | |||
// UserSearchDefaultSortType is the default sort type for user search | |||
const ( | |||
UserSearchDefaultSortType = "recentupdate" | |||
UserSearchDefaultAdminSort = "alphabetically" | |||
) | |||
var nullByte = []byte{0x00} | |||
func isKeywordValid(keyword string) bool { | |||
@@ -60,8 +54,13 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, | |||
// we can not set orderBy to `models.SearchOrderByXxx`, because there may be a JOIN in the statement, different tables may have the same name columns | |||
ctx.Data["SortType"] = ctx.FormString("sort") | |||
switch ctx.FormString("sort") { | |||
sortOrder := ctx.FormString("sort") | |||
if sortOrder == "" { | |||
sortOrder = setting.UI.ExploreDefaultSort | |||
} | |||
ctx.Data["SortType"] = sortOrder | |||
switch sortOrder { | |||
case "newest": | |||
orderBy = "`user`.id DESC" | |||
case "oldest": | |||
@@ -134,7 +133,7 @@ func Users(ctx *context.Context) { | |||
ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled | |||
if ctx.FormString("sort") == "" { | |||
ctx.SetFormString("sort", UserSearchDefaultSortType) | |||
ctx.SetFormString("sort", setting.UI.ExploreDefaultSort) | |||
} | |||
RenderUserSearch(ctx, &user_model.SearchUserOptions{ |
@@ -0,0 +1,43 @@ | |||
// Copyright 2023 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package web | |||
import ( | |||
"net/http" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/web" | |||
"code.gitea.io/gitea/routers/web/repo" | |||
context_service "code.gitea.io/gitea/services/context" | |||
) | |||
func requireSignIn(ctx *context.Context) { | |||
if !setting.Service.RequireSignInView { | |||
return | |||
} | |||
// rely on the results of Contexter | |||
if !ctx.IsSigned { | |||
// TODO: support digit auth - which would be Authorization header with digit | |||
ctx.Resp.Header().Set("WWW-Authenticate", `Basic realm="Gitea"`) | |||
ctx.Error(http.StatusUnauthorized) | |||
} | |||
} | |||
func gitHTTPRouters(m *web.Route) { | |||
m.Group("", func() { | |||
m.PostOptions("/git-upload-pack", repo.ServiceUploadPack) | |||
m.PostOptions("/git-receive-pack", repo.ServiceReceivePack) | |||
m.GetOptions("/info/refs", repo.GetInfoRefs) | |||
m.GetOptions("/HEAD", repo.GetTextFile("HEAD")) | |||
m.GetOptions("/objects/info/alternates", repo.GetTextFile("objects/info/alternates")) | |||
m.GetOptions("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates")) | |||
m.GetOptions("/objects/info/packs", repo.GetInfoPacks) | |||
m.GetOptions("/objects/info/{file:[^/]*}", repo.GetTextFile("")) | |||
m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject) | |||
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile) | |||
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile) | |||
}, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb()) | |||
} |
@@ -13,6 +13,8 @@ import ( | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/modules/base" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/git" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/markup" | |||
"code.gitea.io/gitea/modules/markup/markdown" | |||
"code.gitea.io/gitea/modules/setting" | |||
@@ -155,5 +157,29 @@ func Home(ctx *context.Context) { | |||
ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0 | |||
profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx) | |||
defer profileClose() | |||
prepareOrgProfileReadme(ctx, profileGitRepo, profileReadmeBlob) | |||
ctx.HTML(http.StatusOK, tplOrgHome) | |||
} | |||
func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repository, profileReadme *git.Blob) { | |||
if profileGitRepo == nil || profileReadme == nil { | |||
return | |||
} | |||
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil { | |||
log.Error("failed to GetBlobContent: %v", err) | |||
} else { | |||
if profileContent, err := markdown.RenderString(&markup.RenderContext{ | |||
Ctx: ctx, | |||
GitRepo: profileGitRepo, | |||
Metas: map[string]string{"mode": "document"}, | |||
}, bytes); err != nil { | |||
log.Error("failed to RenderString: %v", err) | |||
} else { | |||
ctx.Data["ProfileReadme"] = profileContent | |||
} | |||
} | |||
} |
@@ -716,14 +716,11 @@ func checkCitationFile(ctx *context.Context, entry *git.TreeEntry) { | |||
return | |||
} | |||
defer dataRc.Close() | |||
buf := make([]byte, 1024) | |||
n, err := util.ReadAtMost(dataRc, buf) | |||
ctx.PageData["citationFileContent"], err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize) | |||
if err != nil { | |||
ctx.ServerError("ReadAtMost", err) | |||
ctx.ServerError("GetBlobContent", err) | |||
return | |||
} | |||
buf = buf[:n] | |||
ctx.PageData["citationFileContent"] = string(buf) | |||
break | |||
} | |||
} |
@@ -6,12 +6,14 @@ package security | |||
import ( | |||
"net/http" | |||
"sort" | |||
auth_model "code.gitea.io/gitea/models/auth" | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/modules/base" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/util" | |||
"code.gitea.io/gitea/services/auth/source/oauth2" | |||
) | |||
@@ -82,7 +84,7 @@ func loadSecurityData(ctx *context.Context) { | |||
// map the provider display name with the AuthSource | |||
sources := make(map[*auth_model.Source]string) | |||
for _, externalAccount := range accountLinks { | |||
if authSource, err := auth_model.GetSourceByID(ctx, externalAccount.LoginSourceID); err == nil && authSource.IsActive { | |||
if authSource, err := auth_model.GetSourceByID(ctx, externalAccount.LoginSourceID); err == nil { | |||
var providerDisplayName string | |||
type DisplayNamed interface { | |||
@@ -105,11 +107,31 @@ func loadSecurityData(ctx *context.Context) { | |||
} | |||
ctx.Data["AccountLinks"] = sources | |||
orderedOAuth2Names, oauth2Providers, err := oauth2.GetActiveOAuth2Providers(ctx) | |||
authSources, err := auth_model.FindSources(ctx, auth_model.FindSourcesOptions{ | |||
IsActive: util.OptionalBoolNone, | |||
LoginType: auth_model.OAuth2, | |||
}) | |||
if err != nil { | |||
ctx.ServerError("GetActiveOAuth2Providers", err) | |||
ctx.ServerError("FindSources", err) | |||
return | |||
} | |||
var orderedOAuth2Names []string | |||
oauth2Providers := make(map[string]oauth2.Provider) | |||
for _, source := range authSources { | |||
provider, err := oauth2.CreateProviderFromSource(source) | |||
if err != nil { | |||
ctx.ServerError("CreateProviderFromSource", err) | |||
return | |||
} | |||
oauth2Providers[source.Name] = provider | |||
if source.IsActive { | |||
orderedOAuth2Names = append(orderedOAuth2Names, source.Name) | |||
} | |||
} | |||
sort.Strings(orderedOAuth2Names) | |||
ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names | |||
ctx.Data["OAuth2Providers"] = oauth2Providers | |||
@@ -276,6 +276,8 @@ func Routes() *web.Route { | |||
return routes | |||
} | |||
var ignSignInAndCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true}) | |||
// registerRoutes register routes | |||
func registerRoutes(m *web.Route) { | |||
reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true}) | |||
@@ -283,7 +285,7 @@ func registerRoutes(m *web.Route) { | |||
// TODO: rename them to "optSignIn", which means that the "sign-in" could be optional, depends on the VerifyOptions (RequireSignInView) | |||
ignSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView}) | |||
ignExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView}) | |||
ignSignInAndCsrf := verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true}) | |||
validation.AddBindingRules() | |||
linkAccountEnabled := func(ctx *context.Context) { | |||
@@ -1512,19 +1514,7 @@ func registerRoutes(m *web.Route) { | |||
}) | |||
}, ignSignInAndCsrf, lfsServerEnabled) | |||
m.Group("", func() { | |||
m.PostOptions("/git-upload-pack", repo.ServiceUploadPack) | |||
m.PostOptions("/git-receive-pack", repo.ServiceReceivePack) | |||
m.GetOptions("/info/refs", repo.GetInfoRefs) | |||
m.GetOptions("/HEAD", repo.GetTextFile("HEAD")) | |||
m.GetOptions("/objects/info/alternates", repo.GetTextFile("objects/info/alternates")) | |||
m.GetOptions("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates")) | |||
m.GetOptions("/objects/info/packs", repo.GetInfoPacks) | |||
m.GetOptions("/objects/info/{file:[^/]*}", repo.GetTextFile("")) | |||
m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject) | |||
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile) | |||
m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile) | |||
}, ignSignInAndCsrf, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb()) | |||
gitHTTPRouters(m) | |||
}) | |||
}) | |||
// ***** END: Repository ***** |
@@ -15,6 +15,7 @@ import ( | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/timeutil" | |||
"code.gitea.io/gitea/modules/util" | |||
"code.gitea.io/gitea/modules/web/middleware" | |||
) | |||
@@ -131,11 +132,30 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore | |||
return nil, err | |||
} | |||
if skipper, ok := source.Cfg.(LocalTwoFASkipper); ok && skipper.IsSkipLocalTwoFA() { | |||
store.GetData()["SkipLocalTwoFA"] = true | |||
if skipper, ok := source.Cfg.(LocalTwoFASkipper); !ok || !skipper.IsSkipLocalTwoFA() { | |||
if err := validateTOTP(req, u); err != nil { | |||
return nil, err | |||
} | |||
} | |||
log.Trace("Basic Authorization: Logged in user %-v", u) | |||
return u, nil | |||
} | |||
func validateTOTP(req *http.Request, u *user_model.User) error { | |||
twofa, err := auth_model.GetTwoFactorByUID(req.Context(), u.ID) | |||
if err != nil { | |||
if auth_model.IsErrTwoFactorNotEnrolled(err) { | |||
// No 2FA enrollment for this user | |||
return nil | |||
} | |||
return err | |||
} | |||
if ok, err := twofa.ValidateTOTP(req.Header.Get("X-Gitea-OTP")); err != nil { | |||
return err | |||
} else if !ok { | |||
return util.NewInvalidArgumentErrorf("invalid provided OTP") | |||
} | |||
return nil | |||
} |
@@ -11,6 +11,7 @@ import ( | |||
"code.gitea.io/gitea/models/db" | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/util" | |||
"code.gitea.io/gitea/services/auth/source/oauth2" | |||
"code.gitea.io/gitea/services/auth/source/smtp" | |||
@@ -85,7 +86,9 @@ func UserSignIn(ctx context.Context, username, password string) (*user_model.Use | |||
} | |||
} | |||
sources, err := auth.AllActiveSources(ctx) | |||
sources, err := auth.FindSources(ctx, auth.FindSourcesOptions{ | |||
IsActive: util.OptionalBoolTrue, | |||
}) | |||
if err != nil { | |||
return nil, nil, err | |||
} |
@@ -12,6 +12,7 @@ import ( | |||
"code.gitea.io/gitea/models/auth" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/util" | |||
"github.com/google/uuid" | |||
"github.com/gorilla/sessions" | |||
@@ -63,7 +64,13 @@ func ResetOAuth2(ctx context.Context) error { | |||
// initOAuth2Sources is used to load and register all active OAuth2 providers | |||
func initOAuth2Sources(ctx context.Context) error { | |||
authSources, _ := auth.GetActiveOAuth2ProviderSources(ctx) | |||
authSources, err := auth.FindSources(ctx, auth.FindSourcesOptions{ | |||
IsActive: util.OptionalBoolTrue, | |||
LoginType: auth.OAuth2, | |||
}) | |||
if err != nil { | |||
return err | |||
} | |||
for _, source := range authSources { | |||
oauth2Source, ok := source.Cfg.(*Source) | |||
if !ok { |
@@ -15,6 +15,7 @@ import ( | |||
"code.gitea.io/gitea/models/auth" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/util" | |||
"github.com/markbates/goth" | |||
) | |||
@@ -80,10 +81,10 @@ func RegisterGothProvider(provider GothProvider) { | |||
gothProviders[provider.Name()] = provider | |||
} | |||
// GetOAuth2Providers returns the map of unconfigured OAuth2 providers | |||
// GetSupportedOAuth2Providers returns the map of unconfigured OAuth2 providers | |||
// key is used as technical name (like in the callbackURL) | |||
// values to display | |||
func GetOAuth2Providers() []Provider { | |||
func GetSupportedOAuth2Providers() []Provider { | |||
providers := make([]Provider, 0, len(gothProviders)) | |||
for _, provider := range gothProviders { | |||
@@ -95,33 +96,39 @@ func GetOAuth2Providers() []Provider { | |||
return providers | |||
} | |||
// GetActiveOAuth2Providers returns the map of configured active OAuth2 providers | |||
// key is used as technical name (like in the callbackURL) | |||
// values to display | |||
func GetActiveOAuth2Providers(ctx context.Context) ([]string, map[string]Provider, error) { | |||
// Maybe also separate used and unused providers so we can force the registration of only 1 active provider for each type | |||
func CreateProviderFromSource(source *auth.Source) (Provider, error) { | |||
oauth2Cfg, ok := source.Cfg.(*Source) | |||
if !ok { | |||
return nil, fmt.Errorf("invalid OAuth2 source config: %v", oauth2Cfg) | |||
} | |||
gothProv := gothProviders[oauth2Cfg.Provider] | |||
return &AuthSourceProvider{GothProvider: gothProv, sourceName: source.Name, iconURL: oauth2Cfg.IconURL}, nil | |||
} | |||
authSources, err := auth.GetActiveOAuth2ProviderSources(ctx) | |||
// GetOAuth2Providers returns the list of configured OAuth2 providers | |||
func GetOAuth2Providers(ctx context.Context, isActive util.OptionalBool) ([]Provider, error) { | |||
authSources, err := auth.FindSources(ctx, auth.FindSourcesOptions{ | |||
IsActive: isActive, | |||
LoginType: auth.OAuth2, | |||
}) | |||
if err != nil { | |||
return nil, nil, err | |||
return nil, err | |||
} | |||
var orderedKeys []string | |||
providers := make(map[string]Provider) | |||
providers := make([]Provider, 0, len(authSources)) | |||
for _, source := range authSources { | |||
oauth2Cfg, ok := source.Cfg.(*Source) | |||
if !ok { | |||
log.Error("Invalid OAuth2 source config: %v", oauth2Cfg) | |||
continue | |||
provider, err := CreateProviderFromSource(source) | |||
if err != nil { | |||
return nil, err | |||
} | |||
gothProv := gothProviders[oauth2Cfg.Provider] | |||
providers[source.Name] = &AuthSourceProvider{GothProvider: gothProv, sourceName: source.Name, iconURL: oauth2Cfg.IconURL} | |||
orderedKeys = append(orderedKeys, source.Name) | |||
providers = append(providers, provider) | |||
} | |||
sort.Strings(orderedKeys) | |||
sort.Slice(providers, func(i, j int) bool { | |||
return providers[i].Name() < providers[j].Name() | |||
}) | |||
return orderedKeys, providers, nil | |||
return providers, nil | |||
} | |||
// RegisterProviderWithGothic register a OAuth2 provider in goth lib |
@@ -130,7 +130,10 @@ func (s *SSPI) Verify(req *http.Request, w http.ResponseWriter, store DataStore, | |||
// getConfig retrieves the SSPI configuration from login sources | |||
func (s *SSPI) getConfig(ctx context.Context) (*sspi.Source, error) { | |||
sources, err := auth.ActiveSources(ctx, auth.SSPI) | |||
sources, err := auth.FindSources(ctx, auth.FindSourcesOptions{ | |||
IsActive: util.OptionalBoolTrue, | |||
LoginType: auth.SSPI, | |||
}) | |||
if err != nil { | |||
return nil, err | |||
} |
@@ -15,7 +15,7 @@ import ( | |||
func SyncExternalUsers(ctx context.Context, updateExisting bool) error { | |||
log.Trace("Doing: SyncExternalUsers") | |||
ls, err := auth.Sources(ctx) | |||
ls, err := auth.FindSources(ctx, auth.FindSourcesOptions{}) | |||
if err != nil { | |||
log.Error("SyncExternalUsers: %v", err) | |||
return err |
@@ -282,6 +282,8 @@ func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Rele | |||
httpClient := NewMigrationHTTPClient() | |||
for _, asset := range rel.Attachments { | |||
assetID := asset.ID // Don't optimize this, for closure we need a local variable | |||
assetDownloadURL := asset.DownloadURL | |||
size := int(asset.Size) | |||
dlCount := int(asset.DownloadCount) | |||
r.Assets = append(r.Assets, &base.ReleaseAsset{ | |||
@@ -292,18 +294,18 @@ func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Rele | |||
Created: asset.Created, | |||
DownloadURL: &asset.DownloadURL, | |||
DownloadFunc: func() (io.ReadCloser, error) { | |||
asset, _, err := g.client.GetReleaseAttachment(g.repoOwner, g.repoName, rel.ID, asset.ID) | |||
asset, _, err := g.client.GetReleaseAttachment(g.repoOwner, g.repoName, rel.ID, assetID) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if !hasBaseURL(asset.DownloadURL, g.baseURL) { | |||
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", asset.ID, g, asset.DownloadURL) | |||
if !hasBaseURL(assetDownloadURL, g.baseURL) { | |||
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", assetID, g, assetDownloadURL) | |||
return io.NopCloser(strings.NewReader(asset.DownloadURL)), nil | |||
} | |||
// FIXME: for a private download? | |||
req, err := http.NewRequest("GET", asset.DownloadURL, nil) | |||
req, err := http.NewRequest("GET", assetDownloadURL, nil) | |||
if err != nil { | |||
return nil, err | |||
} |
@@ -310,6 +310,7 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea | |||
httpClient := NewMigrationHTTPClient() | |||
for k, asset := range rel.Assets.Links { | |||
assetID := asset.ID // Don't optimize this, for closure we need a local variable | |||
r.Assets = append(r.Assets, &base.ReleaseAsset{ | |||
ID: int64(asset.ID), | |||
Name: asset.Name, | |||
@@ -317,13 +318,13 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea | |||
Size: &zero, | |||
DownloadCount: &zero, | |||
DownloadFunc: func() (io.ReadCloser, error) { | |||
link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, rel.TagName, asset.ID, gitlab.WithContext(g.ctx)) | |||
link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, rel.TagName, assetID, gitlab.WithContext(g.ctx)) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if !hasBaseURL(link.URL, g.baseURL) { | |||
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", asset.ID, g, link.URL) | |||
WarnAndNotice("Unexpected AssetURL for assetID[%d] in %s: %s", assetID, g, link.URL) | |||
return io.NopCloser(strings.NewReader(link.URL)), nil | |||
} | |||
@@ -10,6 +10,7 @@ import ( | |||
_ "image/jpeg" // Needed for jpeg support | |||
actions_model "code.gitea.io/gitea/models/actions" | |||
activities_model "code.gitea.io/gitea/models/activities" | |||
asymkey_model "code.gitea.io/gitea/models/asymkey" | |||
auth_model "code.gitea.io/gitea/models/auth" | |||
@@ -90,6 +91,7 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error) | |||
&pull_model.AutoMerge{DoerID: u.ID}, | |||
&pull_model.ReviewState{UserID: u.ID}, | |||
&user_model.Redirect{RedirectUserID: u.ID}, | |||
&actions_model.ActionRunner{OwnerID: u.ID}, | |||
); err != nil { | |||
return fmt.Errorf("deleteBeans: %w", err) | |||
} |
@@ -38,6 +38,9 @@ | |||
<div class="ui container"> | |||
<div class="ui mobile reversed stackable grid"> | |||
<div class="ui {{if .ShowMemberAndTeamTab}}eleven wide{{end}} column"> | |||
{{if .ProfileReadme}} | |||
<div id="readme_profile" class="markup">{{.ProfileReadme | Str2html}}</div> | |||
{{end}} | |||
{{template "explore/repo_search" .}} | |||
{{template "explore/repo_list" .}} | |||
{{template "base/paginate" .}} |
@@ -19,7 +19,7 @@ | |||
{{end}} | |||
<div class="ui top attached header clearing segment gt-relative commit-header {{$class}}"> | |||
<div class="gt-df gt-mb-4 gt-fw"> | |||
<h3 class="gt-mb-0 gt-f1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message $.RepoLink ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses "root" $}}</h3> | |||
<h3 class="gt-mb-0 gt-f1"><span class="commit-summary" title="{{.Commit.Summary}}">{{RenderCommitMessage $.Context .Commit.Message $.RepoLink ($.Repository.ComposeMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3> | |||
{{if not $.PageIsWiki}} | |||
<div> | |||
<a class="ui primary tiny button" href="{{.SourcePath}}"> |
@@ -8,15 +8,7 @@ | |||
{{template "repo/commit_status" .Status}} | |||
</span> | |||
{{end}} | |||
<div class="tippy-target ui relaxed list divided"> | |||
{{range .Statuses}} | |||
<div class="ui item singular-status gt-df"> | |||
{{template "repo/commit_status" .}} | |||
<span class="ui gt-ml-3 gt-f1">{{.Context}} <span class="text grey">{{.Description}}</span></span> | |||
{{if .TargetURL}} | |||
<a class="gt-ml-3" href="{{.TargetURL}}" target="_blank" rel="noopener noreferrer">{{ctx.Locale.Tr "repo.pulls.status_checks_details"}}</a> | |||
{{end}} | |||
</div> | |||
{{end}} | |||
<div class="tippy-target"> | |||
{{template "repo/pulls/status" (dict "CommitStatuses" .Statuses "CommitStatus" .Status)}} | |||
</div> | |||
{{end}} |
@@ -66,7 +66,7 @@ | |||
{{if IsMultilineCommitMessage .Message}} | |||
<button class="ui button js-toggle-commit-body ellipsis-button" aria-expanded="false">...</button> | |||
{{end}} | |||
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses "root" $}} | |||
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}} | |||
{{if IsMultilineCommitMessage .Message}} | |||
<pre class="commit-body gt-hidden">{{RenderCommitBody $.Context .Message $commitRepoLink ($.Repository.ComposeMetas ctx)}}</pre> | |||
{{end}} |
@@ -14,7 +14,7 @@ | |||
{{$commitLink:= printf "%s/commit/%s" $.comment.Issue.PullRequest.BaseRepo.Link (PathEscape .ID.String)}} | |||
<span class="shabox gt-df gt-ac gt-float-right"> | |||
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses "root" $.root}} | |||
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}} | |||
{{$class := "ui sha label"}} | |||
{{if .Signature}} | |||
{{$class = (print $class " isSigned")}} |
@@ -53,7 +53,7 @@ | |||
{{if not .DiffNotAvailable}} | |||
{{if and .IsShowingOnlySingleCommit .PageIsPullFiles}} | |||
<div class="ui info message"> | |||
<div>{{ctx.Locale.Tr "repo.pulls.showing_only_single_commit" (ShortSha .BeforeCommitID)}} - <a href="{{$.Issue.Link}}/files?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated={{$.ShowOutdatedComments}}">{{ctx.Locale.Tr "repo.pulls.show_all_commits"}}</a></div> | |||
<div>{{ctx.Locale.Tr "repo.pulls.showing_only_single_commit" (ShortSha .AfterCommitID)}} - <a href="{{$.Issue.Link}}/files?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated={{$.ShowOutdatedComments}}">{{ctx.Locale.Tr "repo.pulls.show_all_commits"}}</a></div> | |||
</div> | |||
{{else if and (not .IsShowingAllCommits) .PageIsPullFiles}} | |||
<div class="ui info message"> | |||
@@ -238,8 +238,8 @@ | |||
"DropzoneParentContainer" ".ui.form" | |||
)}} | |||
<div class="text right edit buttons"> | |||
<button class="ui cancel button" tabindex="3">{{ctx.Locale.Tr "repo.issues.cancel"}}</button> | |||
<button class="ui primary save button" tabindex="2">{{ctx.Locale.Tr "repo.issues.save"}}</button> | |||
<button class="ui cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button> | |||
<button class="ui primary save button">{{ctx.Locale.Tr "repo.issues.save"}}</button> | |||
</div> | |||
</div> | |||
</template> |
@@ -5,7 +5,7 @@ | |||
{{template "base/alert" .}} | |||
{{template "repo/code/recently_pushed_new_branches" .}} | |||
{{if and (not .HideRepoInfo) (not .IsBlame)}} | |||
<div class="ui repo-description"> | |||
<div class="ui repo-description gt-word-break"> | |||
<div id="repo-desc" class="gt-font-16"> | |||
{{$description := .Repository.DescriptionHTML $.Context}} | |||
{{if $description}}<span class="description">{{$description | RenderCodeBlock}}</span>{{else if .IsRepositoryAdmin}}<span class="no-description text-italic">{{ctx.Locale.Tr "repo.no_desc"}}</span>{{end}} | |||
@@ -35,9 +35,9 @@ | |||
</div> | |||
{{end}} | |||
{{if and .Permission.IsAdmin (not .Repository.IsArchived)}} | |||
<div class="ui form gt-hidden gt-df gt-mt-4" id="topic_edit"> | |||
<div class="field gt-f1 gt-mr-3"> | |||
<div class="ui fluid multiple search selection dropdown" data-text-count-prompt="{{ctx.Locale.Tr "repo.topic.count_prompt"}}" data-text-format-prompt="{{ctx.Locale.Tr "repo.topic.format_prompt"}}"> | |||
<div class="ui form gt-hidden gt-df gt-fc gt-mt-4" id="topic_edit"> | |||
<div class="field gt-f1 gt-mb-2"> | |||
<div class="ui fluid multiple search selection dropdown gt-fw" data-text-count-prompt="{{ctx.Locale.Tr "repo.topic.count_prompt"}}" data-text-format-prompt="{{ctx.Locale.Tr "repo.topic.format_prompt"}}"> | |||
<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}"> | |||
{{range .Topics}} | |||
{{/* keey the same layout as Fomantic UI generated labels */}} |
@@ -49,6 +49,13 @@ | |||
</div> | |||
{{end}} | |||
{{end}} | |||
{{$tasks := .GetTasks}} | |||
{{if gt $tasks 0}} | |||
<div class="meta gt-my-2"> | |||
{{svg "octicon-checklist" 16 "gt-mr-2 gt-vm"}} | |||
<span class="gt-vm">{{.GetTasksDone}} / {{$tasks}}</span> | |||
</div> | |||
{{end}} | |||
</div> | |||
{{if or .Labels .Assignees}} |
@@ -92,7 +92,7 @@ | |||
<div class="text right"> | |||
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .DisableStatusChange)}} | |||
{{if .Issue.IsClosed}} | |||
<button id="status-button" class="ui primary basic button" tabindex="6" data-status="{{ctx.Locale.Tr "repo.issues.reopen_issue"}}" data-status-and-comment="{{ctx.Locale.Tr "repo.issues.reopen_comment_issue"}}" name="status" value="reopen"> | |||
<button id="status-button" class="ui primary basic button" data-status="{{ctx.Locale.Tr "repo.issues.reopen_issue"}}" data-status-and-comment="{{ctx.Locale.Tr "repo.issues.reopen_comment_issue"}}" name="status" value="reopen"> | |||
{{ctx.Locale.Tr "repo.issues.reopen_issue"}} | |||
</button> | |||
{{else}} | |||
@@ -100,12 +100,12 @@ | |||
{{if .Issue.IsPull}} | |||
{{$closeTranslationKey = "repo.pulls.close"}} | |||
{{end}} | |||
<button id="status-button" class="ui red basic button" tabindex="6" data-status="{{ctx.Locale.Tr $closeTranslationKey}}" data-status-and-comment="{{ctx.Locale.Tr "repo.issues.close_comment_issue"}}" name="status" value="close"> | |||
<button id="status-button" class="ui red basic button" data-status="{{ctx.Locale.Tr $closeTranslationKey}}" data-status-and-comment="{{ctx.Locale.Tr "repo.issues.close_comment_issue"}}" name="status" value="close"> | |||
{{ctx.Locale.Tr $closeTranslationKey}} | |||
</button> | |||
{{end}} | |||
{{end}} | |||
<button class="ui primary button" tabindex="5"> | |||
<button class="ui primary button"> | |||
{{ctx.Locale.Tr "repo.issues.create_comment"}} | |||
</button> | |||
</div> | |||
@@ -162,8 +162,8 @@ | |||
<div class="field"> | |||
<div class="text right edit"> | |||
<button class="ui basic cancel button" tabindex="3">{{ctx.Locale.Tr "repo.issues.cancel"}}</button> | |||
<button class="ui primary save button" tabindex="2">{{ctx.Locale.Tr "repo.issues.save"}}</button> | |||
<button class="ui basic cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button> | |||
<button class="ui primary save button">{{ctx.Locale.Tr "repo.issues.save"}}</button> | |||
</div> | |||
</div> | |||
</div> |
@@ -4,7 +4,8 @@ | |||
{{$createdStr:= TimeSinceUnix .CreatedUnix ctx.Locale}} | |||
<!-- 0 = COMMENT, 1 = REOPEN, 2 = CLOSE, 3 = ISSUE_REF, 4 = COMMIT_REF, | |||
5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 12 = START_TRACKING, | |||
5 = COMMENT_REF, 6 = PULL_REF, 7 = COMMENT_LABEL, 8 = MILESTONE_CHANGE, | |||
9 = ASSIGNEES_CHANGE, 10 = TITLE_CHANGE, 11 = DELETE_BRANCH, 12 = START_TRACKING, | |||
13 = STOP_TRACKING, 14 = ADD_TIME_MANUAL, 16 = ADDED_DEADLINE, 17 = MODIFIED_DEADLINE, | |||
18 = REMOVED_DEADLINE, 19 = ADD_DEPENDENCY, 20 = REMOVE_DEPENDENCY, 21 = CODE, | |||
22 = REVIEW, 23 = ISSUE_LOCKED, 24 = ISSUE_UNLOCKED, 25 = TARGET_BRANCH_CHANGED, | |||
@@ -184,31 +185,29 @@ | |||
{{if gt .OldMilestoneID 0}}{{if gt .MilestoneID 0}}{{ctx.Locale.Tr "repo.issues.change_milestone_at" (.OldMilestone.Name|Escape) (.Milestone.Name|Escape) $createdStr | Safe}}{{else}}{{ctx.Locale.Tr "repo.issues.remove_milestone_at" (.OldMilestone.Name|Escape) $createdStr | Safe}}{{end}}{{else if gt .MilestoneID 0}}{{ctx.Locale.Tr "repo.issues.add_milestone_at" (.Milestone.Name|Escape) $createdStr | Safe}}{{end}} | |||
</span> | |||
</div> | |||
{{else if eq .Type 9}} | |||
{{else if and (eq .Type 9) (gt .AssigneeID 0)}} | |||
<div class="timeline-item event" id="{{.HashTag}}"> | |||
<span class="badge">{{svg "octicon-person"}}</span> | |||
{{if gt .AssigneeID 0}} | |||
{{if .RemovedAssignee}} | |||
{{template "shared/user/avatarlink" dict "user" .Assignee}} | |||
<span class="text grey muted-links"> | |||
{{template "shared/user/authorlink" .Assignee}} | |||
{{if eq .Poster.ID .Assignee.ID}} | |||
{{ctx.Locale.Tr "repo.issues.remove_self_assignment" $createdStr | Safe}} | |||
{{else}} | |||
{{ctx.Locale.Tr "repo.issues.remove_assignee_at" (.Poster.GetDisplayName|Escape) $createdStr | Safe}} | |||
{{end}} | |||
</span> | |||
{{else}} | |||
{{template "shared/user/avatarlink" dict "user" .Assignee}} | |||
<span class="text grey muted-links"> | |||
{{template "shared/user/authorlink" .Assignee}} | |||
{{if eq .Poster.ID .AssigneeID}} | |||
{{ctx.Locale.Tr "repo.issues.self_assign_at" $createdStr | Safe}} | |||
{{else}} | |||
{{ctx.Locale.Tr "repo.issues.add_assignee_at" (.Poster.GetDisplayName|Escape) $createdStr | Safe}} | |||
{{end}} | |||
</span> | |||
{{end}} | |||
{{if .RemovedAssignee}} | |||
{{template "shared/user/avatarlink" dict "user" .Assignee}} | |||
<span class="text grey muted-links"> | |||
{{template "shared/user/authorlink" .Assignee}} | |||
{{if eq .Poster.ID .Assignee.ID}} | |||
{{ctx.Locale.Tr "repo.issues.remove_self_assignment" $createdStr | Safe}} | |||
{{else}} | |||
{{ctx.Locale.Tr "repo.issues.remove_assignee_at" (.Poster.GetDisplayName|Escape) $createdStr | Safe}} | |||
{{end}} | |||
</span> | |||
{{else}} | |||
{{template "shared/user/avatarlink" dict "user" .Assignee}} | |||
<span class="text grey muted-links"> | |||
{{template "shared/user/authorlink" .Assignee}} | |||
{{if eq .Poster.ID .AssigneeID}} | |||
{{ctx.Locale.Tr "repo.issues.self_assign_at" $createdStr | Safe}} | |||
{{else}} | |||
{{ctx.Locale.Tr "repo.issues.add_assignee_at" (.Poster.GetDisplayName|Escape) $createdStr | Safe}} | |||
{{end}} | |||
</span> | |||
{{end}} | |||
</div> | |||
{{else if eq .Type 10}} |
@@ -20,7 +20,14 @@ | |||
{{- else if .Issue.PullRequest.CanAutoMerge}}green | |||
{{- else}}red{{end}}">{{svg "octicon-git-merge" 40}}</div> | |||
<div class="content"> | |||
{{template "repo/pulls/status" .}} | |||
<div class="ui attached segment fitted"> | |||
{{template "repo/pulls/status" (dict | |||
"CommitStatus" .LatestCommitStatus | |||
"CommitStatuses" .LatestCommitStatuses | |||
"ShowHideChecks" true | |||
"is_context_required" .is_context_required | |||
)}} | |||
</div> | |||
{{$showGeneralMergeForm := false}} | |||
<div class="ui attached merge-section segment {{if not $.LatestCommitStatus}}no-header{{end}} flex-items-block"> | |||
{{if .Issue.PullRequest.HasMerged}} |
@@ -1,27 +1,43 @@ | |||
{{if $.LatestCommitStatus}} | |||
{{if not $.Issue.PullRequest.HasMerged}} | |||
<div class="ui top attached header"> | |||
{{if eq .LatestCommitStatus.State "pending"}} | |||
{{ctx.Locale.Tr "repo.pulls.status_checking"}} | |||
{{else if eq .LatestCommitStatus.State "success"}} | |||
{{ctx.Locale.Tr "repo.pulls.status_checks_success"}} | |||
{{else if eq .LatestCommitStatus.State "warning"}} | |||
{{ctx.Locale.Tr "repo.pulls.status_checks_warning"}} | |||
{{else if eq .LatestCommitStatus.State "failure"}} | |||
{{ctx.Locale.Tr "repo.pulls.status_checks_failure"}} | |||
{{else if eq .LatestCommitStatus.State "error"}} | |||
{{ctx.Locale.Tr "repo.pulls.status_checks_error"}} | |||
{{else}} | |||
{{ctx.Locale.Tr "repo.pulls.status_checking"}} | |||
{{end}} | |||
</div> | |||
{{end}} | |||
{{/* | |||
Template Attributes: | |||
* CommitStatus: summary of all commit status state | |||
* CommitStatuses: all commit status elements | |||
* ShowHideChecks: whether use a button to show/hide the checks | |||
* is_context_required: Used in pull request commit status check table | |||
*/}} | |||
{{if .CommitStatus}} | |||
<div class="commit-status-panel"> | |||
<div class="ui top attached header commit-status-header"> | |||
{{if eq .CommitStatus.State "pending"}} | |||
{{ctx.Locale.Tr "repo.pulls.status_checking"}} | |||
{{else if eq .CommitStatus.State "success"}} | |||
{{ctx.Locale.Tr "repo.pulls.status_checks_success"}} | |||
{{else if eq .CommitStatus.State "warning"}} | |||
{{ctx.Locale.Tr "repo.pulls.status_checks_warning"}} | |||
{{else if eq .CommitStatus.State "failure"}} | |||
{{ctx.Locale.Tr "repo.pulls.status_checks_failure"}} | |||
{{else if eq .CommitStatus.State "error"}} | |||
{{ctx.Locale.Tr "repo.pulls.status_checks_error"}} | |||
{{else}} | |||
{{ctx.Locale.Tr "repo.pulls.status_checking"}} | |||
{{end}} | |||
{{range $.LatestCommitStatuses}} | |||
<div class="ui attached segment pr-status"> | |||
{{template "repo/commit_status" .}} | |||
<div class="status-context"> | |||
<span>{{.Context}} <span class="text grey">{{.Description}}</span></span> | |||
{{if .ShowHideChecks}} | |||
<div class="ui right"> | |||
<button class="commit-status-hide-checks btn interact-fg" | |||
data-show-all="{{ctx.Locale.Tr "repo.pulls.status_checks_show_all"}}" | |||
data-hide-all="{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}}"> | |||
{{ctx.Locale.Tr "repo.pulls.status_checks_hide_all"}}</button> | |||
</div> | |||
{{end}} | |||
</div> | |||
<div class="commit-status-list"> | |||
{{range .CommitStatuses}} | |||
<div class="commit-status-item"> | |||
{{template "repo/commit_status" .}} | |||
<div class="status-context gt-ellipsis">{{.Context}} <span class="text light-2">{{.Description}}</span></div> | |||
<div class="ui status-details"> | |||
{{if $.is_context_required}} | |||
{{if (call $.is_context_required .Context)}}<div class="ui label">{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}</div>{{end}} | |||
@@ -29,6 +45,7 @@ | |||
<span>{{if .TargetURL}}<a href="{{.TargetURL}}">{{ctx.Locale.Tr "repo.pulls.status_checks_details"}}</a>{{end}}</span> | |||
</div> | |||
</div> | |||
</div> | |||
{{end}} | |||
{{end}} | |||
</div> | |||
</div> | |||
{{end}} |
@@ -16,7 +16,7 @@ | |||
</div> | |||
<div class="ui twelve wide column detail"> | |||
<div class="gt-df gt-ac gt-sb gt-fw gt-mb-3"> | |||
<h4 class="release-list-title"> | |||
<h4 class="release-list-title gt-word-break"> | |||
<a href="{{$.RepoLink}}/releases/tag/{{.TagName | PathEscapeSegments}}">{{.Title}}</a> | |||
{{if .IsDraft}} | |||
<span class="ui yellow label">{{ctx.Locale.Tr "repo.release.draft"}}</span> |
@@ -24,7 +24,7 @@ | |||
{{template "repo/shabox_badge" dict "root" $ "verification" .LatestCommitVerification}} | |||
{{end}} | |||
</a> | |||
{{template "repo/commit_statuses" dict "Status" .LatestCommitStatus "Statuses" .LatestCommitStatuses "root" $}} | |||
{{template "repo/commit_statuses" dict "Status" .LatestCommitStatus "Statuses" .LatestCommitStatuses}} | |||
{{$commitLink:= printf "%s/commit/%s" .RepoLink (PathEscape .LatestCommit.ID.String)}} | |||
<span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span class="message-wrapper">{{RenderCommitMessageLinkSubject $.Context .LatestCommit.Message $.RepoLink $commitLink ($.Repository.ComposeMetas ctx)}}</span> | |||
{{if IsMultilineCommitMessage .LatestCommit.Message}} |
@@ -16,7 +16,7 @@ | |||
<a class="gt-no-underline issue-title" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">{{RenderEmoji $.Context .Title | RenderCodeBlock}}</a> | |||
{{if .IsPull}} | |||
{{if (index $.CommitStatuses .PullRequest.ID)}} | |||
{{template "repo/commit_statuses" dict "Status" (index $.CommitLastStatus .PullRequest.ID) "Statuses" (index $.CommitStatuses .PullRequest.ID) "root" $}} | |||
{{template "repo/commit_statuses" dict "Status" (index $.CommitLastStatus .PullRequest.ID) "Statuses" (index $.CommitStatuses .PullRequest.ID)}} | |||
{{end}} | |||
{{end}} | |||
<span class="labels-list gt-ml-2"> |
@@ -52,16 +52,15 @@ | |||
</div> | |||
{{end}} | |||
{{if and .OrderedOAuth2Names .OAuth2Providers}} | |||
{{if .OAuth2Providers}} | |||
<div class="divider divider-text"> | |||
{{ctx.Locale.Tr "sign_in_or"}} | |||
</div> | |||
<div id="oauth2-login-navigator" class="gt-py-2"> | |||
<div class="gt-df gt-fc gt-jc"> | |||
<div id="oauth2-login-navigator-inner" class="gt-df gt-fc gt-fw gt-ac gt-gap-3"> | |||
{{range $key := .OrderedOAuth2Names}} | |||
{{$provider := index $.OAuth2Providers $key}} | |||
<a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$key}}"> | |||
{{range $provider := .OAuth2Providers}} | |||
<a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}"> | |||
{{$provider.IconHTML 28}} | |||
{{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}} | |||
</a> |
@@ -56,16 +56,15 @@ | |||
{{end}} | |||
{{end}} | |||
{{if and .OrderedOAuth2Names .OAuth2Providers}} | |||
{{if .OAuth2Providers}} | |||
<div class="divider divider-text"> | |||
{{ctx.Locale.Tr "sign_in_or"}} | |||
</div> | |||
<div id="oauth2-login-navigator" class="gt-py-2"> | |||
<div class="gt-df gt-fc gt-jc"> | |||
<div id="oauth2-login-navigator-inner" class="gt-df gt-fc gt-fw gt-ac gt-gap-3"> | |||
{{range $key := .OrderedOAuth2Names}} | |||
{{$provider := index $.OAuth2Providers $key}} | |||
<a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$key}}"> | |||
{{range $provider := .OAuth2Providers}} | |||
<a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}"> | |||
{{$provider.IconHTML 28}} | |||
{{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}} | |||
</a> |
@@ -2,8 +2,8 @@ | |||
<div role="main" aria-label="{{.Title}}" class="page-content dashboard issues"> | |||
{{template "user/dashboard/navbar" .}} | |||
<div class="ui container"> | |||
<div class="ui stackable grid"> | |||
<div class="four wide column"> | |||
<div class="flex-container"> | |||
<div class="flex-container-nav"> | |||
<div class="ui secondary vertical filter menu gt-bg-transparent"> | |||
<a class="{{if eq .ViewType "your_repositories"}}active{{end}} item" href="{{.Link}}?type=your_repositories&repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state={{.State}}&q={{$.Keyword}}"> | |||
{{ctx.Locale.Tr "home.issues.in_your_repos"}} | |||
@@ -59,7 +59,7 @@ | |||
{{end}} | |||
</div> | |||
</div> | |||
<div class="twelve wide column content"> | |||
<div class="flex-container-main content"> | |||
<div class="list-header"> | |||
<div class="small-menu-items ui compact tiny menu list-header-toggle"> | |||
<a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.Link}}?type={{$.ViewType}}&repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=open&q={{$.Keyword}}"> |
@@ -2,8 +2,8 @@ | |||
<div role="main" aria-label="{{.Title}}" class="page-content dashboard issues repository milestones"> | |||
{{template "user/dashboard/navbar" .}} | |||
<div class="ui container"> | |||
<div class="ui stackable grid"> | |||
<div class="four wide column"> | |||
<div class="flex-container"> | |||
<div class="flex-container-nav"> | |||
<div class="ui secondary vertical filter menu gt-bg-transparent"> | |||
<div class="item"> | |||
{{ctx.Locale.Tr "home.issues.in_your_repos"}} | |||
@@ -33,7 +33,7 @@ | |||
{{end}} | |||
</div> | |||
</div> | |||
<div class="twelve wide column content"> | |||
<div class="flex-container-main content"> | |||
<div class="list-header"> | |||
<div class="small-menu-items ui compact tiny menu list-header-toggle"> | |||
<a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=open&q={{$.Keyword}}"> |
@@ -0,0 +1,55 @@ | |||
// Copyright 2023 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package integration | |||
import ( | |||
"net/http" | |||
"testing" | |||
"time" | |||
auth_model "code.gitea.io/gitea/models/auth" | |||
"code.gitea.io/gitea/models/db" | |||
"code.gitea.io/gitea/models/unittest" | |||
user_model "code.gitea.io/gitea/models/user" | |||
"code.gitea.io/gitea/tests" | |||
"github.com/pquerna/otp/totp" | |||
"github.com/stretchr/testify/assert" | |||
) | |||
func TestAPITwoFactor(t *testing.T) { | |||
defer tests.PrepareTestEnv(t)() | |||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16}) | |||
req := NewRequestf(t, "GET", "/api/v1/user") | |||
req = AddBasicAuthHeader(req, user.Name) | |||
MakeRequest(t, req, http.StatusOK) | |||
otpKey, err := totp.Generate(totp.GenerateOpts{ | |||
SecretSize: 40, | |||
Issuer: "gitea-test", | |||
AccountName: user.Name, | |||
}) | |||
assert.NoError(t, err) | |||
tfa := &auth_model.TwoFactor{ | |||
UID: user.ID, | |||
} | |||
assert.NoError(t, tfa.SetSecret(otpKey.Secret())) | |||
assert.NoError(t, auth_model.NewTwoFactor(db.DefaultContext, tfa)) | |||
req = NewRequestf(t, "GET", "/api/v1/user") | |||
req = AddBasicAuthHeader(req, user.Name) | |||
MakeRequest(t, req, http.StatusUnauthorized) | |||
passcode, err := totp.GenerateCode(otpKey.Secret(), time.Now()) | |||
assert.NoError(t, err) | |||
req = NewRequestf(t, "GET", "/api/v1/user") | |||
req = AddBasicAuthHeader(req, user.Name) | |||
req.Header.Set("X-Gitea-OTP", passcode) | |||
MakeRequest(t, req, http.StatusOK) | |||
} |
@@ -1,5 +1,5 @@ | |||
.divider { | |||
margin: 1rem 0; | |||
margin: 10px 0; | |||
height: 0; | |||
font-weight: var(--font-weight-medium); | |||
text-transform: uppercase; | |||
@@ -15,7 +15,7 @@ | |||
.divider.divider-text { | |||
display: flex; | |||
align-items: center; | |||
padding: 7px 0; | |||
padding: 5px 0; | |||
} | |||
.divider.divider-text::before, |
@@ -6,7 +6,7 @@ | |||
} | |||
[data-tippy-root] { | |||
max-width: calc(100vw - 10px); | |||
max-width: calc(100vw - 32px); | |||
} | |||
.tippy-box { | |||
@@ -18,37 +18,60 @@ | |||
font-size: 1rem; | |||
} | |||
.tippy-content { | |||
position: relative; | |||
padding: 1rem; /* if you need different padding, use different data-theme */ | |||
z-index: 1; | |||
} | |||
/* tooltip theme for text tooltips */ | |||
.tippy-box[data-theme="tooltip"] { | |||
background-color: var(--color-tooltip-bg); | |||
color: var(--color-tooltip-text); | |||
border: none; | |||
} | |||
.tippy-box[data-theme="tooltip"] .tippy-content { | |||
padding: 0.5rem 1rem; | |||
} | |||
.tippy-box[data-theme="tooltip"] .tippy-svg-arrow-inner, | |||
.tippy-box[data-theme="tooltip"] .tippy-svg-arrow-outer { | |||
fill: var(--color-tooltip-bg); | |||
} | |||
/* menu theme for .ui.menu */ | |||
.tippy-box[data-theme="menu"] { | |||
background-color: var(--color-menu); | |||
color: var(--color-text); | |||
} | |||
.tippy-box[data-theme="form-fetch-error"] { | |||
border-color: var(--color-error-border); | |||
background-color: var(--color-error-bg); | |||
color: var(--color-error-text); | |||
.tippy-box[data-theme="menu"] .tippy-content { | |||
padding: 0; | |||
} | |||
.tippy-content { | |||
position: relative; | |||
padding: 1rem; | |||
z-index: 1; | |||
.tippy-box[data-theme="menu"] .tippy-svg-arrow-inner { | |||
fill: var(--color-menu); | |||
} | |||
.tippy-box[data-theme="tooltip"] .tippy-content { | |||
padding: 0.5rem 1rem; | |||
} | |||
/* box-with-header theme to look like .ui.attached.segment. can contain .ui.attached.header */ | |||
.tippy-box[data-theme="menu"] .tippy-content { | |||
.tippy-box[data-theme="box-with-header"] .tippy-content { | |||
background: var(--color-box-body); | |||
border-radius: var(--border-radius); | |||
padding: 0; | |||
} | |||
.tippy-box[data-theme="box-with-header"][data-placement^="top"] .tippy-svg-arrow-inner { | |||
fill: var(--color-box-body); | |||
} | |||
.tippy-box[data-theme="box-with-header"][data-placement^="bottom"] .tippy-svg-arrow-inner { | |||
fill: var(--color-box-header); | |||
} | |||
.tippy-box[data-placement^="top"] > .tippy-svg-arrow { | |||
bottom: 0; | |||
} | |||
@@ -107,12 +130,3 @@ | |||
.tippy-svg-arrow-inner { | |||
fill: var(--color-body); | |||
} | |||
.tippy-box[data-theme="tooltip"] .tippy-svg-arrow-inner, | |||
.tippy-box[data-theme="tooltip"] .tippy-svg-arrow-outer { | |||
fill: var(--color-tooltip-bg); | |||
} | |||
.tippy-box[data-theme="menu"] .tippy-svg-arrow-inner { | |||
fill: var(--color-menu); | |||
} |
@@ -211,14 +211,14 @@ | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
gap: 5px; | |||
margin-bottom: 5px; | |||
} | |||
@media (max-width: 767.68px) { | |||
@media (max-width: 767.98px) { | |||
.repository.file.list .repo-description { | |||
flex-direction: column; | |||
gap: 8px; | |||
align-items: normal; | |||
align-items: stretch; | |||
} | |||
} | |||
@@ -3074,43 +3074,57 @@ tbody.commit-list { | |||
} | |||
} | |||
.pr-status { | |||
padding: 0 !important; /* To clear fomantic's padding on .ui.segment elements */ | |||
.commit-status-header { | |||
/* reset the default ".ui.attached.header" styles, to use the outer border */ | |||
border: none !important; | |||
/* add a bottom border to make sure the there is always a divider between the header and list when the list is scrolling */ | |||
border-bottom: 1px solid var(--color-secondary) !important; | |||
/* use negative margin to avoid the newly added border conflict with the list's top border */ | |||
margin: 0 0 -1px !important; | |||
} | |||
.commit-status-list { | |||
max-height: 240px; /* fit exactly 6 items, commit-status-item.height * 6 */ | |||
overflow-x: hidden; | |||
transition: max-height .2s; | |||
} | |||
.commit-status-item { | |||
height: 40px; | |||
padding: 0 10px; | |||
display: flex; | |||
gap: 8px; | |||
align-items: center; | |||
} | |||
.pr-status .commit-status { | |||
margin: 1em; | |||
flex-shrink: 0; | |||
.commit-status-item + .commit-status-item { | |||
border-top: 1px solid var(--color-secondary); | |||
} | |||
.pr-status .status-context { | |||
display: flex; | |||
justify-content: space-between; | |||
width: 100%; | |||
.commit-status-item .commit-status { | |||
flex-shrink: 0; | |||
} | |||
.pr-status .status-context > span { | |||
padding: 1em 0; | |||
.commit-status-item .status-context { | |||
color: var(--color-text); | |||
flex: 1; | |||
} | |||
.pr-status .status-details { | |||
.commit-status-item .status-details { | |||
display: flex; | |||
padding-right: 0.5em; | |||
align-items: center; | |||
justify-content: flex-end; | |||
} | |||
@media (max-width: 767.98px) { | |||
.pr-status .status-details { | |||
.commit-status-item .status-details { | |||
flex-direction: column; | |||
align-items: flex-end; | |||
justify-content: center; | |||
} | |||
} | |||
.pr-status .status-details > span { | |||
.commit-status-item .status-details > span { | |||
padding-right: 0.5em; /* To match the alignment with the "required" label */ | |||
} | |||
@@ -6,7 +6,7 @@ | |||
display: flex; | |||
gap: 8px; | |||
align-items: flex-start; | |||
padding: 1em 0; | |||
padding: 10px 0; | |||
} | |||
.flex-item .flex-item-leading { |
@@ -210,7 +210,6 @@ const sfc = { | |||
this.searchRepos(); | |||
}, | |||
changePage(page) { | |||
this.page = page; | |||
if (this.page > this.finalPage) { |
@@ -94,7 +94,7 @@ export default { | |||
focusElem(elem, prevElem) { | |||
if (elem) { | |||
elem.tabIndex = 0; | |||
prevElem.tabIndex = -1; | |||
if (prevElem) prevElem.tabIndex = -1; | |||
elem.focus(); | |||
} | |||
}, |
@@ -46,7 +46,6 @@ export function initFootLanguageMenu() { | |||
$('.language-menu a[lang]').on('click', linkLanguageAction); | |||
} | |||
export function initGlobalEnterQuickSubmit() { | |||
$(document).on('keydown', '.js-quick-submit', (e) => { | |||
if (((e.ctrlKey && !e.altKey) || e.metaKey) && (e.key === 'Enter')) { | |||
@@ -248,6 +247,10 @@ export function initGlobalDropzone() { | |||
}); | |||
} | |||
}); | |||
this.on('error', function (file, message) { | |||
showErrorToast(message); | |||
this.removeFile(file); | |||
}); | |||
}, | |||
}); | |||
} |
@@ -90,7 +90,6 @@ class CodeMirrorEditor { | |||
} | |||
} | |||
const uploadClipboardImage = async (editor, dropzone, e) => { | |||
const $dropzone = $(dropzone); | |||
const uploadUrl = $dropzone.attr('data-upload-url'); |
@@ -17,4 +17,3 @@ export function setFileFolding(fileContentBox, foldArrow, newFold) { | |||
export function invertFileFolding(fileContentBox, foldArrow) { | |||
setFileFolding(fileContentBox, foldArrow, fileContentBox.getAttribute('data-folded') !== 'true'); | |||
} | |||
@@ -15,7 +15,6 @@ export function initOrgTeamSettings() { | |||
}); | |||
} | |||
export function initOrgTeamSearchRepoBox() { | |||
const $searchRepoBox = $('#search-repo-box'); | |||
$searchRepoBox.search({ |
@@ -9,7 +9,6 @@ const viewedCheckboxSelector = '.viewed-file-form'; // Selector under which all | |||
const expandFilesBtnSelector = '#expand-files-btn'; | |||
const collapseFilesBtnSelector = '#collapse-files-btn'; | |||
// Refreshes the summary of viewed files if present | |||
// The data used will be window.config.pageData.prReview.numberOf{Viewed}Files | |||
function refreshViewedFilesSummary() { | |||
@@ -93,5 +92,3 @@ export function initExpandAndCollapseFilesButton() { | |||
} | |||
}); | |||
} | |||
@@ -66,6 +66,7 @@ export function initCommitStatuses() { | |||
placement: top ? 'top-start' : 'bottom-start', | |||
interactive: true, | |||
role: 'dialog', | |||
theme: 'box-with-header', | |||
}); | |||
}); | |||
} |
@@ -60,7 +60,6 @@ function initEditorForm() { | |||
initEditDiffTab($('.repository .edit.form')); | |||
} | |||
function getCursorPosition($e) { | |||
const el = $e.get(0); | |||
let pos = 0; |
@@ -9,7 +9,6 @@ const threshold = 50; | |||
let files = []; | |||
let $repoFindFileInput, $repoFindFileTableBody, $repoFindFileNoResult; | |||
// return the case-insensitive sub-match result as an array: [unmatched, matched, unmatched, matched, ...] | |||
// res[even] is unmatched, res[odd] is matched, see unit tests for examples | |||
// argument subLower must be a lower-cased string. |
@@ -43,7 +43,7 @@ export function initRepoTopicBar() { | |||
const topicArray = topics.split(','); | |||
topicArray.sort(); | |||
for (let i = 0; i < topicArray.length; i++) { | |||
const link = $('<a class="ui repo-topic large label topic"></a>'); | |||
const link = $('<a class="ui repo-topic large label topic gt-m-0"></a>'); | |||
link.attr('href', `${appSubUrl}/explore/repos?q=${encodeURIComponent(topicArray[i])}&topic=1`); | |||
link.text(topicArray[i]); | |||
link.insertBefore(mgrBtn); // insert all new topics before manage button |
@@ -0,0 +1,10 @@ | |||
export function initRepoPullRequestCommitStatus() { | |||
for (const btn of document.querySelectorAll('.commit-status-hide-checks')) { | |||
const panel = btn.closest('.commit-status-panel'); | |||
const list = panel.querySelector('.commit-status-list'); | |||
btn.addEventListener('click', () => { | |||
list.style.maxHeight = list.style.maxHeight ? '' : '0px'; // toggle | |||
btn.textContent = btn.getAttribute(list.style.maxHeight ? 'data-show-all' : 'data-hide-all'); | |||
}); | |||
} | |||
} |
@@ -308,7 +308,6 @@ export function initRepoIssueReferenceRepositorySearch() { | |||
}); | |||
} | |||
export function initRepoIssueWipTitle() { | |||
$('.title_wip_desc > a').on('click', (e) => { | |||
e.preventDefault(); | |||
@@ -552,7 +551,6 @@ export function initRepoIssueWipToggle() { | |||
}); | |||
} | |||
export function initRepoIssueTitleEdit() { | |||
// Edit issue title | |||
const $issueTitle = $('#issue-title'); |
@@ -20,6 +20,7 @@ import {initCommentContent, initMarkupContent} from '../markup/content.js'; | |||
import {initCompReactionSelector} from './comp/ReactionSelector.js'; | |||
import {initRepoSettingBranches} from './repo-settings.js'; | |||
import {initRepoPullRequestMergeForm} from './repo-issue-pr-form.js'; | |||
import {initRepoPullRequestCommitStatus} from './repo-issue-pr-status.js'; | |||
import {hideElem, showElem} from '../utils/dom.js'; | |||
import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js'; | |||
import {attachRefIssueContextPopup} from './contextpopup.js'; | |||
@@ -300,7 +301,6 @@ export function initRepoCommentForm() { | |||
selectItem('.select-assignee', '#assignee_id'); | |||
} | |||
async function onEditContent(event) { | |||
event.preventDefault(); | |||
@@ -538,7 +538,6 @@ export function initRepository() { | |||
initRepoDiffConversationNav(); | |||
initRepoIssueReferenceIssue(); | |||
initRepoIssueCommentDelete(); | |||
initRepoIssueDependencyDelete(); | |||
initRepoIssueCodeCommentCancel(); | |||
@@ -546,6 +545,7 @@ export function initRepository() { | |||
initCompReactionSelector($(document)); | |||
initRepoPullRequestMergeForm(); | |||
initRepoPullRequestCommitStatus(); | |||
} | |||
// Pull request |
@@ -66,7 +66,6 @@ export function initRepoSettingSearchTeamBox() { | |||
}); | |||
} | |||
export function initRepoSettingGitHook() { | |||
if ($('.edit.githook').length === 0) return; | |||
const filename = document.querySelector('.hook-filename').textContent; |
@@ -1,7 +1,9 @@ | |||
import $ from 'jquery'; | |||
import {initAriaCheckboxPatch} from './aria/checkbox.js'; | |||
import {initAriaDropdownPatch} from './aria/dropdown.js'; | |||
import {initAriaModalPatch} from './aria/modal.js'; | |||
import {initFomanticApiPatch} from './fomantic/api.js'; | |||
import {initAriaCheckboxPatch} from './fomantic/checkbox.js'; | |||
import {initAriaDropdownPatch} from './fomantic/dropdown.js'; | |||
import {initAriaModalPatch} from './fomantic/modal.js'; | |||
import {initFomanticTransition} from './fomantic/transition.js'; | |||
import {svg} from '../svg.js'; | |||
export const fomanticMobileScreen = window.matchMedia('only screen and (max-width: 767.98px)'); | |||
@@ -22,57 +24,7 @@ export function initGiteaFomantic() { | |||
return escape(text, preserveHTML) + svg('octicon-x', 16, `${className.delete} icon`); | |||
}; | |||
const transitionNopBehaviors = new Set([ | |||
'clear queue', 'stop', 'stop all', 'destroy', | |||
'force repaint', 'repaint', 'reset', | |||
'looping', 'remove looping', 'disable', 'enable', | |||
'set duration', 'save conditions', 'restore conditions', | |||
]); | |||
// stand-in for removed transition module | |||
$.fn.transition = function (arg0, arg1, arg2) { | |||
if (arg0 === 'is supported') return true; | |||
if (arg0 === 'is animating') return false; | |||
if (arg0 === 'is inward') return false; | |||
if (arg0 === 'is outward') return false; | |||
let argObj; | |||
if (typeof arg0 === 'string') { | |||
// many behaviors are no-op now. https://fomantic-ui.com/modules/transition.html#/usage | |||
if (transitionNopBehaviors.has(arg0)) return this; | |||
// now, the arg0 is an animation name, the syntax: (animation, duration, complete) | |||
argObj = {animation: arg0, ...(arg1 && {duration: arg1}), ...(arg2 && {onComplete: arg2})}; | |||
} else if (typeof arg0 === 'object') { | |||
argObj = arg0; | |||
} else { | |||
throw new Error(`invalid argument: ${arg0}`); | |||
} | |||
const isAnimationIn = argObj.animation?.startsWith('show') || argObj.animation?.endsWith(' in'); | |||
const isAnimationOut = argObj.animation?.startsWith('hide') || argObj.animation?.endsWith(' out'); | |||
this.each((_, el) => { | |||
let toShow = isAnimationIn; | |||
if (!isAnimationIn && !isAnimationOut) { | |||
// If the animation is not in/out, then it must be a toggle animation. | |||
// Fomantic uses computed styles to check "visibility", but to avoid unnecessary arguments, here it only checks the class. | |||
toShow = this.hasClass('hidden'); // maybe it could also check "!this.hasClass('visible')", leave it to the future until there is a real problem. | |||
} | |||
argObj.onStart?.call(el); | |||
if (toShow) { | |||
el.classList.remove('hidden'); | |||
el.classList.add('visible', 'transition'); | |||
if (argObj.displayType) el.style.setProperty('display', argObj.displayType, 'important'); | |||
argObj.onShow?.call(el); | |||
} else { | |||
el.classList.add('hidden'); | |||
el.classList.remove('visible'); // don't remove the transition class because the Fomantic animation style is `.hidden.transition`. | |||
el.style.removeProperty('display'); | |||
argObj.onHidden?.call(el); | |||
} | |||
argObj.onComplete?.call(el); | |||
}); | |||
return this; | |||
}; | |||
initFomanticTransition(); | |||
initFomanticApiPatch(); | |||
// Use the patches to improve accessibility, these patches are designed to be as independent as possible, make it easy to modify or remove in the future. | |||
@@ -80,42 +32,3 @@ export function initGiteaFomantic() { | |||
initAriaDropdownPatch(); | |||
initAriaModalPatch(); | |||
} | |||
function initFomanticApiPatch() { | |||
// | |||
// Fomantic API module has some very buggy behaviors: | |||
// | |||
// If encodeParameters=true, it calls `urlEncodedValue` to encode the parameter. | |||
// However, `urlEncodedValue` just tries to "guess" whether the parameter is already encoded, by decoding the parameter and encoding it again. | |||
// | |||
// There are 2 problems: | |||
// 1. It may guess wrong, and skip encoding a parameter which looks like encoded. | |||
// 2. If the parameter can't be decoded, `decodeURIComponent` will throw an error, and the whole request will fail. | |||
// | |||
// This patch only fixes the second error behavior at the moment. | |||
// | |||
const patchKey = '_giteaFomanticApiPatch'; | |||
const oldApi = $.api; | |||
$.api = $.fn.api = function(...args) { | |||
const apiCall = oldApi.bind(this); | |||
const ret = oldApi.apply(this, args); | |||
if (typeof args[0] !== 'string') { | |||
const internalGet = apiCall('internal', 'get'); | |||
if (!internalGet.urlEncodedValue[patchKey]) { | |||
const oldUrlEncodedValue = internalGet.urlEncodedValue; | |||
internalGet.urlEncodedValue = function (value) { | |||
try { | |||
return oldUrlEncodedValue(value); | |||
} catch { | |||
// if Fomantic API module's `urlEncodedValue` throws an error, we encode it by ourselves. | |||
return encodeURIComponent(value); | |||
} | |||
}; | |||
internalGet.urlEncodedValue[patchKey] = true; | |||
} | |||
} | |||
return ret; | |||
}; | |||
$.api.settings = oldApi.settings; | |||
} |