mirror of https://github.com/go-gitea/gitea.git
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
35bb7e7ed5
5
go.mod
5
go.mod
|
@ -2,6 +2,11 @@ module code.gitea.io/gitea
|
|||
|
||||
go 1.23
|
||||
|
||||
// rfc5280 said: "The serial number is an integer assigned by the CA to each certificate."
|
||||
// But some CAs use negative serial number, just relax the check. related:
|
||||
// Default TLS cert uses negative serial number #895 https://github.com/microsoft/mssql-docker/issues/895
|
||||
godebug x509negativeserial=1
|
||||
|
||||
require (
|
||||
code.gitea.io/actions-proto-go v0.4.0
|
||||
code.gitea.io/gitea-vet v0.2.3
|
||||
|
|
|
@ -247,7 +247,7 @@ func (r *Review) TooltipContent() string {
|
|||
}
|
||||
return "repo.issues.review.official"
|
||||
case ReviewTypeComment:
|
||||
return "repo.issues.review.comment"
|
||||
return "repo.issues.review.commented"
|
||||
case ReviewTypeReject:
|
||||
return "repo.issues.review.rejected"
|
||||
case ReviewTypeRequest:
|
||||
|
|
|
@ -234,6 +234,7 @@ type FindReleasesOptions struct {
|
|||
IsDraft optional.Option[bool]
|
||||
TagNames []string
|
||||
HasSha1 optional.Option[bool] // useful to find draft releases which are created with existing tags
|
||||
NamePattern optional.Option[string]
|
||||
}
|
||||
|
||||
func (opts FindReleasesOptions) ToConds() builder.Cond {
|
||||
|
@ -261,6 +262,11 @@ func (opts FindReleasesOptions) ToConds() builder.Cond {
|
|||
cond = cond.And(builder.Eq{"sha1": ""})
|
||||
}
|
||||
}
|
||||
|
||||
if opts.NamePattern.Has() && opts.NamePattern.Value() != "" {
|
||||
cond = cond.And(builder.Like{"lower_tag_name", strings.ToLower(opts.NamePattern.Value())})
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ func AvatarHTML(src string, size int, class, name string) template.HTML {
|
|||
name = "avatar"
|
||||
}
|
||||
|
||||
return template.HTML(`<img class="` + class + `" src="` + src + `" title="` + html.EscapeString(name) + `" width="` + sizeStr + `" height="` + sizeStr + `"/>`)
|
||||
return template.HTML(`<img loading="lazy" class="` + class + `" src="` + src + `" title="` + html.EscapeString(name) + `" width="` + sizeStr + `" height="` + sizeStr + `"/>`)
|
||||
}
|
||||
|
||||
// Avatar renders user avatars. args: user, size (int), class (string)
|
||||
|
|
|
@ -218,8 +218,6 @@ string.desc=Z – A
|
|||
|
||||
[error]
|
||||
occurred=Došlo k chybě
|
||||
missing_csrf=Špatný požadavek: Neexistuje CSRF token
|
||||
invalid_csrf=Špatný požadavek: Neplatný CSRF token
|
||||
not_found=Cíl nebyl nalezen.
|
||||
network_error=Chyba sítě
|
||||
|
||||
|
@ -1701,7 +1699,6 @@ issues.dependency.add_error_dep_not_same_repo=Oba úkoly musí být ve stejném
|
|||
issues.review.self.approval=Nemůžete schválit svůj pull request.
|
||||
issues.review.self.rejection=Nemůžete požadovat změny ve svém vlastním pull requestu.
|
||||
issues.review.approve=schválil tyto změny %s
|
||||
issues.review.comment=Okomentovat
|
||||
issues.review.dismissed=zamítl/a posouzení od %s %s
|
||||
issues.review.dismissed_label=Zamítnuto
|
||||
issues.review.left_comment=zanechal komentář
|
||||
|
@ -1726,6 +1723,7 @@ issues.review.hide_resolved=Skrýt vyřešené
|
|||
issues.review.resolve_conversation=Vyřešit konverzaci
|
||||
issues.review.un_resolve_conversation=Nevyřešit konverzaci
|
||||
issues.review.resolved_by=označil tuto konverzaci jako vyřešenou
|
||||
issues.review.commented=Okomentovat
|
||||
issues.assignee.error=Ne všichni zpracovatelé byli přidáni z důvodu neočekávané chyby.
|
||||
issues.reference_issue.body=Tělo zprávy
|
||||
issues.content_history.deleted=vymazáno
|
||||
|
|
|
@ -213,8 +213,6 @@ string.desc=Z–A
|
|||
|
||||
[error]
|
||||
occurred=Ein Fehler ist aufgetreten
|
||||
missing_csrf=Fehlerhafte Anfrage: Kein CSRF Token verfügbar
|
||||
invalid_csrf=Fehlerhafte Anfrage: Ungültiger CSRF Token
|
||||
not_found=Das Ziel konnte nicht gefunden werden.
|
||||
network_error=Netzwerkfehler
|
||||
|
||||
|
@ -1681,7 +1679,6 @@ issues.dependency.add_error_dep_not_same_repo=Beide Issues müssen sich im selbe
|
|||
issues.review.self.approval=Du kannst nicht dein eigenen Pull-Request genehmigen.
|
||||
issues.review.self.rejection=Du kannst keine Änderungen an deinem eigenen Pull-Request anfragen.
|
||||
issues.review.approve=hat die Änderungen %s genehmigt
|
||||
issues.review.comment=Kommentieren
|
||||
issues.review.dismissed=verwarf %ss Review %s
|
||||
issues.review.dismissed_label=Verworfen
|
||||
issues.review.left_comment=hat einen Kommentar hinterlassen
|
||||
|
@ -1706,6 +1703,7 @@ issues.review.hide_resolved=Gelöste ausblenden
|
|||
issues.review.resolve_conversation=Diskussion als "erledigt" markieren
|
||||
issues.review.un_resolve_conversation=Diskussion als "nicht-erledigt" markieren
|
||||
issues.review.resolved_by=markierte diese Unterhaltung als gelöst
|
||||
issues.review.commented=Kommentieren
|
||||
issues.assignee.error=Aufgrund eines unerwarteten Fehlers konnten nicht alle Beauftragten hinzugefügt werden.
|
||||
issues.reference_issue.body=Beschreibung
|
||||
issues.content_history.deleted=gelöscht
|
||||
|
|
|
@ -184,8 +184,6 @@ string.desc=Z - A
|
|||
|
||||
[error]
|
||||
occurred=Παρουσιάστηκε ένα σφάλμα
|
||||
missing_csrf=Bad Request: δεν υπάρχει διακριτικό CSRF
|
||||
invalid_csrf=Λάθος Αίτημα: μη έγκυρο διακριτικό CSRF
|
||||
not_found=Ο προορισμός δεν βρέθηκε.
|
||||
network_error=Σφάλμα δικτύου
|
||||
|
||||
|
@ -1603,7 +1601,6 @@ issues.dependency.add_error_dep_not_same_repo=Και τα δύο ζητήματ
|
|||
issues.review.self.approval=Δεν μπορείτε να εγκρίνετε το δικό σας pull request.
|
||||
issues.review.self.rejection=Δεν μπορείτε να ζητήσετε αλλαγές στο δικό σας pull request.
|
||||
issues.review.approve=ενέκρινε αυτές τις αλλαγές %s
|
||||
issues.review.comment=Σχόλιο
|
||||
issues.review.dismissed=απέρριψε την αξιολόγηση %s %s
|
||||
issues.review.dismissed_label=Απορρίφθηκε
|
||||
issues.review.left_comment=άφησε ένα σχόλιο
|
||||
|
@ -1628,6 +1625,7 @@ issues.review.hide_resolved=Απόκρυψη επιλυμένων
|
|||
issues.review.resolve_conversation=Επίλυση συνομιλίας
|
||||
issues.review.un_resolve_conversation=Ανεπίλυτη συνομιλία
|
||||
issues.review.resolved_by=σημείωση αυτή την συνομιλία ως επιλυμένη
|
||||
issues.review.commented=Σχόλιο
|
||||
issues.assignee.error=Δεν προστέθηκαν όλοι οι παραλήπτες λόγω απροσδόκητου σφάλματος.
|
||||
issues.reference_issue.body=Σώμα
|
||||
issues.content_history.deleted=διαγράφηκε
|
||||
|
|
|
@ -178,6 +178,8 @@ code_search_by_git_grep = Current code search results are provided by "git grep"
|
|||
package_kind = Search packages...
|
||||
project_kind = Search projects...
|
||||
branch_kind = Search branches...
|
||||
tag_kind = Search tags...
|
||||
tag_tooltip = Search for matching tags. Use '%' to match any sequence of numbers.
|
||||
commit_kind = Search commits...
|
||||
runner_kind = Search runners...
|
||||
no_results = No matching results found.
|
||||
|
@ -220,8 +222,6 @@ string.desc = Z - A
|
|||
[error]
|
||||
occurred = An error occurred
|
||||
report_message = If you believe that this is a Gitea bug, please search for issues on <a href="%s" target="_blank">GitHub</a> or open a new issue if necessary.
|
||||
missing_csrf = Bad Request: no CSRF token present
|
||||
invalid_csrf = Bad Request: invalid CSRF token
|
||||
not_found = The target couldn't be found.
|
||||
network_error = Network error
|
||||
|
||||
|
@ -1757,7 +1757,7 @@ issues.review.hide_resolved = Hide resolved
|
|||
issues.review.resolve_conversation = Resolve conversation
|
||||
issues.review.un_resolve_conversation = Unresolve conversation
|
||||
issues.review.resolved_by = marked this conversation as resolved
|
||||
issues.review.comment = Comment
|
||||
issues.review.commented = Comment
|
||||
issues.review.official = Approved
|
||||
issues.review.requested = Review pending
|
||||
issues.review.rejected = Changes requested
|
||||
|
|
|
@ -182,8 +182,6 @@ string.desc=Z - A
|
|||
|
||||
[error]
|
||||
occurred=Ha ocurrido un error
|
||||
missing_csrf=Solicitud incorrecta: sin token CSRF
|
||||
invalid_csrf=Solicitud incorrecta: el token CSRF no es válido
|
||||
not_found=El objetivo no pudo ser encontrado.
|
||||
network_error=Error de red
|
||||
|
||||
|
@ -1593,7 +1591,6 @@ issues.dependency.add_error_dep_not_same_repo=Ambas incidencias deben estar en e
|
|||
issues.review.self.approval=No puede aprobar su propio pull request.
|
||||
issues.review.self.rejection=No puede sugerir cambios en su propio pull request.
|
||||
issues.review.approve=aprobado estos cambios %s
|
||||
issues.review.comment=Comentario
|
||||
issues.review.dismissed=descartó la revisión de %s %s
|
||||
issues.review.dismissed_label=Descartado
|
||||
issues.review.left_comment=dejó un comentario
|
||||
|
@ -1618,6 +1615,7 @@ issues.review.hide_resolved=Ocultar resueltos
|
|||
issues.review.resolve_conversation=Resolver conversación
|
||||
issues.review.un_resolve_conversation=Marcar conversación sin resolver
|
||||
issues.review.resolved_by=ha marcado esta conversación como resuelta
|
||||
issues.review.commented=Comentario
|
||||
issues.assignee.error=No todos los asignados fueron añadidos debido a un error inesperado.
|
||||
issues.reference_issue.body=Cuerpo
|
||||
issues.content_history.deleted=borrado
|
||||
|
|
|
@ -118,7 +118,6 @@ filter.private=خصوصی
|
|||
[filter]
|
||||
|
||||
[error]
|
||||
missing_csrf=درخواست بد: بلیط CSRF ندارد
|
||||
|
||||
[startpage]
|
||||
app_desc=یک سرویس گیت بیدرد سر و راحت
|
||||
|
@ -1235,7 +1234,6 @@ issues.dependency.add_error_dep_not_same_repo=هر دو موضوع باید از
|
|||
issues.review.self.approval=شما نمیتوانید تقاضای واکشی خود را تایید کنید.
|
||||
issues.review.self.rejection=شما نمیتوانید تقاضا تغییرات تقاضای واکشی خود را تغییر دهید.
|
||||
issues.review.approve=این تغییرات را تایید شدند %s
|
||||
issues.review.comment=دیدگاه
|
||||
issues.review.dismissed=بررسی %s %s را رد شده
|
||||
issues.review.dismissed_label=رها شده
|
||||
issues.review.left_comment=یک نظر ثبت کرد
|
||||
|
@ -1256,6 +1254,7 @@ issues.review.hide_resolved=مخفی کردن حل شده ها
|
|||
issues.review.resolve_conversation=مکالمه را بعنوان حل شده علامت گذاری کردن
|
||||
issues.review.un_resolve_conversation=مکالمه را بعنوان حل نشده علامت گذاری کردن
|
||||
issues.review.resolved_by=علامت گذاری این مکالمه بعنوان حل شده
|
||||
issues.review.commented=دیدگاه
|
||||
issues.assignee.error=به دلیل خطای غیرمنتظره همه تکالیف اضافه نشد.
|
||||
issues.reference_issue.body=Body
|
||||
issues.content_history.deleted=حذف شده
|
||||
|
|
|
@ -133,8 +133,6 @@ filter.private=Yksityinen
|
|||
|
||||
[error]
|
||||
occurred=Virhe tapahtui
|
||||
missing_csrf=Virheellinen pyyntö: CSRF-tunnusta ei ole olemassa
|
||||
invalid_csrf=Virheellinen pyyntö: Virheellinen CSRF-tunniste
|
||||
not_found=Kohdetta ei löytynyt.
|
||||
network_error=Verkkovirhe
|
||||
|
||||
|
@ -955,6 +953,7 @@ issues.review.left_comment=jätti kommentin
|
|||
issues.review.pending=Odottaa
|
||||
issues.review.show_resolved=Näytä ratkaisu
|
||||
issues.review.hide_resolved=Piilota ratkaisu
|
||||
issues.review.commented=Kommentoi
|
||||
issues.reference_issue.body=Kuvaus
|
||||
issues.content_history.deleted=poistettu
|
||||
issues.content_history.edited=muokattu
|
||||
|
|
|
@ -217,8 +217,6 @@ string.desc=Z - A
|
|||
|
||||
[error]
|
||||
occurred=Une erreur s’est produite
|
||||
missing_csrf=Requête incorrecte: aucun jeton CSRF présent
|
||||
invalid_csrf=Requête incorrecte : jeton CSRF invalide
|
||||
not_found=La cible n'a pu être trouvée.
|
||||
network_error=Erreur réseau
|
||||
|
||||
|
@ -1704,7 +1702,6 @@ issues.dependency.add_error_dep_not_same_repo=Les deux tickets doivent être dan
|
|||
issues.review.self.approval=Vous ne pouvez approuver vos propres demandes d'ajout.
|
||||
issues.review.self.rejection=Vous ne pouvez demander de changements sur vos propres demandes de changement.
|
||||
issues.review.approve=a approuvé ces modifications %s.
|
||||
issues.review.comment=Commenter
|
||||
issues.review.dismissed=a révoqué l’évaluation de %s %s.
|
||||
issues.review.dismissed_label=Révoquée
|
||||
issues.review.left_comment=laisser un commentaire
|
||||
|
@ -1729,6 +1726,7 @@ issues.review.hide_resolved=Réduire
|
|||
issues.review.resolve_conversation=Clore la conversation
|
||||
issues.review.un_resolve_conversation=Rouvrir la conversation
|
||||
issues.review.resolved_by=a marqué cette conversation comme résolue.
|
||||
issues.review.commented=Commenter
|
||||
issues.assignee.error=Tous les assignés n'ont pas été ajoutés en raison d'une erreur inattendue.
|
||||
issues.reference_issue.body=Corps
|
||||
issues.content_history.deleted=a supprimé
|
||||
|
|
|
@ -901,13 +901,13 @@ issues.dependency.add_error_dep_issue_not_exist=Függő hibajegy nem létezik.
|
|||
issues.dependency.add_error_dep_not_exist=A függőség nem létezik.
|
||||
issues.dependency.add_error_dep_exists=A függőség már létezik.
|
||||
issues.dependency.add_error_dep_not_same_repo=Mindkét hibajegynek ugyanabban a tárolóban kell lennie.
|
||||
issues.review.comment=Hozzászólás
|
||||
issues.review.reject=%s változtatások kérése
|
||||
issues.review.pending=Függőben
|
||||
issues.review.review=Értékelés
|
||||
issues.review.reviewers=Véleményezők
|
||||
issues.review.show_outdated=Elavultak mutatása
|
||||
issues.review.hide_outdated=Elavultak elrejtése
|
||||
issues.review.commented=Hozzászólás
|
||||
issues.assignee.error=Nem minden megbízott lett hozzáadva egy nem várt hiba miatt.
|
||||
|
||||
|
||||
|
|
|
@ -129,8 +129,6 @@ filter.public=Opinbert
|
|||
|
||||
[error]
|
||||
occurred=Villa kom upp
|
||||
missing_csrf=Slæm beiðni: enginn CSRF lykill
|
||||
invalid_csrf=Slæm beiðni: ógildur CSRF lykill
|
||||
not_found=Markmiðið fannst ekki.
|
||||
network_error=Netkerfisvilla
|
||||
|
||||
|
@ -850,13 +848,13 @@ issues.dependency.remove_header=Fjarlægja Kröfu
|
|||
issues.dependency.add_error_dep_not_exist=Krafa er ekki til.
|
||||
issues.dependency.add_error_dep_exists=Krafa er nú þegar til.
|
||||
issues.review.approve=samþykkti þessar breytingar %s
|
||||
issues.review.comment=Senda Ummæli
|
||||
issues.review.dismissed_label=Hunsað
|
||||
issues.review.left_comment=gerði ummæli
|
||||
issues.review.pending=Í bið
|
||||
issues.review.outdated=Úrelt
|
||||
issues.review.show_outdated=Sýna úrelt
|
||||
issues.review.hide_outdated=Fela úreld
|
||||
issues.review.commented=Senda Ummæli
|
||||
issues.reference_issue.body=Meginmál
|
||||
issues.content_history.deleted=eytt
|
||||
issues.content_history.edited=breytt
|
||||
|
|
|
@ -135,8 +135,6 @@ filter.private=Privati
|
|||
|
||||
[error]
|
||||
occurred=Si è verificato un errore
|
||||
missing_csrf=Richiesta errata: nessun token CSRF presente
|
||||
invalid_csrf=Richiesta errata: token CSRF non valido
|
||||
not_found=Il bersaglio non è stato trovato.
|
||||
network_error=Errore di rete
|
||||
|
||||
|
@ -1331,7 +1329,6 @@ issues.dependency.add_error_dep_not_same_repo=Entrambi i problemi devono essere
|
|||
issues.review.self.approval=Non puoi approvare la tua pull request.
|
||||
issues.review.self.rejection=Non puoi richiedere modifiche sulla tua pull request.
|
||||
issues.review.approve=hanno approvato queste modifiche %s
|
||||
issues.review.comment=Commentare
|
||||
issues.review.dismissed=recensione %s di %s respinta
|
||||
issues.review.dismissed_label=Respinta
|
||||
issues.review.left_comment=lascia un commento
|
||||
|
@ -1352,6 +1349,7 @@ issues.review.hide_resolved=Nascondi risolte
|
|||
issues.review.resolve_conversation=Risolvi la conversazione
|
||||
issues.review.un_resolve_conversation=Segnala la conversazione come non risolta
|
||||
issues.review.resolved_by=ha contrassegnato questa conversazione come risolta
|
||||
issues.review.commented=Commentare
|
||||
issues.assignee.error=Non tutte le assegnazioni sono state aggiunte a causa di un errore imprevisto.
|
||||
issues.reference_issue.body=Corpo
|
||||
issues.content_history.deleted=eliminato
|
||||
|
|
|
@ -218,8 +218,6 @@ string.desc=Z - A
|
|||
|
||||
[error]
|
||||
occurred=エラーが発生しました
|
||||
missing_csrf=不正なリクエスト: CSRFトークンがありません
|
||||
invalid_csrf=不正なリクエスト: CSRFトークンが無効です
|
||||
not_found=ターゲットが見つかりませんでした。
|
||||
network_error=ネットワークエラー
|
||||
|
||||
|
@ -1714,7 +1712,6 @@ issues.dependency.add_error_dep_not_same_repo=両方とも同じリポジトリ
|
|||
issues.review.self.approval=自分のプルリクエストを承認することはできません。
|
||||
issues.review.self.rejection=自分のプルリクエストに対して修正を要求することはできません。
|
||||
issues.review.approve=が変更を承認 %s
|
||||
issues.review.comment=コメント
|
||||
issues.review.dismissed=が %s のレビューを棄却 %s
|
||||
issues.review.dismissed_label=棄却
|
||||
issues.review.left_comment=がコメント
|
||||
|
@ -1739,6 +1736,7 @@ issues.review.hide_resolved=解決済みを隠す
|
|||
issues.review.resolve_conversation=解決済みにする
|
||||
issues.review.un_resolve_conversation=未解決にする
|
||||
issues.review.resolved_by=がこの会話を解決済みにしました
|
||||
issues.review.commented=コメント
|
||||
issues.assignee.error=予期しないエラーにより、一部の担当者を追加できませんでした。
|
||||
issues.reference_issue.body=内容
|
||||
issues.content_history.deleted=削除しました
|
||||
|
|
|
@ -818,12 +818,12 @@ issues.dependency.add_error_dep_not_same_repo=두 이슈는 같은 레포지토
|
|||
issues.review.self.approval=자신의 풀 리퀘스트를 승인할 수 없습니다.
|
||||
issues.review.self.rejection=자신의 풀 리퀘스트에 대한 변경을 요청할 수 없습니다.
|
||||
issues.review.approve="이 변경사항을 승인하였습니다. %s"
|
||||
issues.review.comment=댓글
|
||||
issues.review.pending=보류
|
||||
issues.review.review=검토
|
||||
issues.review.reviewers=리뷰어
|
||||
issues.review.show_outdated=오래된 내역 보기
|
||||
issues.review.hide_outdated=오래된 내역 숨기기
|
||||
issues.review.commented=댓글
|
||||
|
||||
|
||||
pulls.new=새 풀 리퀘스트
|
||||
|
|
|
@ -187,8 +187,6 @@ string.desc=Z - A
|
|||
|
||||
[error]
|
||||
occurred=Radusies kļūda
|
||||
missing_csrf=Kļūdains pieprasījums: netika iesūtīta drošības pilnvara
|
||||
invalid_csrf=Kļūdains pieprasījums: iesūtīta kļūdaina drošības pilnvara
|
||||
not_found=Pieprasītie dati netika atrasti.
|
||||
network_error=Tīkla kļūda
|
||||
|
||||
|
@ -1609,7 +1607,6 @@ issues.dependency.add_error_dep_not_same_repo=Abām problēmām ir jābūt no vi
|
|||
issues.review.self.approval=Nevar apstiprināt savu izmaiņu pieprasījumi.
|
||||
issues.review.self.rejection=Nevar pieprasīt izmaiņas savam izmaiņu pieprasījumam.
|
||||
issues.review.approve=apstiprināja izmaiņas %s
|
||||
issues.review.comment=Komentēt
|
||||
issues.review.dismissed=atmeta %s recenziju %s
|
||||
issues.review.dismissed_label=Atmesta
|
||||
issues.review.left_comment=atstāja komentāru
|
||||
|
@ -1634,6 +1631,7 @@ issues.review.hide_resolved=Paslēpt atrisināto
|
|||
issues.review.resolve_conversation=Atrisināt sarunu
|
||||
issues.review.un_resolve_conversation=Atcelt sarunas atrisinājumu
|
||||
issues.review.resolved_by=atzīmēja sarunu kā atrisinātu
|
||||
issues.review.commented=Komentēt
|
||||
issues.assignee.error=Ne visi atbildīgie tika pievienoti, jo radās neparedzēta kļūda.
|
||||
issues.reference_issue.body=Saturs
|
||||
issues.content_history.deleted=dzēsts
|
||||
|
|
|
@ -134,8 +134,6 @@ filter.private=Prive
|
|||
|
||||
[error]
|
||||
occurred=Er is een fout opgetreden
|
||||
missing_csrf=Foutief verzoek: geen CSRF-token aanwezig
|
||||
invalid_csrf=Verkeerd verzoek: ongeldig CSRF-token
|
||||
not_found=Het doel kon niet worden gevonden.
|
||||
network_error=Netwerk fout
|
||||
|
||||
|
@ -1328,7 +1326,6 @@ issues.dependency.add_error_dep_not_same_repo=Beide kwesties moeten in dezelfde
|
|||
issues.review.self.approval=Je kan je eigen pull-aanvraag niet goedkeuren.
|
||||
issues.review.self.rejection=Je kan geen wijzigingen aanvragen op je eigen pull-aanvraag.
|
||||
issues.review.approve=heeft deze veranderingen %s goedgekeurd
|
||||
issues.review.comment=Opmerking
|
||||
issues.review.dismissed=%s's beoordeling afgewezen %s
|
||||
issues.review.dismissed_label=Afgewezen
|
||||
issues.review.left_comment=heeft een reactie achtergelaten
|
||||
|
@ -1349,6 +1346,7 @@ issues.review.hide_resolved=Verbergen afgehandeld
|
|||
issues.review.resolve_conversation=Gesprek oplossen
|
||||
issues.review.un_resolve_conversation=Gesprek niet oplossen
|
||||
issues.review.resolved_by=markeerde dit gesprek als opgelost
|
||||
issues.review.commented=Opmerking
|
||||
issues.assignee.error=Niet alle aangewezen personen zijn toegevoegd vanwege een onverwachte fout.
|
||||
issues.reference_issue.body=Inhoud
|
||||
issues.content_history.deleted=verwijderd
|
||||
|
|
|
@ -131,8 +131,6 @@ filter.private=Prywatne
|
|||
|
||||
[error]
|
||||
occurred=Wystąpił błąd
|
||||
missing_csrf=Błędne żądanie: brak tokenu CSRF
|
||||
invalid_csrf=Błędne żądanie: nieprawidłowy token CSRF
|
||||
not_found=Nie można odnaleźć celu.
|
||||
network_error=Błąd sieci
|
||||
|
||||
|
@ -1220,7 +1218,6 @@ issues.dependency.add_error_dep_not_same_repo=Oba zgłoszenia muszą być w tym
|
|||
issues.review.self.approval=Nie możesz zatwierdzić swojego własnego Pull Requesta.
|
||||
issues.review.self.rejection=Nie możesz zażądać zmian w swoim własnym Pull Requeście.
|
||||
issues.review.approve=zatwierdza te zmiany %s
|
||||
issues.review.comment=Skomentuj
|
||||
issues.review.dismissed_label=Odrzucony
|
||||
issues.review.left_comment=zostawił komentarz
|
||||
issues.review.content.empty=Musisz pozostawić komentarz o pożądanej zmianie/zmianach.
|
||||
|
@ -1240,6 +1237,7 @@ issues.review.hide_resolved=Ukryj rozwiązane
|
|||
issues.review.resolve_conversation=Rozwiąż dyskusję
|
||||
issues.review.un_resolve_conversation=Oznacz dyskusję jako nierozstrzygniętą
|
||||
issues.review.resolved_by=oznaczył(-a) tę rozmowę jako rozwiązaną
|
||||
issues.review.commented=Skomentuj
|
||||
issues.assignee.error=Nie udało się dodać wszystkich wybranych osób do przypisanych przez nieoczekiwany błąd.
|
||||
issues.reference_issue.body=Treść
|
||||
issues.content_history.edited=edytowano
|
||||
|
|
|
@ -184,8 +184,6 @@ string.desc=Z - A
|
|||
|
||||
[error]
|
||||
occurred=Ocorreu um erro
|
||||
missing_csrf=Pedido inválido: não tem token CSRF presente
|
||||
invalid_csrf=Requisição Inválida: token CSRF inválido
|
||||
not_found=Não foi possível encontrar o destino.
|
||||
network_error=Erro de rede
|
||||
|
||||
|
@ -1599,7 +1597,6 @@ issues.dependency.add_error_dep_not_same_repo=Ambas as issues devem estar no mes
|
|||
issues.review.self.approval=Você não pode aprovar o seu próprio pull request.
|
||||
issues.review.self.rejection=Você não pode solicitar alterações em seu próprio pull request.
|
||||
issues.review.approve=aprovou estas alterações %s
|
||||
issues.review.comment=Comentar
|
||||
issues.review.dismissed=rejeitou a revisão de %s %s
|
||||
issues.review.dismissed_label=Rejeitada
|
||||
issues.review.left_comment=deixou um comentário
|
||||
|
@ -1624,6 +1621,7 @@ issues.review.hide_resolved=Ocultar resolvidas
|
|||
issues.review.resolve_conversation=Resolver conversa
|
||||
issues.review.un_resolve_conversation=Conversa não resolvida
|
||||
issues.review.resolved_by=marcou esta conversa como resolvida
|
||||
issues.review.commented=Comentar
|
||||
issues.assignee.error=Nem todos os responsáveis foram adicionados devido a um erro inesperado.
|
||||
issues.reference_issue.body=Conteúdo
|
||||
issues.content_history.deleted=excluído
|
||||
|
|
|
@ -178,6 +178,8 @@ code_search_by_git_grep=Os resultados da pesquisa no código-fonte neste momento
|
|||
package_kind=Pesquisar pacotes...
|
||||
project_kind=Pesquisar planeamentos...
|
||||
branch_kind=Pesquisar ramos...
|
||||
tag_kind=Pesquisar etiquetas...
|
||||
tag_tooltip=Pesquisar etiquetas correspondentes. Use '%' para corresponder a qualquer sequência de números.
|
||||
commit_kind=Pesquisar cometimentos...
|
||||
runner_kind=Pesquisar executores...
|
||||
no_results=Não foram encontrados resultados correspondentes.
|
||||
|
@ -220,8 +222,6 @@ string.desc=Z - A
|
|||
[error]
|
||||
occurred=Ocorreu um erro
|
||||
report_message=Se acredita tratar-se de um erro do Gitea, procure questões relacionadas no <a href="%s">GitHub</a> ou abra uma nova questão, se necessário.
|
||||
missing_csrf=Pedido inválido: não há código CSRF
|
||||
invalid_csrf=Pedido inválido: código CSRF inválido
|
||||
not_found=Não foi possível encontrar o destino.
|
||||
network_error=Erro de rede
|
||||
|
||||
|
@ -1731,7 +1731,7 @@ issues.dependency.add_error_dep_not_same_repo=Ambas as questões têm que estar
|
|||
issues.review.self.approval=Não pode aprovar o seu próprio pedido de integração.
|
||||
issues.review.self.rejection=Não pode solicitar modificações sobre o seu próprio pedido de integração.
|
||||
issues.review.approve=aprovou estas modificações %s
|
||||
issues.review.comment=Comentar
|
||||
issues.review.comment=reviu %s
|
||||
issues.review.dismissed=descartou a revisão de %s %s
|
||||
issues.review.dismissed_label=Descartada
|
||||
issues.review.left_comment=deixou um comentário
|
||||
|
@ -1756,6 +1756,7 @@ issues.review.hide_resolved=Ocultar os concluídos
|
|||
issues.review.resolve_conversation=Passar diálogo ao estado de resolvido
|
||||
issues.review.un_resolve_conversation=Passar diálogo ao estado de não resolvido
|
||||
issues.review.resolved_by=marcou este diálogo como estando concluído
|
||||
issues.review.commented=Comentar
|
||||
issues.review.official=Aprovada
|
||||
issues.review.requested=Revisão pendente
|
||||
issues.review.rejected=Modificações solicitadas
|
||||
|
|
|
@ -182,8 +182,6 @@ string.desc=Я - А
|
|||
|
||||
[error]
|
||||
occurred=Произошла ошибка
|
||||
missing_csrf=Некорректный запрос: отсутствует токен CSRF
|
||||
invalid_csrf=Некорректный запрос: неверный токен CSRF
|
||||
not_found=Цель не найдена.
|
||||
network_error=Ошибка сети
|
||||
|
||||
|
@ -1578,7 +1576,6 @@ issues.dependency.add_error_dep_not_same_repo=Обе задачи должны
|
|||
issues.review.self.approval=Вы не можете одобрить собственный запрос на слияние.
|
||||
issues.review.self.rejection=Невозможно запрашивать изменения своего запроса на слияние.
|
||||
issues.review.approve=одобрил(а) эти изменения %s
|
||||
issues.review.comment=Комментировать
|
||||
issues.review.dismissed=отклонен отзыв %s %s
|
||||
issues.review.dismissed_label=Отклонено
|
||||
issues.review.left_comment=оставил комментарий
|
||||
|
@ -1602,6 +1599,7 @@ issues.review.hide_resolved=Скрыть разрешенные
|
|||
issues.review.resolve_conversation=Покинуть диалог
|
||||
issues.review.un_resolve_conversation=Незавершённый разговор
|
||||
issues.review.resolved_by=пометить этот разговор как разрешённый
|
||||
issues.review.commented=Комментировать
|
||||
issues.assignee.error=Не все назначения были добавлены из-за непредвиденной ошибки.
|
||||
issues.reference_issue.body=Тело
|
||||
issues.content_history.deleted=удалено
|
||||
|
|
|
@ -118,7 +118,6 @@ filter.private=පෞද්ගලික
|
|||
[filter]
|
||||
|
||||
[error]
|
||||
missing_csrf=නරක ඉල්ලීම: CSRF ටෝකන් නොමැත
|
||||
|
||||
[startpage]
|
||||
app_desc=වේදනාකාරී, ස්වයං-සත්කාරක Git සේවාවක්
|
||||
|
@ -1200,7 +1199,6 @@ issues.dependency.add_error_dep_not_same_repo=මෙම ගැටළු දෙ
|
|||
issues.review.self.approval=ඔබ ඔබේ ම අදින්න ඉල්ලීම අනුමත කළ නොහැක.
|
||||
issues.review.self.rejection=ඔබ ඔබේ ම අදින්න ඉල්ලීම මත වෙනස්කම් ඉල්ලා සිටිය නොහැක.
|
||||
issues.review.approve=මෙම වෙනස්කම් අනුමත %s
|
||||
issues.review.comment=අදහස
|
||||
issues.review.dismissed=%sහි සමාලෝචනය %sප්රතික්ෂේප කරන ලද
|
||||
issues.review.dismissed_label=බැහැර
|
||||
issues.review.left_comment=අදහසක් හැරගියා
|
||||
|
@ -1221,6 +1219,7 @@ issues.review.hide_resolved=විසඳා සඟවන්න
|
|||
issues.review.resolve_conversation=සංවාදය විසඳන්න
|
||||
issues.review.un_resolve_conversation=නොවිසඳිය හැකි සංවාදය
|
||||
issues.review.resolved_by=මෙම සංවාදය විසඳා ඇති පරිදි සලකුණු කර ඇත
|
||||
issues.review.commented=අදහස
|
||||
issues.assignee.error=අනපේක්ෂිත දෝෂයක් හේතුවෙන් සියලුම ඇසිග්නස් එකතු නොකළේය.
|
||||
issues.reference_issue.body=ශරීරය
|
||||
issues.content_history.deleted=මකා දැමූ
|
||||
|
|
|
@ -181,8 +181,6 @@ string.desc=Z - A
|
|||
|
||||
[error]
|
||||
occurred=Vyskytla sa chyba
|
||||
missing_csrf=Nesprávna žiadosť: neprítomný CSFR token
|
||||
invalid_csrf=Nesprávna žiadosť: nesprávny CSFR token
|
||||
not_found=Nebolo možné nájsť cieľ.
|
||||
network_error=Chyba siete
|
||||
|
||||
|
|
|
@ -1039,7 +1039,6 @@ issues.dependency.add_error_dep_not_same_repo=Båda ärendena måste vara i samm
|
|||
issues.review.self.approval=Du kan inte godkänna din egen pull-begäran.
|
||||
issues.review.self.rejection=Du kan inte begära ändringar för din egna pull-förfrågan.
|
||||
issues.review.approve=godkände dessa ändringar %s
|
||||
issues.review.comment=Kommentar
|
||||
issues.review.left_comment=lämnade en kommentar
|
||||
issues.review.content.empty=Du måste skriva en kommentar som anger de önskade ändringarna.
|
||||
issues.review.reject=begärda ändringar %s
|
||||
|
@ -1056,6 +1055,7 @@ issues.review.show_resolved=Visa löst
|
|||
issues.review.hide_resolved=Dölj löst
|
||||
issues.review.resolve_conversation=Lös konversation
|
||||
issues.review.resolved_by=markerade denna konversation som löst
|
||||
issues.review.commented=Kommentar
|
||||
issues.assignee.error=Inte alla tilldelade har lagts till på grund av ett oväntat fel.
|
||||
issues.content_history.options=Alternativ
|
||||
|
||||
|
|
|
@ -218,8 +218,6 @@ string.desc=Z - A
|
|||
|
||||
[error]
|
||||
occurred=Bir hata oluştu
|
||||
missing_csrf=Hatalı İstek: CSRF anahtarı yok
|
||||
invalid_csrf=Hatalı İstek: geçersiz CSRF erişim anahtarı
|
||||
not_found=Hedef bulunamadı.
|
||||
network_error=Ağ hatası
|
||||
|
||||
|
@ -1704,7 +1702,6 @@ issues.dependency.add_error_dep_not_same_repo=Her iki konu da aynı depoda olmal
|
|||
issues.review.self.approval=Kendi değişiklik isteğinizi onaylayamazsınız.
|
||||
issues.review.self.rejection=Kendi değişiklik isteğinizde değişiklik isteyemezsiniz.
|
||||
issues.review.approve=%s bu değişiklikleri onayladı
|
||||
issues.review.comment=Yorum Yap
|
||||
issues.review.dismissed=%s incelemesini %s reddetti
|
||||
issues.review.dismissed_label=Reddedildi
|
||||
issues.review.left_comment=bir yorum yaptı
|
||||
|
@ -1729,6 +1726,7 @@ issues.review.hide_resolved=Çözülenleri gizle
|
|||
issues.review.resolve_conversation=Konuşmayı çöz
|
||||
issues.review.un_resolve_conversation=Konuşmayı çözme
|
||||
issues.review.resolved_by=bu konuşmayı çözümlenmiş olarak işaretledi
|
||||
issues.review.commented=Yorum Yap
|
||||
issues.assignee.error=Beklenmeyen bir hata nedeniyle tüm atananlar eklenmedi.
|
||||
issues.reference_issue.body=Gövde
|
||||
issues.content_history.deleted=silindi
|
||||
|
|
|
@ -120,7 +120,6 @@ filter.private=Приватний
|
|||
|
||||
[error]
|
||||
occurred=Сталася помилка
|
||||
missing_csrf=Некоректний запит: токен CSRF не задано
|
||||
network_error=Помилка мережі
|
||||
|
||||
[startpage]
|
||||
|
@ -1245,7 +1244,6 @@ issues.dependency.add_error_dep_not_same_repo=Обидві задачі пови
|
|||
issues.review.self.approval=Ви не можете схвалити власний пулл-реквест.
|
||||
issues.review.self.rejection=Ви не можете надіслати запит на зміну на власний пулл-реквест.
|
||||
issues.review.approve=зміни затверджено %s
|
||||
issues.review.comment=Коментар
|
||||
issues.review.dismissed=відхилено відгук %s %s
|
||||
issues.review.dismissed_label=Відхилено
|
||||
issues.review.left_comment=додав коментар
|
||||
|
@ -1266,6 +1264,7 @@ issues.review.hide_resolved=Приховати вирішене
|
|||
issues.review.resolve_conversation=Завершити обговорення
|
||||
issues.review.un_resolve_conversation=Поновити обговорення
|
||||
issues.review.resolved_by=позначив обговорення завершеним
|
||||
issues.review.commented=Коментар
|
||||
issues.assignee.error=Додано не всіх виконавців через непередбачену помилку.
|
||||
issues.reference_issue.body=Тіло
|
||||
issues.content_history.deleted=видалено
|
||||
|
|
|
@ -217,8 +217,6 @@ string.desc=Z - A
|
|||
|
||||
[error]
|
||||
occurred=发生了一个错误
|
||||
missing_csrf=错误的请求:没有 CSRF 令牌
|
||||
invalid_csrf=错误的请求:无效的 CSRF 令牌
|
||||
not_found=找不到目标。
|
||||
network_error=网络错误
|
||||
|
||||
|
@ -1692,7 +1690,6 @@ issues.dependency.add_error_dep_not_same_repo=这两个工单必须在同一仓
|
|||
issues.review.self.approval=您不能批准您自己的合并请求。
|
||||
issues.review.self.rejection=您不能请求对您自己的合并请求进行更改。
|
||||
issues.review.approve=于 %s 批准此合并请求
|
||||
issues.review.comment=评论
|
||||
issues.review.dismissed=于 %[2]s 取消了 %[1]s 的评审
|
||||
issues.review.dismissed_label=已取消
|
||||
issues.review.left_comment=留下了一条评论
|
||||
|
@ -1717,6 +1714,7 @@ issues.review.hide_resolved=隐藏已解决的
|
|||
issues.review.resolve_conversation=已解决问题
|
||||
issues.review.un_resolve_conversation=未解决问题
|
||||
issues.review.resolved_by=标记问题为已解决
|
||||
issues.review.commented=评论
|
||||
issues.assignee.error=因为未知原因,并非所有的指派都成功。
|
||||
issues.reference_issue.body=内容
|
||||
issues.content_history.deleted=删除于
|
||||
|
|
|
@ -167,8 +167,6 @@ string.desc=Z - A
|
|||
|
||||
[error]
|
||||
occurred=發生錯誤
|
||||
missing_csrf=錯誤的請求:未提供 CSRF token
|
||||
invalid_csrf=錯誤的請求:無效的 CSRF token
|
||||
not_found=找不到目標。
|
||||
network_error=網路錯誤
|
||||
|
||||
|
@ -1467,7 +1465,6 @@ issues.dependency.add_error_dep_not_same_repo=這兩個問題必須在同一個
|
|||
issues.review.self.approval=您不能核可自己的合併請求。
|
||||
issues.review.self.rejection=您不能對自己的合併請求提出請求變更。
|
||||
issues.review.approve=核可了這些變更 %s
|
||||
issues.review.comment=留言
|
||||
issues.review.dismissed=取消 %s 的審核 %s
|
||||
issues.review.dismissed_label=已取消
|
||||
issues.review.left_comment=留下了回應
|
||||
|
@ -1488,6 +1485,7 @@ issues.review.hide_resolved=隱藏已解決
|
|||
issues.review.resolve_conversation=解決對話
|
||||
issues.review.un_resolve_conversation=取消解決對話
|
||||
issues.review.resolved_by=標記了此對話為已解決
|
||||
issues.review.commented=留言
|
||||
issues.assignee.error=因為未預期的錯誤,未能成功加入所有負責人。
|
||||
issues.reference_issue.body=內容
|
||||
issues.content_history.deleted=刪除
|
||||
|
|
|
@ -214,6 +214,8 @@ func TagsList(ctx *context.Context) {
|
|||
ctx.Data["HideBranchesInDropdown"] = true
|
||||
ctx.Data["CanCreateRelease"] = ctx.Repo.CanWrite(unit.TypeReleases) && !ctx.Repo.Repository.IsArchived
|
||||
|
||||
namePattern := ctx.FormTrim("q")
|
||||
|
||||
listOptions := db.ListOptions{
|
||||
Page: ctx.FormInt("page"),
|
||||
PageSize: ctx.FormInt("limit"),
|
||||
|
@ -233,6 +235,7 @@ func TagsList(ctx *context.Context) {
|
|||
IncludeTags: true,
|
||||
HasSha1: optional.Some(true),
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
NamePattern: optional.Some(namePattern),
|
||||
}
|
||||
|
||||
releases, err := db.Find[repo_model.Release](ctx, opts)
|
||||
|
@ -241,14 +244,21 @@ func TagsList(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
ctx.Data["Releases"] = releases
|
||||
count, err := db.Count[repo_model.Release](ctx, opts)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetReleasesByRepoID", err)
|
||||
return
|
||||
}
|
||||
|
||||
numTags := ctx.Data["NumTags"].(int64)
|
||||
pager := context.NewPagination(int(numTags), opts.PageSize, opts.Page, 5)
|
||||
ctx.Data["Keyword"] = namePattern
|
||||
ctx.Data["Releases"] = releases
|
||||
ctx.Data["TagCount"] = count
|
||||
|
||||
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.Data["PageIsViewCode"] = !ctx.Repo.Repository.UnitEnabled(ctx, unit.TypeReleases)
|
||||
|
||||
ctx.HTML(http.StatusOK, tplTagsList)
|
||||
}
|
||||
|
||||
|
|
|
@ -129,6 +129,8 @@ func webAuth(authMethod auth_service.Method) func(*context.Context) {
|
|||
// ensure the session uid is deleted
|
||||
_ = ctx.Session.Delete("uid")
|
||||
}
|
||||
|
||||
ctx.Csrf.PrepareForSessionUser(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -138,10 +138,8 @@ func Contexter() func(next http.Handler) http.Handler {
|
|||
csrfOpts := CsrfOptions{
|
||||
Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()),
|
||||
Cookie: setting.CSRFCookieName,
|
||||
SetCookie: true,
|
||||
Secure: setting.SessionConfig.Secure,
|
||||
CookieHTTPOnly: setting.CSRFCookieHTTPOnly,
|
||||
Header: "X-Csrf-Token",
|
||||
CookieDomain: setting.SessionConfig.Domain,
|
||||
CookiePath: setting.SessionConfig.CookiePath,
|
||||
SameSite: setting.SessionConfig.SameSite,
|
||||
|
@ -167,7 +165,7 @@ func Contexter() func(next http.Handler) http.Handler {
|
|||
ctx.Base.AppendContextValue(WebContextKey, ctx)
|
||||
ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
|
||||
|
||||
ctx.Csrf = PrepareCSRFProtector(csrfOpts, ctx)
|
||||
ctx.Csrf = NewCSRFProtector(csrfOpts)
|
||||
|
||||
// Get the last flash message from cookie
|
||||
lastFlashCookie := middleware.GetSiteCookie(ctx.Req, CookieNameFlash)
|
||||
|
@ -204,8 +202,6 @@ func Contexter() func(next http.Handler) http.Handler {
|
|||
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
|
||||
|
||||
ctx.Data["SystemConfig"] = setting.Config()
|
||||
ctx.Data["CsrfToken"] = ctx.Csrf.GetToken()
|
||||
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.Data["CsrfToken"].(string) + `">`)
|
||||
|
||||
// FIXME: do we really always need these setting? There should be someway to have to avoid having to always set these
|
||||
ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations
|
||||
|
|
|
@ -20,64 +20,42 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
)
|
||||
|
||||
const (
|
||||
CsrfHeaderName = "X-Csrf-Token"
|
||||
CsrfFormName = "_csrf"
|
||||
)
|
||||
|
||||
// CSRFProtector represents a CSRF protector and is used to get the current token and validate the token.
|
||||
type CSRFProtector interface {
|
||||
// GetHeaderName returns HTTP header to search for token.
|
||||
GetHeaderName() string
|
||||
// GetFormName returns form value to search for token.
|
||||
GetFormName() string
|
||||
// GetToken returns the token.
|
||||
GetToken() string
|
||||
// Validate validates the token in http context.
|
||||
// PrepareForSessionUser prepares the csrf protector for the current session user.
|
||||
PrepareForSessionUser(ctx *Context)
|
||||
// Validate validates the csrf token in http context.
|
||||
Validate(ctx *Context)
|
||||
// DeleteCookie deletes the cookie
|
||||
// DeleteCookie deletes the csrf cookie
|
||||
DeleteCookie(ctx *Context)
|
||||
}
|
||||
|
||||
type csrfProtector struct {
|
||||
opt CsrfOptions
|
||||
// Token generated to pass via header, cookie, or hidden form value.
|
||||
Token string
|
||||
// This value must be unique per user.
|
||||
ID string
|
||||
}
|
||||
|
||||
// GetHeaderName returns the name of the HTTP header for csrf token.
|
||||
func (c *csrfProtector) GetHeaderName() string {
|
||||
return c.opt.Header
|
||||
}
|
||||
|
||||
// GetFormName returns the name of the form value for csrf token.
|
||||
func (c *csrfProtector) GetFormName() string {
|
||||
return c.opt.Form
|
||||
}
|
||||
|
||||
// GetToken returns the current token. This is typically used
|
||||
// to populate a hidden form in an HTML template.
|
||||
func (c *csrfProtector) GetToken() string {
|
||||
return c.Token
|
||||
// id must be unique per user.
|
||||
id string
|
||||
// token is the valid one which wil be used by end user and passed via header, cookie, or hidden form value.
|
||||
token string
|
||||
}
|
||||
|
||||
// CsrfOptions maintains options to manage behavior of Generate.
|
||||
type CsrfOptions struct {
|
||||
// The global secret value used to generate Tokens.
|
||||
Secret string
|
||||
// HTTP header used to set and get token.
|
||||
Header string
|
||||
// Form value used to set and get token.
|
||||
Form string
|
||||
// Cookie value used to set and get token.
|
||||
Cookie string
|
||||
// Cookie domain.
|
||||
|
@ -87,103 +65,64 @@ type CsrfOptions struct {
|
|||
CookieHTTPOnly bool
|
||||
// SameSite set the cookie SameSite type
|
||||
SameSite http.SameSite
|
||||
// Key used for getting the unique ID per user.
|
||||
SessionKey string
|
||||
// oldSessionKey saves old value corresponding to SessionKey.
|
||||
oldSessionKey string
|
||||
// If true, send token via X-Csrf-Token header.
|
||||
SetHeader bool
|
||||
// If true, send token via _csrf cookie.
|
||||
SetCookie bool
|
||||
// Set the Secure flag to true on the cookie.
|
||||
Secure bool
|
||||
// Disallow Origin appear in request header.
|
||||
Origin bool
|
||||
// Cookie lifetime. Default is 0
|
||||
CookieLifeTime int
|
||||
// sessionKey is the key used for getting the unique ID per user.
|
||||
sessionKey string
|
||||
// oldSessionKey saves old value corresponding to sessionKey.
|
||||
oldSessionKey string
|
||||
}
|
||||
|
||||
func prepareDefaultCsrfOptions(opt CsrfOptions) CsrfOptions {
|
||||
if opt.Secret == "" {
|
||||
randBytes, err := util.CryptoRandomBytes(8)
|
||||
if err != nil {
|
||||
// this panic can be handled by the recover() in http handlers
|
||||
panic(fmt.Errorf("failed to generate random bytes: %w", err))
|
||||
}
|
||||
opt.Secret = base32.StdEncoding.EncodeToString(randBytes)
|
||||
}
|
||||
if opt.Header == "" {
|
||||
opt.Header = "X-Csrf-Token"
|
||||
}
|
||||
if opt.Form == "" {
|
||||
opt.Form = "_csrf"
|
||||
}
|
||||
if opt.Cookie == "" {
|
||||
opt.Cookie = "_csrf"
|
||||
}
|
||||
if opt.CookiePath == "" {
|
||||
opt.CookiePath = "/"
|
||||
}
|
||||
if opt.SessionKey == "" {
|
||||
opt.SessionKey = "uid"
|
||||
}
|
||||
if opt.CookieLifeTime == 0 {
|
||||
opt.CookieLifeTime = int(CsrfTokenTimeout.Seconds())
|
||||
}
|
||||
|
||||
opt.oldSessionKey = "_old_" + opt.SessionKey
|
||||
return opt
|
||||
}
|
||||
|
||||
func newCsrfCookie(c *csrfProtector, value string) *http.Cookie {
|
||||
func newCsrfCookie(opt *CsrfOptions, value string) *http.Cookie {
|
||||
return &http.Cookie{
|
||||
Name: c.opt.Cookie,
|
||||
Name: opt.Cookie,
|
||||
Value: value,
|
||||
Path: c.opt.CookiePath,
|
||||
Domain: c.opt.CookieDomain,
|
||||
MaxAge: c.opt.CookieLifeTime,
|
||||
Secure: c.opt.Secure,
|
||||
HttpOnly: c.opt.CookieHTTPOnly,
|
||||
SameSite: c.opt.SameSite,
|
||||
Path: opt.CookiePath,
|
||||
Domain: opt.CookieDomain,
|
||||
MaxAge: int(CsrfTokenTimeout.Seconds()),
|
||||
Secure: opt.Secure,
|
||||
HttpOnly: opt.CookieHTTPOnly,
|
||||
SameSite: opt.SameSite,
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareCSRFProtector returns a CSRFProtector to be used for every request.
|
||||
// Additionally, depending on options set, generated tokens will be sent via Header and/or Cookie.
|
||||
func PrepareCSRFProtector(opt CsrfOptions, ctx *Context) CSRFProtector {
|
||||
opt = prepareDefaultCsrfOptions(opt)
|
||||
x := &csrfProtector{opt: opt}
|
||||
|
||||
if opt.Origin && len(ctx.Req.Header.Get("Origin")) > 0 {
|
||||
return x
|
||||
func NewCSRFProtector(opt CsrfOptions) CSRFProtector {
|
||||
if opt.Secret == "" {
|
||||
panic("CSRF secret is empty but it must be set") // it shouldn't happen because it is always set in code
|
||||
}
|
||||
opt.Cookie = util.IfZero(opt.Cookie, "_csrf")
|
||||
opt.CookiePath = util.IfZero(opt.CookiePath, "/")
|
||||
opt.sessionKey = "uid"
|
||||
opt.oldSessionKey = "_old_" + opt.sessionKey
|
||||
return &csrfProtector{opt: opt}
|
||||
}
|
||||
|
||||
x.ID = "0"
|
||||
uidAny := ctx.Session.Get(opt.SessionKey)
|
||||
if uidAny != nil {
|
||||
func (c *csrfProtector) PrepareForSessionUser(ctx *Context) {
|
||||
c.id = "0"
|
||||
if uidAny := ctx.Session.Get(c.opt.sessionKey); uidAny != nil {
|
||||
switch uidVal := uidAny.(type) {
|
||||
case string:
|
||||
x.ID = uidVal
|
||||
c.id = uidVal
|
||||
case int64:
|
||||
x.ID = strconv.FormatInt(uidVal, 10)
|
||||
c.id = strconv.FormatInt(uidVal, 10)
|
||||
default:
|
||||
log.Error("invalid uid type in session: %T", uidAny)
|
||||
}
|
||||
}
|
||||
|
||||
oldUID := ctx.Session.Get(opt.oldSessionKey)
|
||||
uidChanged := oldUID == nil || oldUID.(string) != x.ID
|
||||
cookieToken := ctx.GetSiteCookie(opt.Cookie)
|
||||
oldUID := ctx.Session.Get(c.opt.oldSessionKey)
|
||||
uidChanged := oldUID == nil || oldUID.(string) != c.id
|
||||
cookieToken := ctx.GetSiteCookie(c.opt.Cookie)
|
||||
|
||||
needsNew := true
|
||||
if uidChanged {
|
||||
_ = ctx.Session.Set(opt.oldSessionKey, x.ID)
|
||||
_ = ctx.Session.Set(c.opt.oldSessionKey, c.id)
|
||||
} else if cookieToken != "" {
|
||||
// If cookie token presents, re-use existing unexpired token, else generate a new one.
|
||||
if issueTime, ok := ParseCsrfToken(cookieToken); ok {
|
||||
dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time.
|
||||
if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval {
|
||||
x.Token = cookieToken
|
||||
c.token = cookieToken
|
||||
needsNew = false
|
||||
}
|
||||
}
|
||||
|
@ -191,42 +130,33 @@ func PrepareCSRFProtector(opt CsrfOptions, ctx *Context) CSRFProtector {
|
|||
|
||||
if needsNew {
|
||||
// FIXME: actionId.
|
||||
x.Token = GenerateCsrfToken(x.opt.Secret, x.ID, "POST", time.Now())
|
||||
if opt.SetCookie {
|
||||
cookie := newCsrfCookie(x, x.Token)
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
c.token = GenerateCsrfToken(c.opt.Secret, c.id, "POST", time.Now())
|
||||
cookie := newCsrfCookie(&c.opt, c.token)
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
|
||||
if opt.SetHeader {
|
||||
ctx.Resp.Header().Add(opt.Header, x.Token)
|
||||
}
|
||||
return x
|
||||
ctx.Data["CsrfToken"] = c.token
|
||||
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + template.HTMLEscapeString(c.token) + `">`)
|
||||
}
|
||||
|
||||
func (c *csrfProtector) validateToken(ctx *Context, token string) {
|
||||
if !ValidCsrfToken(token, c.opt.Secret, c.ID, "POST", time.Now()) {
|
||||
if !ValidCsrfToken(token, c.opt.Secret, c.id, "POST", time.Now()) {
|
||||
c.DeleteCookie(ctx)
|
||||
if middleware.IsAPIPath(ctx.Req) {
|
||||
// currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
|
||||
http.Error(ctx.Resp, "Invalid CSRF token.", http.StatusBadRequest)
|
||||
} else {
|
||||
ctx.Flash.Error(ctx.Tr("error.invalid_csrf"))
|
||||
ctx.Redirect(setting.AppSubURL + "/")
|
||||
}
|
||||
// currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints.
|
||||
// FIXME: distinguish what the response is for: HTML (web page) or JSON (fetch)
|
||||
http.Error(ctx.Resp, "Invalid CSRF token.", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate should be used as a per route middleware. It attempts to get a token from an "X-Csrf-Token"
|
||||
// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated.
|
||||
// If this validation fails, custom Error is sent in the reply.
|
||||
// If neither a header nor form value is found, http.StatusBadRequest is sent.
|
||||
// If this validation fails, http.StatusBadRequest is sent.
|
||||
func (c *csrfProtector) Validate(ctx *Context) {
|
||||
if token := ctx.Req.Header.Get(c.GetHeaderName()); token != "" {
|
||||
if token := ctx.Req.Header.Get(CsrfHeaderName); token != "" {
|
||||
c.validateToken(ctx, token)
|
||||
return
|
||||
}
|
||||
if token := ctx.Req.FormValue(c.GetFormName()); token != "" {
|
||||
if token := ctx.Req.FormValue(CsrfFormName); token != "" {
|
||||
c.validateToken(ctx, token)
|
||||
return
|
||||
}
|
||||
|
@ -234,9 +164,7 @@ func (c *csrfProtector) Validate(ctx *Context) {
|
|||
}
|
||||
|
||||
func (c *csrfProtector) DeleteCookie(ctx *Context) {
|
||||
if c.opt.SetCookie {
|
||||
cookie := newCsrfCookie(c, "")
|
||||
cookie.MaxAge = -1
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
cookie := newCsrfCookie(&c.opt, "")
|
||||
cookie.MaxAge = -1
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
|
|
|
@ -82,43 +82,40 @@ func (h *ReplyHandler) Handle(ctx context.Context, content *MailContent, doer *u
|
|||
return nil
|
||||
}
|
||||
|
||||
attachmentIDs := make([]string, 0, len(content.Attachments))
|
||||
if setting.Attachment.Enabled {
|
||||
for _, attachment := range content.Attachments {
|
||||
a, err := attachment_service.UploadAttachment(ctx, bytes.NewReader(attachment.Content), setting.Attachment.AllowedTypes, int64(len(attachment.Content)), &repo_model.Attachment{
|
||||
Name: attachment.Name,
|
||||
UploaderID: doer.ID,
|
||||
RepoID: issue.Repo.ID,
|
||||
})
|
||||
if err != nil {
|
||||
if upload.IsErrFileTypeForbidden(err) {
|
||||
log.Info("Skipping disallowed attachment type: %s", attachment.Name)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
attachmentIDs = append(attachmentIDs, a.UUID)
|
||||
}
|
||||
}
|
||||
|
||||
if content.Content == "" && len(attachmentIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch r := ref.(type) {
|
||||
case *issues_model.Issue:
|
||||
attachmentIDs := make([]string, 0, len(content.Attachments))
|
||||
if setting.Attachment.Enabled {
|
||||
for _, attachment := range content.Attachments {
|
||||
a, err := attachment_service.UploadAttachment(ctx, bytes.NewReader(attachment.Content), setting.Attachment.AllowedTypes, int64(len(attachment.Content)), &repo_model.Attachment{
|
||||
Name: attachment.Name,
|
||||
UploaderID: doer.ID,
|
||||
RepoID: issue.Repo.ID,
|
||||
})
|
||||
if err != nil {
|
||||
if upload.IsErrFileTypeForbidden(err) {
|
||||
log.Info("Skipping disallowed attachment type: %s", attachment.Name)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
attachmentIDs = append(attachmentIDs, a.UUID)
|
||||
}
|
||||
}
|
||||
|
||||
if content.Content == "" && len(attachmentIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = issue_service.CreateIssueComment(ctx, doer, issue.Repo, issue, content.Content, attachmentIDs)
|
||||
_, err := issue_service.CreateIssueComment(ctx, doer, issue.Repo, issue, content.Content, attachmentIDs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CreateIssueComment failed: %w", err)
|
||||
}
|
||||
case *issues_model.Comment:
|
||||
comment := r
|
||||
|
||||
if content.Content == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if comment.Type == issues_model.CommentTypeCode {
|
||||
switch comment.Type {
|
||||
case issues_model.CommentTypeCode:
|
||||
_, err := pull_service.CreateCodeComment(
|
||||
ctx,
|
||||
doer,
|
||||
|
@ -130,11 +127,16 @@ func (h *ReplyHandler) Handle(ctx context.Context, content *MailContent, doer *u
|
|||
false, // not pending review but a single review
|
||||
comment.ReviewID,
|
||||
"",
|
||||
nil,
|
||||
attachmentIDs,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CreateCodeComment failed: %w", err)
|
||||
}
|
||||
default:
|
||||
_, err := issue_service.CreateIssueComment(ctx, doer, issue.Repo, issue, content.Content, attachmentIDs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("CreateIssueComment failed: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -320,8 +320,9 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
|||
}
|
||||
|
||||
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
||||
RepoID: repo.ID,
|
||||
TagNames: tags,
|
||||
RepoID: repo.ID,
|
||||
TagNames: tags,
|
||||
IncludeTags: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("db.Find[repo_model.Release]: %w", err)
|
||||
|
@ -382,12 +383,12 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
|||
|
||||
rel, has := relMap[lowerTag]
|
||||
|
||||
parts := strings.SplitN(tag.Message, "\n", 2)
|
||||
note := ""
|
||||
if len(parts) > 1 {
|
||||
note = parts[1]
|
||||
}
|
||||
if !has {
|
||||
parts := strings.SplitN(tag.Message, "\n", 2)
|
||||
note := ""
|
||||
if len(parts) > 1 {
|
||||
note = parts[1]
|
||||
}
|
||||
rel = &repo_model.Release{
|
||||
RepoID: repo.ID,
|
||||
Title: parts[0],
|
||||
|
@ -408,10 +409,11 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
|
|||
|
||||
newReleases = append(newReleases, rel)
|
||||
} else {
|
||||
rel.Title = parts[0]
|
||||
rel.Note = note
|
||||
rel.Sha1 = commit.ID.String()
|
||||
rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
|
||||
rel.NumCommits = commitsCount
|
||||
rel.IsDraft = false
|
||||
if rel.IsTag && author != nil {
|
||||
rel.PublisherID = author.ID
|
||||
}
|
||||
|
|
|
@ -4,14 +4,19 @@
|
|||
<div class="ui container">
|
||||
{{template "base/alert" .}}
|
||||
{{template "repo/release_tag_header" .}}
|
||||
{{if .Releases}}
|
||||
<h4 class="ui top attached header">
|
||||
<div class="five wide column tw-flex tw-items-center">
|
||||
{{svg "octicon-tag" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.release.tags"}}
|
||||
{{.TagCount}} {{ctx.Locale.Tr "repo.release.tags"}}
|
||||
</div>
|
||||
</h4>
|
||||
{{$canReadReleases := $.Permission.CanRead ctx.Consts.RepoUnitTypeReleases}}
|
||||
<div class="ui attached segment">
|
||||
<form class="ignore-dirty" method="get">
|
||||
{{template "shared/search/combo" dict "Value" .Keyword "Placeholder" (ctx.Locale.Tr "search.tag_kind") "Tooltip" (ctx.Locale.Tr "search.tag_tooltip")}}
|
||||
</form>
|
||||
</div>
|
||||
<div class="ui attached table segment">
|
||||
{{if .Releases}}
|
||||
<table class="ui very basic striped fixed table single line" id="tags-table">
|
||||
<tbody class="tag-list">
|
||||
{{range $idx, $release := .Releases}}
|
||||
|
@ -57,9 +62,12 @@
|
|||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
{{if .NumTags}}
|
||||
<p class="tw-p-4">{{ctx.Locale.Tr "no_results_found"}}</p>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{template "base/paginate" .}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -59,7 +59,8 @@ func createAttachment(t *testing.T, session *TestSession, repoURL, filename stri
|
|||
func TestCreateAnonymousAttachment(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
session := emptyTestSession(t)
|
||||
createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusSeeOther)
|
||||
// this test is not right because it just doesn't pass the CSRF validation
|
||||
createAttachment(t, session, "user2/repo1", "image.png", generateImg(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateIssueAttachment(t *testing.T) {
|
||||
|
|
|
@ -5,12 +5,10 @@ package integration
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -25,28 +23,12 @@ func TestCsrfProtection(t *testing.T) {
|
|||
req := NewRequestWithValues(t, "POST", "/user/settings", map[string]string{
|
||||
"_csrf": "fake_csrf",
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
resp := session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
loc := resp.Header().Get("Location")
|
||||
assert.Equal(t, setting.AppSubURL+"/", loc)
|
||||
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
assert.Equal(t, "Bad Request: invalid CSRF token",
|
||||
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
|
||||
)
|
||||
resp := session.MakeRequest(t, req, http.StatusBadRequest)
|
||||
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
|
||||
|
||||
// test web form csrf via header. TODO: should use an UI api to test
|
||||
req = NewRequest(t, "POST", "/user/settings")
|
||||
req.Header.Add("X-Csrf-Token", "fake_csrf")
|
||||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
loc = resp.Header().Get("Location")
|
||||
assert.Equal(t, setting.AppSubURL+"/", loc)
|
||||
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
|
||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||
assert.Equal(t, "Bad Request: invalid CSRF token",
|
||||
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
|
||||
)
|
||||
resp = session.MakeRequest(t, req, http.StatusBadRequest)
|
||||
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -26,187 +27,190 @@ import (
|
|||
)
|
||||
|
||||
func TestIncomingEmail(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
|
||||
t.Run("Payload", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
t.Run("Payload", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1})
|
||||
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1})
|
||||
_, err := incoming_payload.CreateReferencePayload(user)
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err := incoming_payload.CreateReferencePayload(user)
|
||||
assert.Error(t, err)
|
||||
issuePayload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
commentPayload, err := incoming_payload.CreateReferencePayload(comment)
|
||||
assert.NoError(t, err)
|
||||
|
||||
issuePayload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
commentPayload, err := incoming_payload.CreateReferencePayload(comment)
|
||||
assert.NoError(t, err)
|
||||
_, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, []byte{1, 2, 3})
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, []byte{1, 2, 3})
|
||||
assert.Error(t, err)
|
||||
ref, err := incoming_payload.GetReferenceFromPayload(db.DefaultContext, issuePayload)
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, ref, new(issues_model.Issue))
|
||||
assert.EqualValues(t, issue.ID, ref.(*issues_model.Issue).ID)
|
||||
|
||||
ref, err := incoming_payload.GetReferenceFromPayload(db.DefaultContext, issuePayload)
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, ref, new(issues_model.Issue))
|
||||
assert.EqualValues(t, issue.ID, ref.(*issues_model.Issue).ID)
|
||||
ref, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, commentPayload)
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, ref, new(issues_model.Comment))
|
||||
assert.EqualValues(t, comment.ID, ref.(*issues_model.Comment).ID)
|
||||
})
|
||||
|
||||
ref, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, commentPayload)
|
||||
assert.NoError(t, err)
|
||||
assert.IsType(t, ref, new(issues_model.Comment))
|
||||
assert.EqualValues(t, comment.ID, ref.(*issues_model.Comment).ID)
|
||||
})
|
||||
t.Run("Token", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
t.Run("Token", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
payload := []byte{1, 2, 3, 4, 5}
|
||||
|
||||
payload := []byte{1, 2, 3, 4, 5}
|
||||
token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, token)
|
||||
ht, u, p, err := token_service.ExtractToken(db.DefaultContext, token)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, token_service.ReplyHandlerType, ht)
|
||||
assert.Equal(t, user.ID, u.ID)
|
||||
assert.Equal(t, payload, p)
|
||||
})
|
||||
|
||||
ht, u, p, err := token_service.ExtractToken(db.DefaultContext, token)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, token_service.ReplyHandlerType, ht)
|
||||
assert.Equal(t, user.ID, u.ID)
|
||||
assert.Equal(t, payload, p)
|
||||
})
|
||||
t.Run("Handler", func(t *testing.T) {
|
||||
t.Run("Reply", func(t *testing.T) {
|
||||
t.Run("Comment", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
t.Run("Handler", func(t *testing.T) {
|
||||
t.Run("Reply", func(t *testing.T) {
|
||||
t.Run("Comment", func(t *testing.T) {
|
||||
handler := &incoming.ReplyHandler{}
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Error(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, nil, payload))
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, user, payload))
|
||||
|
||||
content := &incoming.MailContent{
|
||||
Content: "reply by mail",
|
||||
Attachments: []*incoming.Attachment{
|
||||
{
|
||||
Name: "attachment.txt",
|
||||
Content: []byte("test"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
|
||||
|
||||
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
|
||||
IssueID: issue.ID,
|
||||
Type: issues_model.CommentTypeComment,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, comments)
|
||||
comment := comments[len(comments)-1]
|
||||
assert.Equal(t, user.ID, comment.PosterID)
|
||||
assert.Equal(t, content.Content, comment.Content)
|
||||
assert.NoError(t, comment.LoadAttachments(db.DefaultContext))
|
||||
assert.Len(t, comment.Attachments, 1)
|
||||
attachment := comment.Attachments[0]
|
||||
assert.Equal(t, content.Attachments[0].Name, attachment.Name)
|
||||
assert.EqualValues(t, 4, attachment.Size)
|
||||
})
|
||||
|
||||
t.Run("CodeComment", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 6})
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
|
||||
|
||||
handler := &incoming.ReplyHandler{}
|
||||
content := &incoming.MailContent{
|
||||
Content: "code reply by mail",
|
||||
Attachments: []*incoming.Attachment{
|
||||
{
|
||||
Name: "attachment.txt",
|
||||
Content: []byte("test"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(comment)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
|
||||
|
||||
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
|
||||
IssueID: issue.ID,
|
||||
Type: issues_model.CommentTypeCode,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, comments)
|
||||
comment = comments[len(comments)-1]
|
||||
assert.Equal(t, user.ID, comment.PosterID)
|
||||
assert.Equal(t, content.Content, comment.Content)
|
||||
assert.NoError(t, comment.LoadAttachments(db.DefaultContext))
|
||||
assert.Len(t, comment.Attachments, 1)
|
||||
attachment := comment.Attachments[0]
|
||||
assert.Equal(t, content.Attachments[0].Name, attachment.Name)
|
||||
assert.EqualValues(t, 4, attachment.Size)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Unsubscribe", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
handler := &incoming.ReplyHandler{}
|
||||
watching, err := issues_model.CheckIssueWatch(db.DefaultContext, user, issue)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, watching)
|
||||
|
||||
handler := &incoming.UnsubscribeHandler{}
|
||||
|
||||
content := &incoming.MailContent{
|
||||
Content: "unsub me",
|
||||
}
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Error(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, nil, payload))
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, user, payload))
|
||||
|
||||
content := &incoming.MailContent{
|
||||
Content: "reply by mail",
|
||||
Attachments: []*incoming.Attachment{
|
||||
{
|
||||
Name: "attachment.txt",
|
||||
Content: []byte("test"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
|
||||
|
||||
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
|
||||
IssueID: issue.ID,
|
||||
Type: issues_model.CommentTypeComment,
|
||||
})
|
||||
watching, err = issues_model.CheckIssueWatch(db.DefaultContext, user, issue)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, comments)
|
||||
comment := comments[len(comments)-1]
|
||||
assert.Equal(t, user.ID, comment.PosterID)
|
||||
assert.Equal(t, content.Content, comment.Content)
|
||||
assert.NoError(t, comment.LoadAttachments(db.DefaultContext))
|
||||
assert.Len(t, comment.Attachments, 1)
|
||||
attachment := comment.Attachments[0]
|
||||
assert.Equal(t, content.Attachments[0].Name, attachment.Name)
|
||||
assert.EqualValues(t, 4, attachment.Size)
|
||||
assert.False(t, watching)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("CodeComment", func(t *testing.T) {
|
||||
if setting.IncomingEmail.Enabled {
|
||||
// This test connects to the configured email server and is currently only enabled for MySql integration tests.
|
||||
// It sends a reply to create a comment. If the comment is not detected after 10 seconds the test fails.
|
||||
t.Run("IMAP", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 6})
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
|
||||
|
||||
handler := &incoming.ReplyHandler{}
|
||||
content := &incoming.MailContent{
|
||||
Content: "code reply by mail",
|
||||
Attachments: []*incoming.Attachment{
|
||||
{
|
||||
Name: "attachment.txt",
|
||||
Content: []byte("test"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(comment)
|
||||
payload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
|
||||
|
||||
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
|
||||
IssueID: issue.ID,
|
||||
Type: issues_model.CommentTypeCode,
|
||||
})
|
||||
msg := gomail.NewMessage()
|
||||
msg.SetHeader("To", strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1))
|
||||
msg.SetHeader("From", user.Email)
|
||||
msg.SetBody("text/plain", token)
|
||||
err = gomail.Send(&smtpTestSender{}, msg)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, comments)
|
||||
comment = comments[len(comments)-1]
|
||||
assert.Equal(t, user.ID, comment.PosterID)
|
||||
assert.Equal(t, content.Content, comment.Content)
|
||||
assert.NoError(t, comment.LoadAttachments(db.DefaultContext))
|
||||
assert.Empty(t, comment.Attachments)
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
|
||||
IssueID: issue.ID,
|
||||
Type: issues_model.CommentTypeComment,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, comments)
|
||||
|
||||
comment := comments[len(comments)-1]
|
||||
|
||||
return comment.PosterID == user.ID && comment.Content == token
|
||||
}, 10*time.Second, 1*time.Second)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Unsubscribe", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
watching, err := issues_model.CheckIssueWatch(db.DefaultContext, user, issue)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, watching)
|
||||
|
||||
handler := &incoming.UnsubscribeHandler{}
|
||||
|
||||
content := &incoming.MailContent{
|
||||
Content: "unsub me",
|
||||
}
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
|
||||
|
||||
watching, err = issues_model.CheckIssueWatch(db.DefaultContext, user, issue)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, watching)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if setting.IncomingEmail.Enabled {
|
||||
// This test connects to the configured email server and is currently only enabled for MySql integration tests.
|
||||
// It sends a reply to create a comment. If the comment is not detected after 10 seconds the test fails.
|
||||
t.Run("IMAP", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
payload, err := incoming_payload.CreateReferencePayload(issue)
|
||||
assert.NoError(t, err)
|
||||
token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
|
||||
assert.NoError(t, err)
|
||||
|
||||
msg := gomail.NewMessage()
|
||||
msg.SetHeader("To", strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1))
|
||||
msg.SetHeader("From", user.Email)
|
||||
msg.SetBody("text/plain", token)
|
||||
err = gomail.Send(&smtpTestSender{}, msg)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Eventually(t, func() bool {
|
||||
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
|
||||
IssueID: issue.ID,
|
||||
Type: issues_model.CommentTypeComment,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, comments)
|
||||
|
||||
comment := comments[len(comments)-1]
|
||||
|
||||
return comment.PosterID == user.ID && comment.Content == token
|
||||
}, 10*time.Second, 1*time.Second)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A simple SMTP mail sender used for integration tests.
|
||||
|
|
|
@ -17,7 +17,6 @@ import (
|
|||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
|
@ -146,15 +145,8 @@ func TestCreateBranchInvalidCSRF(t *testing.T) {
|
|||
"_csrf": "fake_csrf",
|
||||
"new_branch_name": "test",
|
||||
})
|
||||
resp := session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
loc := resp.Header().Get("Location")
|
||||
assert.Equal(t, setting.AppSubURL+"/", loc)
|
||||
resp = session.MakeRequest(t, NewRequest(t, "GET", loc), http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
assert.Equal(t,
|
||||
"Bad Request: invalid CSRF token",
|
||||
strings.TrimSpace(htmlDoc.doc.Find(".ui.message").Text()),
|
||||
)
|
||||
resp := session.MakeRequest(t, req, http.StatusBadRequest)
|
||||
assert.Contains(t, resp.Body.String(), "Invalid CSRF token")
|
||||
}
|
||||
|
||||
func prepareBranch(t *testing.T, session *TestSession, repo *repo_model.Repository) {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
|
@ -18,6 +19,7 @@ import (
|
|||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateNewTagProtected(t *testing.T) {
|
||||
|
@ -60,6 +62,40 @@ func TestCreateNewTagProtected(t *testing.T) {
|
|||
})
|
||||
})
|
||||
|
||||
t.Run("GitTagForce", func(t *testing.T) {
|
||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||
httpContext := NewAPITestContext(t, owner.Name, repo.Name)
|
||||
|
||||
dstPath := t.TempDir()
|
||||
|
||||
u.Path = httpContext.GitPath()
|
||||
u.User = url.UserPassword(owner.Name, userPassword)
|
||||
|
||||
doGitClone(dstPath, u)(t)
|
||||
|
||||
_, _, err := git.NewCommand(git.DefaultContext, "tag", "v-1.1", "-m", "force update", "--force").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "tag", "v-1.1", "-m", "force update v2", "--force").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "--tags").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "the tag already exists in the remote")
|
||||
|
||||
_, _, err = git.NewCommand(git.DefaultContext, "push", "--tags", "--force").RunStdString(&git.RunOpts{Dir: dstPath})
|
||||
require.NoError(t, err)
|
||||
req := NewRequestf(t, "GET", "/%s/releases/tag/v-1.1", repo.FullName())
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
tagsTab := htmlDoc.Find(".release-list-title")
|
||||
assert.Contains(t, tagsTab.Text(), "force update v2")
|
||||
})
|
||||
})
|
||||
|
||||
// Cleanup
|
||||
releases, err := db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{
|
||||
IncludeTags: true,
|
||||
|
|
Loading…
Reference in New Issue