Compare commits

...

77 Commits

Author SHA1 Message Date
Andrey Nering
0904e5b9b4 [WIP] Add watch button on issue 2017-03-27 21:15:12 -03:00
Andrey Nering
9c267a071a Create issue_watch table 2017-03-27 20:12:58 -03:00
Andrey Nering
6a451a2b59 Minor improvements on commit graph UI (#1380)
* Make "Commit Graph" link a button and translate it

* Add title on Commit Graph page
2017-03-24 10:32:40 +08:00
Pavel Korovin
ae9b02b079 Fix FCGI (over TCP) support (#1368) 2017-03-23 15:57:43 +08:00
Philippe Kueck
fe94032f74 rewrite pre-commit, post-commit and options hooks (fixes #1250) (#1257)
* issue #1250, replace {pre,post}-receive and update hooks with a single shell script that does not require custom hooks to be a sh-script

* issue #1250, make script posix compilant

* v23, add migration script to update {pre,post}-receive and update hooks

* migration: use a more common name and rename v23 to v26 to avoid conflicts

* gofmt'ed and added copyright header

* fix SyncRepositoryHooks to also sync wiki repos
2017-03-23 09:12:51 +08:00
Lunny Xiao
d330a23ce1 fix migration failed when authorized_keys is not exist (#1180) 2017-03-23 00:04:31 +08:00
Kim "BKC" Carlbäcker
d349f059af Cleaner IMO (#1361) 2017-03-22 22:30:40 +08:00
Jonas
bbbd08edc1 Changed text when password reset disabled. (#1364)
Fixes #1340
2017-03-22 22:29:29 +08:00
aloupfor
1b1b85439e Remove href on first/last link when on first/last page (#1345)
Signed-off-by: Arthur Loupforest <arthur.loupforest@outlook.fr>
2017-03-22 19:37:47 +08:00
Antoine GIRARD
14fe9010ae GPG commit validation (#1150)
* GPG commit validation

* Add translation

+ some little fix

* Move hash calc after retrieving of potential key + missing translation

* Add some little test
2017-03-22 18:43:54 +08:00
Fabian Zaremba
9224405155 LFS: Fix SSH authentication for trailing arguments (#1328)
Fixes #1296
2017-03-22 18:43:28 +08:00
Kim "BKC" Carlbäcker
c05bd1789c Reduce conditionals in signin/signup inner forms (#1138) 2017-03-22 00:50:56 +01:00
Jonas
16732fbfde Removed email copyright year (#1348) 2017-03-21 18:00:39 +08:00
Jonas
9a9f8fa25b Fix broken table layout (#1344) 2017-03-21 09:44:59 +08:00
Sandro Santilli
f73e734411 Run "make fmt" with go-1.6 (#1333) 2017-03-21 08:55:00 +08:00
Lunny Xiao
888dee3b5f fix delete user failed on sqlite (#1321) 2017-03-20 22:13:52 +08:00
Lunny Xiao
bd8fe49076 fix wiki bugs (#1294) 2017-03-20 21:36:19 +08:00
caiwp
dbabc35e71 Delete the useless code (#1335) 2017-03-20 21:17:07 +08:00
Sandro Santilli
15f5d8e794 Reduce conditionals in signin/signup inner forms
by always using SignInLink and SignUpLink in the form action
2017-03-20 09:40:42 +01:00
Fabian Zaremba
5586445207 LFS: Return 404 for unimplemented endpoints (#1330)
Without this patch a 401 is returned for unspecified endpoints, making
the LFS client ask for HTTP credentials. This behaviour was introduced
with the new locking API:

https://github.com/git-lfs/git-lfs/blob/master/docs/api/locking.md
2017-03-20 16:40:20 +08:00
Sandro Santilli
9182a35f18 Show user OpenID URIs in their profile (#1314) 2017-03-20 09:31:08 +01:00
Sandro Santilli
e1586898b2 Add change-password admin command (#1304)
* Add change-password admin command
2017-03-20 09:23:38 +01:00
Sandro Santilli
97ee88975a Add captcha support to OpenID based signup 2017-03-20 08:58:00 +01:00
Sandro Santilli
f00a4c8078 Gitea OpenID-2.0 login has been tested with livejournal.com too 2017-03-20 08:51:56 +01:00
Sandro Santilli
a9de85d31d Improve style of user OpenID setting page (#1324)
* Improve user openid list CSS and regenerate CSS
2017-03-20 08:51:33 +01:00
Lunny Xiao
608cd54a68 fix gpg wrong column types 2017-03-20 02:55:07 +01:00
Sandro Santilli
430cc4f42a Use readonly input form to show the validated OpenID URI (#1308) 2017-03-20 09:50:24 +08:00
Lunny Xiao
8bcb643a03 remove empty file (#1326) 2017-03-19 21:43:04 +08:00
Sandro Santilli
2aad4a5f97 Use font-awesome OpenID icon more (#1320) 2017-03-19 16:25:24 +08:00
Patrick G
b57b0c6e40 Fix inconsistency in layout (#1316) 2017-03-18 19:00:09 +08:00
Bo-Yi Wu
1c3bd436cc feat: Only use issue and wiki on repo. (#1297) 2017-03-18 18:59:07 +08:00
Sandro Santilli
5ecb369dac Specify that time interval units are seconds (#1311) 2017-03-18 11:10:30 +08:00
Sandro Santilli
71d16f69ff Login via OpenID-2.0 (#618) 2017-03-17 15:16:08 +01:00
Kim "BKC" Carlbäcker
0693fbfc00 Refactor repo/issue/view_content.tmpl (#1276) 2017-03-17 13:57:43 +08:00
Kim "BKC" Carlbäcker
7a81cd16c5 Refactor admin/auth/new.tmpl (#1277) 2017-03-17 13:55:04 +08:00
Lunny Xiao
ebbcf6fe12 Allow push to init a wiki repo (#1279)
* allow push to init a wiki repo

* compact error check
2017-03-17 12:59:42 +08:00
Lunny Xiao
3cf0e513e6 fix wrong log when push to a new branch (#1220) 2017-03-17 10:15:00 +08:00
Antoine GIRARD
925b252927 Fix missing less sources for oauth (#1288) 2017-03-17 08:49:24 +08:00
Patrick G
1476bf909e Make wording of commit search more clear 2017-03-17 01:34:35 +01:00
Fabian Zaremba
f1d2f16b54 Add notice that LFS mirroring is not supported (#1251)
* Add notice that LFS mirroring is not supported

* Drop German translation
2017-03-16 19:33:22 +08:00
Sandro Santilli
447c9b428f Send notifications to partecipants in issue comments (#1217)
* Send notifications to partecipants in issue comments

Closes #1216

Includes test (still failing)

* Do not include "labelers" to participants

Fix test to expect what GetParticipants return
2017-03-16 09:34:24 +08:00
Antoine GIRARD
ca1c3f1926 Implement GPG api (#710)
* Implement GPG API

* Better handle error

* Apply review recommendation + simplify database operations

* Remove useless comments
2017-03-16 09:27:35 +08:00
Andrey Nering
43c5469f81 Merge pull request #1258 from ethantkoenig/fix/access
Fix nil-dereference bug
2017-03-15 21:21:57 -03:00
Patrick G
efbb895ebe Cleaner ui for admin, repo settings, and user settings page (#1269) (#1270) 2017-03-15 23:39:38 +01:00
Patrick G
03d79983ee Cleaner UI for explore page (#1253) (#1255)
* Take navbar out of column
* Add styles to navbar
* Changed navbar classes
* Remove unneeded !important from index.css
* Remove unneeded !important from _explore.less
2017-03-15 19:55:12 +01:00
Ethan Koenig
17f403fbcd Fix nil-dereference bug 2017-03-15 14:09:44 -04:00
Thomas Boerger
780cb692d6 Make sure both scripts/ can live side by side (#1264) 2017-03-15 16:11:26 +01:00
Thomas Boerger
42032fdecf Synced licenses with github repo (#1246)
* Added script to download licenses from github

* Synced licenses with github repo
2017-03-15 13:30:16 +01:00
Thomas Boerger
a06c3ad2c0 Synced gitignores with github repo (#1245)
* Renamed scripts directory into contrib

* Added script to download gitignores from github

* Synced gitignores with github repo
2017-03-15 13:30:00 +01:00
Ethan Koenig
09fe4a2ae9 Batch updates for issues (#926) 2017-03-15 02:10:35 +01:00
Ethan Koenig
021904e4e6 Fix typos in models/ and modules/ (#1248) 2017-03-15 08:52:01 +08:00
Ethan Koenig
ec0ae5d50c Refactor and fix incorrect comment (#1247) 2017-03-15 08:51:46 +08:00
Sandro Santilli
7d8f9d1c46 Rename /forget_password url to /forgot_password
Also renames `forgot_password` translation key to
`forgot_password_title` and `forget_password` to
`forgot_password`

Includes entry in CHANGELOG about the breaking change
(and some markdown fixes in there)
2017-03-14 22:54:07 +01:00
Sandro Santilli
8746fb3385 Fix migration comment (#1241) 2017-03-14 14:39:02 +08:00
Patrick G
79ec33fd60 Update locale_en-US.ini (#1235) 2017-03-14 11:24:46 +08:00
Lunny Xiao
1ccdf19fae fix forget migration for wiki hooks (#1227) 2017-03-13 14:27:29 +08:00
Lunny Xiao
be5738243c fix repo settings external tracker failed and check external urls (#1215) 2017-03-13 09:41:40 +08:00
Patrick G
08aae4952b Add myself to maintainers (#1229) 2017-03-13 08:48:39 +08:00
Sandro Santilli
f0efb615c5 Add LibreJS support (#1201)
See See https://www.gnu.org/software/librejs/

Closes #1200
2017-03-12 22:50:20 +08:00
Lunny Xiao
608bbedee1 fix 500 caused by branches settings introduced by #1198 (#1214) 2017-03-12 21:50:26 +08:00
Sandro Santilli
0475e7351f Reserve the "explore" user/org name (#1222) 2017-03-12 09:39:38 +08:00
Mura Li
bdcc1a23e0 Add integration test for signup (#1135)
* Add integration test for signup

* Remove unused functions

* Refactoring

* Add repo_create_test.go

* Rollback the incomplete repo create test

* Comply with linter requirements and simplify the code a little bit
2017-03-11 22:30:29 +08:00
Sandro Santilli
8a98a25d8e Show a link to password reset from password change and delete account (#862)
It's helpful when you forgot your password thus cannot change it
(can happen if you log in via OAuth2 or OpenID)

Also make sure that both the delete-account and password-change
links to forgot-password will have the primary email pre-filled
2017-03-11 17:11:54 +08:00
Ethan Koenig
c99e7e1a62 Simplify RepositoryList.loadAttributes() (#1211) 2017-03-11 16:50:12 +08:00
Ethan Koenig
3803f257fb Move user_follow to separate file (#1210)
Also add unit tests
2017-03-11 16:46:53 +08:00
Ethan Koenig
1e3548b7e7 Unit tests for issue_list (#1209) 2017-03-11 16:46:23 +08:00
Patrick G
64214a9426 Search bar fixes for #1187 and #1205 (#1207) 2017-03-11 16:45:15 +08:00
Philippe Kueck
e2b2fd6e78 fix #1189, commit messages containing a pipe (#1203) 2017-03-11 12:01:38 +08:00
Patrick G
ccc15b9e1a Make strings translatable (#1188) (#1198)
* Add strings to ini

* Make strings translatable (#1188)
2017-03-11 09:28:25 +08:00
Lunny Xiao
74cde12677 Fix upgrade failed after ever rollback (#1194)
* fix upgrade failed after rollback

* fix upgrade failed after rollback

* fix possible upgrade twice fail after rollback
2017-03-10 23:43:51 +08:00
Lunny Xiao
f5476bdbb1 bug fixed for delete repo failed (#1193) 2017-03-10 22:05:53 +08:00
Lunny Xiao
656efdc1c7 fix ini format incomiptable with crowdin (#1177) 2017-03-10 19:40:25 +08:00
mcheng89
dbcd452758 rename OSX to macOS (#1176) 2017-03-10 16:20:17 +08:00
atzoum
05f0c4bbf5 Dockerfile for aarch64 (#1128) (#1130) 2017-03-10 10:02:55 +08:00
Patrick G
5463640fe6 Fix double borders on edit page (#1152) (#1153)
* Fix double borders on edit page (#1152)

* Add border modifications to less file
2017-03-10 09:33:35 +08:00
esell
4e716fb0fa add mssql to app.ini db config comment (#1172) 2017-03-10 09:33:19 +08:00
esell
d87596aec4 Add MSSQL to issues template (#1171) 2017-03-10 09:31:57 +08:00
689 changed files with 34191 additions and 5473 deletions

View File

@@ -11,6 +11,7 @@
- Database (use `[x]`):
- [ ] PostgreSQL
- [ ] MySQL
- [ ] MSSQL
- [ ] SQLite
- Can you reproduce the bug at https://try.gitea.io:
- [ ] Yes (provide example URL)

View File

@@ -1,5 +1,10 @@
# Changelog
## Unreleased
* BREAKING
* Password reset URL changed from `/user/forget_password` to `/user/forgot_password`
## [1.1.0](https://github.com/go-gitea/gitea/releases/tag/v1.1.0) - 2017-03-09
* BREAKING
@@ -72,7 +77,7 @@
* Added option to config to disable local path imports [#724](https://github.com/go-gitea/gitea/pull/724)
* Allow custom public files [#782](https://github.com/go-gitea/gitea/pull/782)
* Added pprof endpoint for debugging [#801](https://github.com/go-gitea/gitea/pull/801)
* Added X-GitHub-* headers [#809](https://github.com/go-gitea/gitea/pull/809)
* Added `X-GitHub-*` headers [#809](https://github.com/go-gitea/gitea/pull/809)
* Fill SSH key title automatically [#863](https://github.com/go-gitea/gitea/pull/863)
* Display Git version on admin panel [#921](https://github.com/go-gitea/gitea/pull/921)
* Expose URL field on issue API [#982](https://github.com/go-gitea/gitea/pull/982)
@@ -104,7 +109,7 @@
## [1.0.1](https://github.com/go-gitea/gitea/releases/tag/v1.0.1) - 2017-01-05
* BUGFIXES
* Fixed localized MIN_PASSWORD_LENGTH [#501](https://github.com/go-gitea/gitea/pull/501)
* Fixed localized `MIN_PASSWORD_LENGTH` [#501](https://github.com/go-gitea/gitea/pull/501)
* Fixed 500 error on organization delete [#507](https://github.com/go-gitea/gitea/pull/507)
* Ignore empty wiki repo on migrate [#544](https://github.com/go-gitea/gitea/pull/544)
* Proper check access for forking [#563](https://github.com/go-gitea/gitea/pull/563)

43
Dockerfile.aarch64 Normal file
View File

@@ -0,0 +1,43 @@
FROM aarch64/alpine:3.5
EXPOSE 22 3000
RUN apk update && \
apk add \
su-exec \
ca-certificates \
sqlite \
bash \
git \
linux-pam \
s6 \
curl \
openssh \
tzdata && \
rm -rf \
/var/cache/apk/* && \
addgroup \
-S -g 1000 \
git && \
adduser \
-S -H -D \
-h /data/git \
-s /bin/bash \
-u 1000 \
-G git \
git && \
echo "git:$(date +%s | sha256sum | base64 | head -c 32)" | chpasswd
ENV USER git
ENV GITEA_CUSTOM /data/gitea
COPY docker /
COPY gitea /app/gitea/gitea
ENV GODEBUG=netdns=go
VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/entrypoint"]
CMD ["/bin/s6-svscan", "/etc/s6"]

View File

@@ -12,3 +12,4 @@ Rémy Boulanouar <admin@dblk.org> (@DblK)
Sandro Santilli <strk@kbt.io> (@strk)
Thibault Meyer <meyer.thibault@gmail.com> (@0xbaadf00d)
Thomas Boerger <thomas@webhippie.de> (@tboerger)
Patrick G <geek1011@outlook.com> (@geek1011)

View File

@@ -10,12 +10,11 @@
[![GoDoc](https://godoc.org/code.gitea.io/gitea?status.svg)](https://godoc.org/code.gitea.io/gitea)
[![Release](https://github-release-version.herokuapp.com/github/go-gitea/gitea/release.svg?style=flat)](https://github.com/go-gitea/gitea/releases/latest)
||||
|:-------------:|:-------:|:-------:|
| | | |
|:---:|:---:|:---:|
|![Dashboard](https://i.imgur.com/3iEQsux.jpg)|![Repository](https://i.imgur.com/glqFnj8.jpg)|![Commits History](https://i.imgur.com/ad1FEpi.jpg)|
|![Profile](https://i.imgur.com/q81EcGa.jpg)|![Admin Dashboard](https://i.imgur.com/L2CQeN0.jpg)|![Diff](https://i.imgur.com/cNuvMum.jpg)|
|![Issues](https://i.imgur.com/xCYRqaF.jpg)|![Releases](https://i.imgur.com/ILpRBCe.jpg)|![Organization](https://i.imgur.com/0BHnrcL.jpg)|
||||
## Purpose

View File

@@ -10,12 +10,11 @@
[![GoDoc](https://godoc.org/code.gitea.io/gitea?status.svg)](https://godoc.org/code.gitea.io/gitea)
[![Release](https://github-release-version.herokuapp.com/github/go-gitea/gitea/release.svg?style=flat)](https://github.com/go-gitea/gitea/releases/latest)
||||
|:-------------:|:-------:|:-------:|
| | | |
|:---:|:---:|:---:|
|![Dashboard](https://i.imgur.com/3iEQsux.jpg)|![Repository](https://i.imgur.com/glqFnj8.jpg)|![Commits History](https://i.imgur.com/ad1FEpi.jpg)|
|![Profile](https://i.imgur.com/q81EcGa.jpg)|![Admin Dashboard](https://i.imgur.com/L2CQeN0.jpg)|![Diff](https://i.imgur.com/cNuvMum.jpg)|
|![Issues](https://i.imgur.com/xCYRqaF.jpg)|![Releases](https://i.imgur.com/ILpRBCe.jpg)|![Organization](https://i.imgur.com/0BHnrcL.jpg)|
||||
## 目标

View File

@@ -23,6 +23,7 @@ var (
to make automatic initialization process more smoothly`,
Subcommands: []cli.Command{
subcmdCreateUser,
subcmdChangePassword,
},
}
@@ -57,8 +58,59 @@ to make automatic initialization process more smoothly`,
},
},
}
subcmdChangePassword = cli.Command{
Name: "change-password",
Usage: "Change a user's password",
Action: runChangePassword,
Flags: []cli.Flag{
cli.StringFlag{
Name: "username,u",
Value: "",
Usage: "The user to change password for",
},
cli.StringFlag{
Name: "password,p",
Value: "",
Usage: "New password to set for user",
},
},
}
)
func runChangePassword(c *cli.Context) error {
if !c.IsSet("password") {
return fmt.Errorf("Password is not specified")
} else if !c.IsSet("username") {
return fmt.Errorf("Username is not specified")
}
setting.NewContext()
models.LoadConfigs()
setting.NewXORMLogService(false)
if err := models.SetEngine(); err != nil {
return fmt.Errorf("models.SetEngine: %v", err)
}
uname := c.String("username")
user, err := models.GetUserByName(uname)
if err != nil {
return fmt.Errorf("%v", err)
}
user.Passwd = c.String("password")
if user.Salt, err = models.GetUserSalt(); err != nil {
return fmt.Errorf("%v", err)
}
user.EncodePasswd()
if err := models.UpdateUser(user); err != nil {
return fmt.Errorf("%v", err)
}
fmt.Printf("User '%s' password has been successfully updated!\n", uname)
return nil
}
func runCreateUser(c *cli.Context) error {
if !c.IsSet("name") {
return fmt.Errorf("Username is not specified")

View File

@@ -123,8 +123,8 @@ func runServ(c *cli.Context) error {
fail("Unknown git command", "LFS authentication request over SSH denied, LFS support is disabled")
}
if strings.Contains(args, " ") {
argsSplit := strings.SplitN(args, " ", 2)
argsSplit := strings.Split(args, " ")
if len(argsSplit) >= 2 {
args = strings.TrimSpace(argsSplit[0])
lfsVerb = strings.TrimSpace(argsSplit[1])
}
@@ -179,8 +179,10 @@ func runServ(c *cli.Context) error {
if verb == lfsAuthenticateVerb {
if lfsVerb == "upload" {
requestedMode = models.AccessModeWrite
} else {
} else if lfsVerb == "download" {
requestedMode = models.AccessModeRead
} else {
fail("Unknown LFS verb", "Unkown lfs verb %s", lfsVerb)
}
}
@@ -232,7 +234,7 @@ func runServ(c *cli.Context) error {
fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err)
}
mode, err := models.AccessLevel(user, repo)
mode, err := models.AccessLevel(user.ID, repo)
if err != nil {
fail("Internal error", "Failed to check access: %v", err)
} else if mode < requestedMode {
@@ -296,6 +298,12 @@ func runServ(c *cli.Context) error {
gitcmd = exec.Command(verb, repoPath)
}
if isWiki {
if err = repo.InitWiki(); err != nil {
fail("Internal error", "Failed to init wiki repo: %v", err)
}
}
os.Setenv(models.ProtectedBranchRepoID, fmt.Sprintf("%d", repo.ID))
gitcmd.Dir = setting.RepoRootPath

View File

@@ -200,6 +200,19 @@ func runWeb(ctx *cli.Context) error {
m.Group("/user", func() {
m.Get("/login", user.SignIn)
m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)
if setting.EnableOpenIDSignIn {
m.Combo("/login/openid").
Get(user.SignInOpenID).
Post(bindIgnErr(auth.SignInOpenIDForm{}), user.SignInOpenIDPost)
m.Group("/openid", func() {
m.Combo("/connect").
Get(user.ConnectOpenID).
Post(bindIgnErr(auth.ConnectOpenIDForm{}), user.ConnectOpenIDPost)
m.Combo("/register").
Get(user.RegisterOpenID).
Post(bindIgnErr(auth.SignUpOpenIDForm{}), user.RegisterOpenIDPost)
})
}
m.Get("/sign_up", user.SignUp)
m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
m.Get("/reset_password", user.ResetPasswd)
@@ -230,6 +243,15 @@ func runWeb(ctx *cli.Context) error {
m.Post("/email/delete", user.DeleteEmail)
m.Get("/password", user.SettingsPassword)
m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
if setting.EnableOpenIDSignIn {
m.Group("/openid", func() {
m.Combo("").Get(user.SettingsOpenID).
Post(bindIgnErr(auth.AddOpenIDForm{}), user.SettingsOpenIDPost)
m.Post("/delete", user.DeleteOpenID)
m.Post("/toggle_visibility", user.ToggleOpenIDVisibility)
})
}
m.Combo("/ssh").Get(user.SettingsSSHKeys).
Post(bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
m.Post("/ssh/delete", user.DeleteSSHKey)
@@ -254,8 +276,8 @@ func runWeb(ctx *cli.Context) error {
m.Any("/activate", user.Activate)
m.Any("/activate_email", user.ActivateEmail)
m.Get("/email2user", user.Email2User)
m.Get("/forget_password", user.ForgotPasswd)
m.Post("/forget_password", user.ForgotPasswdPost)
m.Get("/forgot_password", user.ForgotPasswd)
m.Post("/forgot_password", user.ForgotPasswdPost)
m.Get("/logout", user.SignOut)
})
// ***** END: User *****
@@ -427,7 +449,7 @@ func runWeb(ctx *cli.Context) error {
m.Combo("").Get(repo.ProtectedBranch).Post(repo.ProtectedBranchPost)
m.Post("/can_push", repo.ChangeProtectedBranch)
m.Post("/delete", repo.DeleteProtectedBranch)
})
}, repo.MustBeNotBare)
m.Group("/hooks", func() {
m.Get("", repo.Webhooks)
@@ -466,17 +488,17 @@ func runWeb(ctx *cli.Context) error {
m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
m.Group("/:index", func() {
m.Post("/label", repo.UpdateIssueLabel)
m.Post("/milestone", repo.UpdateIssueMilestone)
m.Post("/assignee", repo.UpdateIssueAssignee)
}, reqRepoWriter)
m.Group("/:index", func() {
m.Post("/title", repo.UpdateIssueTitle)
m.Post("/content", repo.UpdateIssueContent)
m.Post("/watch", repo.IssueWatch)
m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
})
m.Post("/labels", repo.UpdateIssueLabel, reqRepoWriter)
m.Post("/milestone", repo.UpdateIssueMilestone, reqRepoWriter)
m.Post("/assignee", repo.UpdateIssueAssignee, reqRepoWriter)
m.Post("/status", repo.UpdateIssueStatus, reqRepoWriter)
})
m.Group("/comments/:id", func() {
m.Post("", repo.UpdateCommentContent)
@@ -500,11 +522,11 @@ func runWeb(ctx *cli.Context) error {
m.Get("/new", repo.NewRelease)
m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
m.Post("/delete", repo.DeleteRelease)
}, reqRepoWriter, context.RepoRef())
}, repo.MustBeNotBare, reqRepoWriter, context.RepoRef())
m.Group("/releases", func() {
m.Get("/edit/*", repo.EditRelease)
m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
}, reqRepoWriter, func(ctx *context.Context) {
}, repo.MustBeNotBare, reqRepoWriter, func(ctx *context.Context) {
var err error
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
if err != nil {
@@ -543,17 +565,17 @@ func runWeb(ctx *cli.Context) error {
return
}
})
}, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) {
}, repo.MustBeNotBare, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) {
if !ctx.Repo.Repository.CanEnableEditor() || ctx.Repo.IsViewCommit {
ctx.Handle(404, "", nil)
return
}
})
}, reqSignIn, context.RepoAssignment(), repo.MustBeNotBare, context.UnitTypes())
}, reqSignIn, context.RepoAssignment(), context.UnitTypes())
m.Group("/:username/:reponame", func() {
m.Group("", func() {
m.Get("/releases", repo.Releases)
m.Get("/releases", repo.MustBeNotBare, repo.Releases)
m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues)
m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue)
m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
@@ -561,7 +583,7 @@ func runWeb(ctx *cli.Context) error {
}, context.RepoRef())
// m.Get("/branches", repo.Branches)
m.Post("/branches/:name/delete", reqSignIn, reqRepoWriter, repo.DeleteBranchPost)
m.Post("/branches/:name/delete", reqSignIn, reqRepoWriter, repo.MustBeNotBare, repo.DeleteBranchPost)
m.Group("/wiki", func() {
m.Get("/?:page", repo.Wiki)
@@ -581,7 +603,7 @@ func runWeb(ctx *cli.Context) error {
m.Get("/*", repo.WikiRaw)
}, repo.MustEnableWiki)
m.Get("/archive/*", repo.Download)
m.Get("/archive/*", repo.MustBeNotBare, repo.Download)
m.Group("/pulls/:index", func() {
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
@@ -597,10 +619,10 @@ func runWeb(ctx *cli.Context) error {
m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.Diff)
m.Get("/forks", repo.Forks)
}, context.RepoRef())
m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.RawDiff)
m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.MustBeNotBare, repo.RawDiff)
m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.CompareDiff)
}, ignSignIn, context.RepoAssignment(), repo.MustBeNotBare, context.UnitTypes())
m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.MustBeNotBare, repo.CompareDiff)
}, ignSignIn, context.RepoAssignment(), context.UnitTypes())
m.Group("/:username/:reponame", func() {
m.Get("/stars", repo.Stars)
m.Get("/watchers", repo.Watchers)
@@ -610,7 +632,7 @@ func runWeb(ctx *cli.Context) error {
m.Group("/:reponame", func() {
m.Get("", repo.SetEditorconfigIfExists, repo.Home)
m.Get("\\.git$", repo.SetEditorconfigIfExists, repo.Home)
}, ignSignIn, context.RepoAssignment(true), context.RepoRef(), context.UnitTypes())
}, ignSignIn, context.RepoAssignment(), context.RepoRef(), context.UnitTypes())
m.Group("/:reponame", func() {
m.Group("/info/lfs", func() {
@@ -618,6 +640,9 @@ func runWeb(ctx *cli.Context) error {
m.Get("/objects/:oid/:filename", lfs.ObjectOidHandler)
m.Any("/objects/:oid", lfs.ObjectOidHandler)
m.Post("/objects", lfs.PostHandler)
m.Any("/*", func(ctx *context.Context) {
ctx.Handle(404, "", nil)
})
}, ignSignInAndCsrf)
m.Any("/*", ignSignInAndCsrf, repo.HTTP)
m.Head("/tasks/trigger", repo.TriggerTask)
@@ -677,7 +702,12 @@ func runWeb(ctx *cli.Context) error {
case setting.HTTPS:
err = runHTTPS(listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m))
case setting.FCGI:
err = fcgi.Serve(nil, context2.ClearHandler(m))
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
log.Fatal(4, "Failed to bind %s", listenAddr, err)
}
defer listener.Close()
err = fcgi.Serve(listener, context2.ClearHandler(m))
case setting.UnixSocket:
if err := os.Remove(listenAddr); err != nil && !os.IsNotExist(err) {
log.Fatal(4, "Failed to remove unix socket directory %s: %v", listenAddr, err)

39
conf/app.ini vendored
View File

@@ -147,7 +147,7 @@ RSA = 2048
DSA = 1024
[database]
; Either "mysql", "postgres" or "sqlite3", it's your choice
; Either "mysql", "postgres", "mssql" or "sqlite3", it's your choice
DB_TYPE = mysql
HOST = 127.0.0.1:3306
NAME = gitea
@@ -182,6 +182,39 @@ MIN_PASSWORD_LENGTH = 6
; True when users are allowed to import local server paths
IMPORT_LOCAL_PATHS = false
[openid]
;
; OpenID is an open standard and decentralized authentication protocol.
; Your identity is the address of a webpage you provide, which describes
; how to prove you are in control of that page.
;
; For more info: https://en.wikipedia.org/wiki/OpenID
;
; Current implementation supports OpenID-2.0
;
; Tested to work providers at the time of writing:
; - Any GNUSocial node (your.hostname.tld/username)
; - Any SimpleID provider (http://simpleid.koinic.net)
; - http://openid.org.cn/
; - openid.stackexchange.com
; - login.launchpad.net
; - <username>.livejournal.com
;
; Whether to allow signin in via OpenID
ENABLE_OPENID_SIGNIN = true
; Whether to allow registering via OpenID
ENABLE_OPENID_SIGNUP = true
; Allowed URI patterns (POSIX regexp).
; Space separated.
; Only these would be allowed if non-blank.
; Example value: trusted.domain.org trusted.domain.net
WHITELISTED_URIS =
; Forbidden URI patterns (POSIX regexp).
; Space sepaated.
; Only used if WHITELISTED_URIS is blank.
; Example value: loadaverage.org/badguy stackexchange.com/.*spammer
BLACKLISTED_URIS =
[service]
ACTIVE_CODE_LIVE_MINUTES = 180
RESET_PASSWD_CODE_LIVE_MINUTES = 180
@@ -274,9 +307,9 @@ COOKIE_NAME = i_like_gitea
COOKIE_SECURE = false
; Enable set cookie, default is true
ENABLE_SET_COOKIE = true
; Session GC time interval, default is 86400
; Session GC time interval in seconds, default is 86400 (1 day)
GC_INTERVAL_TIME = 86400
; Session life time, default is 86400
; Session life time in seconds, default is 86400 (1 day)
SESSION_LIFE_TIME = 86400
[picture]

View File

@@ -6,9 +6,11 @@ package utils
import (
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
@@ -123,3 +125,32 @@ func (t *T) RunTest(tests ...func(*T) error) (err error) {
// Note that the return value 'err' may be updated by the 'defer' statement before despite it's returning nil here.
return nil
}
// GetAndPost provides a convenient helper function for testing an HTTP endpoint with GET and POST method.
// The function sends GET first and then POST with the given form.
func GetAndPost(url string, form map[string][]string) error {
var err error
var r *http.Response
r, err = http.Get(url)
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode != http.StatusOK {
return fmt.Errorf("GET '%s': %s", url, r.Status)
}
r, err = http.PostForm(url, form)
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode != http.StatusOK {
return fmt.Errorf("POST '%s': %s", url, r.Status)
}
return nil
}

View File

@@ -0,0 +1,35 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package integration
import (
"os"
"testing"
"code.gitea.io/gitea/integrations/internal/utils"
)
var signupFormSample map[string][]string = map[string][]string{
"Name": {"tester"},
"Email": {"user1@example.com"},
"Passwd": {"12345678"},
}
func signup(t *utils.T) error {
return utils.GetAndPost("http://:"+ServerHTTPPort+"/user/sign_up", signupFormSample)
}
func TestSignup(t *testing.T) {
conf := utils.Config{
Program: "../gitea",
WorkDir: "",
Args: []string{"web", "--port", ServerHTTPPort},
LogFile: os.Stderr,
}
if err := utils.New(t, &conf).RunTest(install, signup); err != nil {
t.Fatal(err)
}
}

View File

@@ -59,21 +59,21 @@ type Access struct {
Mode AccessMode
}
func accessLevel(e Engine, user *User, repo *Repository) (AccessMode, error) {
func accessLevel(e Engine, userID int64, repo *Repository) (AccessMode, error) {
mode := AccessModeNone
if !repo.IsPrivate {
mode = AccessModeRead
}
if user == nil {
if userID == 0 {
return mode, nil
}
if user.ID == repo.OwnerID {
if userID == repo.OwnerID {
return AccessModeOwner, nil
}
a := &Access{UserID: user.ID, RepoID: repo.ID}
a := &Access{UserID: userID, RepoID: repo.ID}
if has, err := e.Get(a); !has || err != nil {
return mode, err
}
@@ -81,19 +81,19 @@ func accessLevel(e Engine, user *User, repo *Repository) (AccessMode, error) {
}
// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
// user does not have access. User can be nil!
func AccessLevel(user *User, repo *Repository) (AccessMode, error) {
return accessLevel(x, user, repo)
// user does not have access.
func AccessLevel(userID int64, repo *Repository) (AccessMode, error) {
return accessLevel(x, userID, repo)
}
func hasAccess(e Engine, user *User, repo *Repository, testMode AccessMode) (bool, error) {
mode, err := accessLevel(e, user, repo)
func hasAccess(e Engine, userID int64, repo *Repository, testMode AccessMode) (bool, error) {
mode, err := accessLevel(e, userID, repo)
return testMode <= mode, err
}
// HasAccess returns true if someone has the request access level. User can be nil!
func HasAccess(user *User, repo *Repository, testMode AccessMode) (bool, error) {
return hasAccess(x, user, repo, testMode)
// HasAccess returns true if user has access to repo
func HasAccess(userID int64, repo *Repository, testMode AccessMode) (bool, error) {
return hasAccess(x, userID, repo, testMode)
}
type repoAccess struct {

View File

@@ -25,19 +25,19 @@ func TestAccessLevel(t *testing.T) {
repo1 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 2, IsPrivate: false}).(*Repository)
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository)
level, err := AccessLevel(user1, repo1)
level, err := AccessLevel(user1.ID, repo1)
assert.NoError(t, err)
assert.Equal(t, AccessModeOwner, level)
level, err = AccessLevel(user1, repo2)
level, err = AccessLevel(user1.ID, repo2)
assert.NoError(t, err)
assert.Equal(t, AccessModeWrite, level)
level, err = AccessLevel(user2, repo1)
level, err = AccessLevel(user2.ID, repo1)
assert.NoError(t, err)
assert.Equal(t, AccessModeRead, level)
level, err = AccessLevel(user2, repo2)
level, err = AccessLevel(user2.ID, repo2)
assert.NoError(t, err)
assert.Equal(t, AccessModeNone, level)
}
@@ -51,19 +51,19 @@ func TestHasAccess(t *testing.T) {
repo2 := AssertExistsAndLoadBean(t, &Repository{OwnerID: 3, IsPrivate: true}).(*Repository)
for _, accessMode := range accessModes {
has, err := HasAccess(user1, repo1, accessMode)
has, err := HasAccess(user1.ID, repo1, accessMode)
assert.NoError(t, err)
assert.True(t, has)
has, err = HasAccess(user1, repo2, accessMode)
has, err = HasAccess(user1.ID, repo2, accessMode)
assert.NoError(t, err)
assert.Equal(t, accessMode <= AccessModeWrite, has)
has, err = HasAccess(user2, repo1, accessMode)
has, err = HasAccess(user2.ID, repo1, accessMode)
assert.NoError(t, err)
assert.Equal(t, accessMode <= AccessModeRead, has)
has, err = HasAccess(user2, repo2, accessMode)
has, err = HasAccess(user2.ID, repo2, accessMode)
assert.NoError(t, err)
assert.Equal(t, accessMode <= AccessModeNone, has)
}

View File

@@ -360,7 +360,7 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) err
issue, err := GetIssueByRef(ref)
if err != nil {
if IsErrIssueNotExist(err) {
if IsErrIssueNotExist(err) || err == errMissingIssueNumber {
continue
}
return err

View File

@@ -57,7 +57,7 @@ func GetProtectedBranchBy(repoID int64, BranchName string) (*ProtectedBranch, er
return rel, nil
}
// GetProtectedBranches get all protected btanches
// GetProtectedBranches get all protected branches
func (repo *Repository) GetProtectedBranches() ([]*ProtectedBranch, error) {
protectedBranches := make([]*ProtectedBranch, 0)
return protectedBranches, x.Find(&protectedBranches, &ProtectedBranch{RepoID: repo.ID})

View File

@@ -93,6 +93,21 @@ func (err ErrEmailAlreadyUsed) Error() string {
return fmt.Sprintf("e-mail has been used [email: %s]", err.Email)
}
// ErrOpenIDAlreadyUsed represents a "OpenIDAlreadyUsed" kind of error.
type ErrOpenIDAlreadyUsed struct {
OpenID string
}
// IsErrOpenIDAlreadyUsed checks if an error is a ErrOpenIDAlreadyUsed.
func IsErrOpenIDAlreadyUsed(err error) bool {
_, ok := err.(ErrOpenIDAlreadyUsed)
return ok
}
func (err ErrOpenIDAlreadyUsed) Error() string {
return fmt.Sprintf("OpenID has been used [oid: %s]", err.OpenID)
}
// ErrUserOwnRepos represents a "UserOwnRepos" kind of error.
type ErrUserOwnRepos struct {
UID int64
@@ -245,6 +260,54 @@ func (err ErrKeyNameAlreadyUsed) Error() string {
return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name)
}
// ErrGPGKeyNotExist represents a "GPGKeyNotExist" kind of error.
type ErrGPGKeyNotExist struct {
ID int64
}
// IsErrGPGKeyNotExist checks if an error is a ErrGPGKeyNotExist.
func IsErrGPGKeyNotExist(err error) bool {
_, ok := err.(ErrGPGKeyNotExist)
return ok
}
func (err ErrGPGKeyNotExist) Error() string {
return fmt.Sprintf("public gpg key does not exist [id: %d]", err.ID)
}
// ErrGPGKeyIDAlreadyUsed represents a "GPGKeyIDAlreadyUsed" kind of error.
type ErrGPGKeyIDAlreadyUsed struct {
KeyID string
}
// IsErrGPGKeyIDAlreadyUsed checks if an error is a ErrKeyNameAlreadyUsed.
func IsErrGPGKeyIDAlreadyUsed(err error) bool {
_, ok := err.(ErrGPGKeyIDAlreadyUsed)
return ok
}
func (err ErrGPGKeyIDAlreadyUsed) Error() string {
return fmt.Sprintf("public key already exists [key_id: %s]", err.KeyID)
}
// ErrGPGKeyAccessDenied represents a "GPGKeyAccessDenied" kind of Error.
type ErrGPGKeyAccessDenied struct {
UserID int64
KeyID int64
}
// IsErrGPGKeyAccessDenied checks if an error is a ErrGPGKeyAccessDenied.
func IsErrGPGKeyAccessDenied(err error) bool {
_, ok := err.(ErrGPGKeyAccessDenied)
return ok
}
// Error pretty-prints an error of type ErrGPGKeyAccessDenied.
func (err ErrGPGKeyAccessDenied) Error() string {
return fmt.Sprintf("user does not have access to the key [user_id: %d, key_id: %d]",
err.UserID, err.KeyID)
}
// ErrKeyAccessDenied represents a "KeyAccessDenied" kind of error.
type ErrKeyAccessDenied struct {
UserID int64

View File

@@ -9,8 +9,8 @@ import "github.com/markbates/goth"
// ExternalLoginUser makes the connecting between some existing user and additional external login sources
type ExternalLoginUser struct {
ExternalID string `xorm:"NOT NULL"`
UserID int64 `xorm:"NOT NULL"`
LoginSourceID int64 `xorm:"NOT NULL"`
UserID int64 `xorm:"NOT NULL"`
LoginSourceID int64 `xorm:"NOT NULL"`
}
// GetExternalLogin checks if a externalID in loginSourceID scope already exists
@@ -67,8 +67,8 @@ func RemoveAccountLink(user *User, loginSourceID int64) (int64, error) {
return deleted, err
}
// RemoveAllAccountLinks will remove all external login sources for the given user
func RemoveAllAccountLinks(user *User) error {
_, err := x.Delete(&ExternalLoginUser{UserID: user.ID})
// removeAllAccountLinks will remove all external login sources for the given user
func removeAllAccountLinks(e Engine, user *User) error {
_, err := e.Delete(&ExternalLoginUser{UserID: user.ID})
return err
}

View File

@@ -2,6 +2,18 @@
id: 1
type: 7 # label
poster_id: 2
issue_id: 1
issue_id: 1 # in repo_id 1
label_id: 1
content: "1"
-
id: 2
type: 0 # comment
poster_id: 3 # user not watching (see watch.yml)
issue_id: 1 # in repo_id 1
content: "good work!"
-
id: 3
type: 0 # comment
poster_id: 5 # user not watching (see watch.yml)
issue_id: 1 # in repo_id 1
content: "meh..."

View File

@@ -0,0 +1,4 @@
-
id: 1
user_id: 4
follow_id: 2

View File

@@ -8,7 +8,7 @@
content: content1
is_closed: false
is_pull: false
num_comments: 0
num_comments: 2
created_unix: 946684800
updated_unix: 978307200

View File

@@ -26,6 +26,7 @@
avatar_email: user2@example.com
num_repos: 2
num_stars: 2
num_followers: 1
-
id: 3
@@ -56,6 +57,7 @@
avatar: avatar4
avatar_email: user4@example.com
num_repos: 0
num_following: 1
-
id: 5
@@ -72,6 +74,7 @@
num_repos: 1
allow_create_organization: false
is_active: true
num_following: 0
-
id: 6

View File

@@ -0,0 +1,17 @@
-
id: 1
uid: 1
uri: https://user1.domain1.tld/
show: false
-
id: 2
uid: 1
uri: http://user1.domain2.tld/
show: true
-
id: 3
uid: 2
uri: https://domain1.tld/user2/
show: true

462
models/gpg_key.go Normal file
View File

@@ -0,0 +1,462 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"bytes"
"container/list"
"crypto"
"encoding/base64"
"fmt"
"hash"
"io"
"strings"
"time"
"code.gitea.io/git"
"code.gitea.io/gitea/modules/log"
"github.com/go-xorm/xorm"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
)
// GPGKey represents a GPG key.
type GPGKey struct {
ID int64 `xorm:"pk autoincr"`
OwnerID int64 `xorm:"INDEX NOT NULL"`
KeyID string `xorm:"INDEX CHAR(16) NOT NULL"`
PrimaryKeyID string `xorm:"CHAR(16)"`
Content string `xorm:"TEXT NOT NULL"`
Created time.Time `xorm:"-"`
CreatedUnix int64
Expired time.Time `xorm:"-"`
ExpiredUnix int64
Added time.Time `xorm:"-"`
AddedUnix int64
SubsKey []*GPGKey `xorm:"-"`
Emails []*EmailAddress
CanSign bool
CanEncryptComms bool
CanEncryptStorage bool
CanCertify bool
}
// BeforeInsert will be invoked by XORM before inserting a record
func (key *GPGKey) BeforeInsert() {
key.AddedUnix = time.Now().Unix()
key.ExpiredUnix = key.Expired.Unix()
key.CreatedUnix = key.Created.Unix()
}
// AfterSet is invoked from XORM after setting the value of a field of this object.
func (key *GPGKey) AfterSet(colName string, _ xorm.Cell) {
switch colName {
case "key_id":
x.Where("primary_key_id=?", key.KeyID).Find(&key.SubsKey)
case "added_unix":
key.Added = time.Unix(key.AddedUnix, 0).Local()
case "expired_unix":
key.Expired = time.Unix(key.ExpiredUnix, 0).Local()
case "created_unix":
key.Created = time.Unix(key.CreatedUnix, 0).Local()
}
}
// ListGPGKeys returns a list of public keys belongs to given user.
func ListGPGKeys(uid int64) ([]*GPGKey, error) {
keys := make([]*GPGKey, 0, 5)
return keys, x.Where("owner_id=? AND primary_key_id=''", uid).Find(&keys)
}
// GetGPGKeyByID returns public key by given ID.
func GetGPGKeyByID(keyID int64) (*GPGKey, error) {
key := new(GPGKey)
has, err := x.Id(keyID).Get(key)
if err != nil {
return nil, err
} else if !has {
return nil, ErrGPGKeyNotExist{keyID}
}
return key, nil
}
// checkArmoredGPGKeyString checks if the given key string is a valid GPG armored key.
// The function returns the actual public key on success
func checkArmoredGPGKeyString(content string) (*openpgp.Entity, error) {
list, err := openpgp.ReadArmoredKeyRing(strings.NewReader(content))
if err != nil {
return nil, err
}
return list[0], nil
}
//addGPGKey add key and subkeys to database
func addGPGKey(e Engine, key *GPGKey) (err error) {
// Save GPG primary key.
if _, err = e.Insert(key); err != nil {
return err
}
// Save GPG subs key.
for _, subkey := range key.SubsKey {
if err := addGPGKey(e, subkey); err != nil {
return err
}
}
return nil
}
// AddGPGKey adds new public key to database.
func AddGPGKey(ownerID int64, content string) (*GPGKey, error) {
ekey, err := checkArmoredGPGKeyString(content)
if err != nil {
return nil, err
}
// Key ID cannot be duplicated.
has, err := x.Where("key_id=?", ekey.PrimaryKey.KeyIdString()).
Get(new(GPGKey))
if err != nil {
return nil, err
} else if has {
return nil, ErrGPGKeyIDAlreadyUsed{ekey.PrimaryKey.KeyIdString()}
}
//Get DB session
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return nil, err
}
key, err := parseGPGKey(ownerID, ekey)
if err != nil {
return nil, err
}
if err = addGPGKey(sess, key); err != nil {
return nil, err
}
return key, sess.Commit()
}
//base64EncPubKey encode public kay content to base 64
func base64EncPubKey(pubkey *packet.PublicKey) (string, error) {
var w bytes.Buffer
err := pubkey.Serialize(&w)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(w.Bytes()), nil
}
//parseSubGPGKey parse a sub Key
func parseSubGPGKey(ownerID int64, primaryID string, pubkey *packet.PublicKey, expiry time.Time) (*GPGKey, error) {
content, err := base64EncPubKey(pubkey)
if err != nil {
return nil, err
}
return &GPGKey{
OwnerID: ownerID,
KeyID: pubkey.KeyIdString(),
PrimaryKeyID: primaryID,
Content: content,
Created: pubkey.CreationTime,
Expired: expiry,
CanSign: pubkey.CanSign(),
CanEncryptComms: pubkey.PubKeyAlgo.CanEncrypt(),
CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
CanCertify: pubkey.PubKeyAlgo.CanSign(),
}, nil
}
//parseGPGKey parse a PrimaryKey entity (primary key + subs keys + self-signature)
func parseGPGKey(ownerID int64, e *openpgp.Entity) (*GPGKey, error) {
pubkey := e.PrimaryKey
//Extract self-sign for expire date based on : https://github.com/golang/crypto/blob/master/openpgp/keys.go#L165
var selfSig *packet.Signature
for _, ident := range e.Identities {
if selfSig == nil {
selfSig = ident.SelfSignature
} else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId {
selfSig = ident.SelfSignature
break
}
}
expiry := time.Time{}
if selfSig.KeyLifetimeSecs != nil {
expiry = selfSig.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs) * time.Second)
}
//Parse Subkeys
subkeys := make([]*GPGKey, len(e.Subkeys))
for i, k := range e.Subkeys {
subs, err := parseSubGPGKey(ownerID, pubkey.KeyIdString(), k.PublicKey, expiry)
if err != nil {
return nil, err
}
subkeys[i] = subs
}
//Check emails
userEmails, err := GetEmailAddresses(ownerID)
if err != nil {
return nil, err
}
emails := make([]*EmailAddress, len(e.Identities))
n := 0
for _, ident := range e.Identities {
for _, e := range userEmails {
if e.Email == ident.UserId.Email && e.IsActivated {
emails[n] = e
break
}
}
if emails[n] == nil {
return nil, fmt.Errorf("Failed to found email or is not confirmed : %s", ident.UserId.Email)
}
n++
}
content, err := base64EncPubKey(pubkey)
if err != nil {
return nil, err
}
return &GPGKey{
OwnerID: ownerID,
KeyID: pubkey.KeyIdString(),
PrimaryKeyID: "",
Content: content,
Created: pubkey.CreationTime,
Expired: expiry,
Emails: emails,
SubsKey: subkeys,
CanSign: pubkey.CanSign(),
CanEncryptComms: pubkey.PubKeyAlgo.CanEncrypt(),
CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
CanCertify: pubkey.PubKeyAlgo.CanSign(),
}, nil
}
// deleteGPGKey does the actual key deletion
func deleteGPGKey(e *xorm.Session, keyID string) (int64, error) {
if keyID == "" {
return 0, fmt.Errorf("empty KeyId forbidden") //Should never happen but just to be sure
}
return e.Where("key_id=?", keyID).Or("primary_key_id=?", keyID).Delete(new(GPGKey))
}
// DeleteGPGKey deletes GPG key information in database.
func DeleteGPGKey(doer *User, id int64) (err error) {
key, err := GetGPGKeyByID(id)
if err != nil {
if IsErrGPGKeyNotExist(err) {
return nil
}
return fmt.Errorf("GetPublicKeyByID: %v", err)
}
// Check if user has access to delete this key.
if !doer.IsAdmin && doer.ID != key.OwnerID {
return ErrGPGKeyAccessDenied{doer.ID, key.ID}
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = deleteGPGKey(sess, key.KeyID); err != nil {
return err
}
if err = sess.Commit(); err != nil {
return err
}
return nil
}
// CommitVerification represents a commit validation of signature
type CommitVerification struct {
Verified bool
Reason string
SigningUser *User
SigningKey *GPGKey
}
// SignCommit represents a commit with validation of signature.
type SignCommit struct {
Verification *CommitVerification
*UserCommit
}
func readerFromBase64(s string) (io.Reader, error) {
bs, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return nil, err
}
return bytes.NewBuffer(bs), nil
}
func populateHash(hashFunc crypto.Hash, msg []byte) (hash.Hash, error) {
h := hashFunc.New()
if _, err := h.Write(msg); err != nil {
return nil, err
}
return h, nil
}
// readArmoredSign read an armored signature block with the given type. https://sourcegraph.com/github.com/golang/crypto/-/blob/openpgp/read.go#L24:6-24:17
func readArmoredSign(r io.Reader) (body io.Reader, err error) {
block, err := armor.Decode(r)
if err != nil {
return
}
if block.Type != openpgp.SignatureType {
return nil, fmt.Errorf("expected '" + openpgp.SignatureType + "', got: " + block.Type)
}
return block.Body, nil
}
func extractSignature(s string) (*packet.Signature, error) {
r, err := readArmoredSign(strings.NewReader(s))
if err != nil {
return nil, fmt.Errorf("Failed to read signature armor")
}
p, err := packet.Read(r)
if err != nil {
return nil, fmt.Errorf("Failed to read signature packet")
}
sig, ok := p.(*packet.Signature)
if !ok {
return nil, fmt.Errorf("Packet is not a signature")
}
return sig, nil
}
func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error {
//Check if key can sign
if !k.CanSign {
return fmt.Errorf("key can not sign")
}
//Decode key
b, err := readerFromBase64(k.Content)
if err != nil {
return err
}
//Read key
p, err := packet.Read(b)
if err != nil {
return err
}
//Check type
pkey, ok := p.(*packet.PublicKey)
if !ok {
return fmt.Errorf("key is not a public key")
}
return pkey.VerifySignature(h, s)
}
// ParseCommitWithSignature check if signature is good against keystore.
func ParseCommitWithSignature(c *git.Commit) *CommitVerification {
if c.Signature != nil {
//Parsing signature
sig, err := extractSignature(c.Signature.Signature)
if err != nil { //Skipping failed to extract sign
log.Error(3, "SignatureRead err: %v", err)
return &CommitVerification{
Verified: false,
Reason: "gpg.error.extract_sign",
}
}
//Find Committer account
committer, err := GetUserByEmail(c.Committer.Email)
if err != nil { //Skipping not user for commiter
log.Error(3, "NoCommitterAccount: %v", err)
return &CommitVerification{
Verified: false,
Reason: "gpg.error.no_committer_account",
}
}
keys, err := ListGPGKeys(committer.ID)
if err != nil || len(keys) == 0 { //Skipping failed to get gpg keys of user
log.Error(3, "ListGPGKeys: %v", err)
return &CommitVerification{
Verified: false,
Reason: "gpg.error.failed_retrieval_gpg_keys",
}
}
//Generating hash of commit
hash, err := populateHash(sig.Hash, []byte(c.Signature.Payload))
if err != nil { //Skipping ailed to generate hash
log.Error(3, "PopulateHash: %v", err)
return &CommitVerification{
Verified: false,
Reason: "gpg.error.generate_hash",
}
}
for _, k := range keys {
//We get PK
if err := verifySign(sig, hash, k); err == nil {
return &CommitVerification{ //Everything is ok
Verified: true,
Reason: fmt.Sprintf("%s <%s> / %s", c.Committer.Name, c.Committer.Email, k.KeyID),
SigningUser: committer,
SigningKey: k,
}
}
//And test also SubsKey
for _, sk := range k.SubsKey {
if err := verifySign(sig, hash, sk); err == nil {
return &CommitVerification{ //Everything is ok
Verified: true,
Reason: fmt.Sprintf("%s <%s> / %s", c.Committer.Name, c.Committer.Email, sk.KeyID),
SigningUser: committer,
SigningKey: sk,
}
}
}
}
return &CommitVerification{ //Default at this stage
Verified: false,
Reason: "gpg.error.no_gpg_keys_found",
}
}
return &CommitVerification{
Verified: false, //Default value
Reason: "gpg.error.not_signed_commit", //Default value
}
}
// ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys.
func ParseCommitsWithSignature(oldCommits *list.List) *list.List {
var (
newCommits = list.New()
e = oldCommits.Front()
)
for e != nil {
c := e.Value.(UserCommit)
newCommits.PushBack(SignCommit{
UserCommit: &c,
Verification: ParseCommitWithSignature(c.Commit),
})
e = e.Next()
}
return newCommits
}

164
models/gpg_key_test.go Normal file
View File

@@ -0,0 +1,164 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCheckArmoredGPGKeyString(t *testing.T) {
testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBFh91QoBCADciaDd7aqegYkn4ZIG7J0p1CRwpqMGjxFroJEMg6M1ZiuEVTRv
z49P4kcr1+98NvFmcNc+x5uJgvPCwr/N8ZW5nqBUs2yrklbFF4MeQomyZJJegP8m
/dsRT3BwIT8YMUtJuCj0iqD9vuKYfjrztcMgC1sYwcE9E9OlA0pWBvUdU2i0TIB1
vOq6slWGvHHa5l5gPfm09idlVxfH5+I+L1uIMx5ovbiVVU5x2f1AR1T18f0t2TVN
0agFTyuoYE1ATmvJHmMcsfgM1Gpd9hIlr9vlupT2kKTPoNzVzsJsOU6Ku/Lf/bac
mF+TfSbRCtmG7dkYZ4metLj7zG/WkW8IvJARABEBAAG0HUFudG9pbmUgR0lSQVJE
IDxzYXBrQHNhcGsuZnI+iQFUBBMBCAA+FiEEEIOwJg/1vpF1itJ4roJVuKDYKOQF
Alh91QoCGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQroJVuKDY
KORreggAlIkC2QjHP5tb7b0+LksB2JMXdY+UzZBcJxtNmvA7gNQaGvWRrhrbePpa
MKDP+3A4BPDBsWFbbB7N56vQ5tROpmWbNKuFOVER4S1bj0JZV0E+xkDLqt9QwQtQ
ojd7oIZJwDUwdud1PvCza2mjgBqqiFE+twbc3i9xjciCGspMniUul1eQYLxRJ0w+
sbvSOUnujnq5ByMSz9ij00O6aiPfNQS5oB5AALfpjYZDvWAAljLVrtmlQJWZ6dZo
T/YNwsW2dECPuti8+Nmu5FxPGDTXxdbnRaeJTQ3T6q1oUVAv7yTXBx5NXfXkMa5i
iEayQIH8Joq5Ev5ja/lRGQQhArMQ2bkBDQRYfdUKAQgAv7B3coLSrOQbuTZSlgWE
QeT+7DWbmqE1LAQA1pQPcUPXLBUVd60amZJxF9nzUYcY83ylDi0gUNJS+DJGOXpT
pzX2IOuOMGbtUSeKwg5s9O4SUO7f2yCc3RGaegER5zgESxelmOXG+b/hoNt7JbdU
JtxcnLr91Jw2PBO/Xf0ZKJ01CQG2Yzdrrj6jnrHyx94seHy0i6xH1o0OuvfVMLfN
/Vbb/ZHh6ym2wHNqRX62b0VAbchcJXX/MEehXGknKTkO6dDUd+mhRgWMf9ZGRFWx
ag4qALimkf1FXtAyD0vxFYeyoWUQzrOvUsm2BxIN/986R08fhkBQnp5nz07mrU02
cQARAQABiQE8BBgBCAAmFiEEEIOwJg/1vpF1itJ4roJVuKDYKOQFAlh91QoCGwwF
CQPCZwAACgkQroJVuKDYKOT32wf/UZqMdPn5OhyhffFzjQx7wolrf92WkF2JkxtH
6c3Htjlt/p5RhtKEeErSrNAxB4pqB7dznHaJXiOdWEZtRVXXjlNHjrokGTesqtKk
lHWtK62/MuyLdr+FdCl68F3ewuT2iu/MDv+D4HPqA47zma9xVgZ9ZNwJOpv3fCOo
RfY66UjGEnfgYifgtI5S84/mp2jaSc9UNvlZB6RSf8cfbJUL74kS2lq+xzSlf0yP
Av844q/BfRuVsJsK1NDNG09LC30B0l3LKBqlrRmRTUMHtgchdX2dY+p7GPOoSzlR
MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
=i9b7
-----END PGP PUBLIC KEY BLOCK-----`
key, err := checkArmoredGPGKeyString(testGPGArmor)
assert.Nil(t, err, "Could not parse a valid GPG armored key", key)
//TODO verify value of key
}
func TestExtractSignature(t *testing.T) {
testGPGArmor := `-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBFh91QoBCADciaDd7aqegYkn4ZIG7J0p1CRwpqMGjxFroJEMg6M1ZiuEVTRv
z49P4kcr1+98NvFmcNc+x5uJgvPCwr/N8ZW5nqBUs2yrklbFF4MeQomyZJJegP8m
/dsRT3BwIT8YMUtJuCj0iqD9vuKYfjrztcMgC1sYwcE9E9OlA0pWBvUdU2i0TIB1
vOq6slWGvHHa5l5gPfm09idlVxfH5+I+L1uIMx5ovbiVVU5x2f1AR1T18f0t2TVN
0agFTyuoYE1ATmvJHmMcsfgM1Gpd9hIlr9vlupT2kKTPoNzVzsJsOU6Ku/Lf/bac
mF+TfSbRCtmG7dkYZ4metLj7zG/WkW8IvJARABEBAAG0HUFudG9pbmUgR0lSQVJE
IDxzYXBrQHNhcGsuZnI+iQFUBBMBCAA+FiEEEIOwJg/1vpF1itJ4roJVuKDYKOQF
Alh91QoCGwMFCQPCZwAFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQroJVuKDY
KORreggAlIkC2QjHP5tb7b0+LksB2JMXdY+UzZBcJxtNmvA7gNQaGvWRrhrbePpa
MKDP+3A4BPDBsWFbbB7N56vQ5tROpmWbNKuFOVER4S1bj0JZV0E+xkDLqt9QwQtQ
ojd7oIZJwDUwdud1PvCza2mjgBqqiFE+twbc3i9xjciCGspMniUul1eQYLxRJ0w+
sbvSOUnujnq5ByMSz9ij00O6aiPfNQS5oB5AALfpjYZDvWAAljLVrtmlQJWZ6dZo
T/YNwsW2dECPuti8+Nmu5FxPGDTXxdbnRaeJTQ3T6q1oUVAv7yTXBx5NXfXkMa5i
iEayQIH8Joq5Ev5ja/lRGQQhArMQ2bkBDQRYfdUKAQgAv7B3coLSrOQbuTZSlgWE
QeT+7DWbmqE1LAQA1pQPcUPXLBUVd60amZJxF9nzUYcY83ylDi0gUNJS+DJGOXpT
pzX2IOuOMGbtUSeKwg5s9O4SUO7f2yCc3RGaegER5zgESxelmOXG+b/hoNt7JbdU
JtxcnLr91Jw2PBO/Xf0ZKJ01CQG2Yzdrrj6jnrHyx94seHy0i6xH1o0OuvfVMLfN
/Vbb/ZHh6ym2wHNqRX62b0VAbchcJXX/MEehXGknKTkO6dDUd+mhRgWMf9ZGRFWx
ag4qALimkf1FXtAyD0vxFYeyoWUQzrOvUsm2BxIN/986R08fhkBQnp5nz07mrU02
cQARAQABiQE8BBgBCAAmFiEEEIOwJg/1vpF1itJ4roJVuKDYKOQFAlh91QoCGwwF
CQPCZwAACgkQroJVuKDYKOT32wf/UZqMdPn5OhyhffFzjQx7wolrf92WkF2JkxtH
6c3Htjlt/p5RhtKEeErSrNAxB4pqB7dznHaJXiOdWEZtRVXXjlNHjrokGTesqtKk
lHWtK62/MuyLdr+FdCl68F3ewuT2iu/MDv+D4HPqA47zma9xVgZ9ZNwJOpv3fCOo
RfY66UjGEnfgYifgtI5S84/mp2jaSc9UNvlZB6RSf8cfbJUL74kS2lq+xzSlf0yP
Av844q/BfRuVsJsK1NDNG09LC30B0l3LKBqlrRmRTUMHtgchdX2dY+p7GPOoSzlR
MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
=i9b7
-----END PGP PUBLIC KEY BLOCK-----`
ekey, err := checkArmoredGPGKeyString(testGPGArmor)
assert.Nil(t, err, "Could not parse a valid GPG armored key", ekey)
pubkey := ekey.PrimaryKey
content, err := base64EncPubKey(pubkey)
assert.Nil(t, err, "Could not base64 encode a valid PublicKey content", ekey)
key := &GPGKey{
KeyID: pubkey.KeyIdString(),
Content: content,
Created: pubkey.CreationTime,
CanSign: pubkey.CanSign(),
CanEncryptComms: pubkey.PubKeyAlgo.CanEncrypt(),
CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
CanCertify: pubkey.PubKeyAlgo.CanSign(),
}
cannotsignkey := &GPGKey{
KeyID: pubkey.KeyIdString(),
Content: content,
Created: pubkey.CreationTime,
CanSign: false,
CanEncryptComms: false,
CanEncryptStorage: false,
CanCertify: false,
}
testGoodSigArmor := `-----BEGIN PGP SIGNATURE-----
iQEzBAABCAAdFiEEEIOwJg/1vpF1itJ4roJVuKDYKOQFAljAiQIACgkQroJVuKDY
KORvCgf6A/Ehh0r7QbO2tFEghT+/Ab+bN7jRN3zP9ed6/q/ophYmkrU0NibtbJH9
AwFVdHxCmj78SdiRjaTKyevklXw34nvMftmvnOI4lBNUdw6KWl25/n/7wN0l2oZW
rW3UawYpZgodXiLTYarfEimkDQmT67ArScjRA6lLbkEYKO0VdwDu+Z6yBUH3GWtm
45RkXpnsF6AXUfuD7YxnfyyDE1A7g7zj4vVYUAfWukJjqow/LsCUgETETJOqj9q3
52/oQDs04fVkIEtCDulcY+K/fKlukBPJf9WceNDEqiENUzN/Z1y0E+tJ07cSy4bk
yIJb+d0OAaG8bxloO7nJq4Res1Qa8Q==
=puvG
-----END PGP SIGNATURE-----`
testGoodPayload := `tree 56ae8d2799882b20381fc11659db06c16c68c61a
parent c7870c39e4e6b247235ca005797703ec4254613f
author Antoine GIRARD <sapk@sapk.fr> 1489012989 +0100
committer Antoine GIRARD <sapk@sapk.fr> 1489012989 +0100
Goog GPG
`
testBadSigArmor := `-----BEGIN PGP SIGNATURE-----
iQEzBAABCAAdFiEE5yr4rn9ulbdMxJFiPYI/ySNrtNkFAljAiYkACgkQPYI/ySNr
tNmDdQf+NXhVRiOGt0GucpjJCGrOnK/qqVUmQyRUfrqzVUdb/1/Ws84V5/wE547I
6z3oxeBKFsJa1CtIlxYaUyVhYnDzQtphJzub+Aw3UG0E2ywiE+N7RCa1Ufl7pPxJ
U0SD6gvNaeTDQV/Wctu8v8DkCtEd3N8cMCDWhvy/FQEDztVtzm8hMe0Vdm0ozEH6
P0W93sDNkLC5/qpWDN44sFlYDstW5VhMrnF0r/ohfaK2kpYHhkPk7WtOoHSUwQSg
c4gfhjvXIQrWFnII1Kr5jFGlmgNSR02qpb31VGkMzSnBhWVf2OaHS/kI49QHJakq
AhVDEnoYLCgoDGg9c3p1Ll2452/c6Q==
=uoGV
-----END PGP SIGNATURE-----`
testBadPayload := `tree 3074ff04951956a974e8b02d57733b0766f7cf6c
parent fd3577542f7ad1554c7c7c0eb86bb57a1324ad91
author Antoine GIRARD <sapk@sapk.fr> 1489013107 +0100
committer Antoine GIRARD <sapk@sapk.fr> 1489013107 +0100
Unkonwn GPG key with good email
`
//Reading Sign
goodSig, err := extractSignature(testGoodSigArmor)
assert.Nil(t, err, "Could not parse a valid GPG armored signature", testGoodSigArmor)
badSig, err := extractSignature(testBadSigArmor)
assert.Nil(t, err, "Could not parse a valid GPG armored signature", testBadSigArmor)
//Generating hash of commit
goodHash, err := populateHash(goodSig.Hash, []byte(testGoodPayload))
assert.Nil(t, err, "Could not generate a valid hash of payload", testGoodPayload)
badHash, err := populateHash(badSig.Hash, []byte(testBadPayload))
assert.Nil(t, err, "Could not generate a valid hash of payload", testBadPayload)
//Verify
err = verifySign(goodSig, goodHash, key)
assert.Nil(t, err, "Could not validate a good signature")
err = verifySign(badSig, badHash, key)
assert.NotNil(t, err, "Validate a bad signature")
err = verifySign(goodSig, goodHash, cannotsignkey)
assert.NotNil(t, err, "Validate a bad signature with a kay that can not sign")
}

View File

@@ -78,8 +78,8 @@ func graphItemFromString(s string, r *git.Repository) (GraphItem, error) {
return GraphItem{}, fmt.Errorf("Failed parsing grap line:%s. Expect 1 or two fields", s)
}
rows := strings.Split(data, "|")
if len(rows) != 8 {
rows := strings.SplitN(data, "|", 8)
if len(rows) < 8 {
return GraphItem{}, fmt.Errorf("Failed parsing grap line:%s - Should containt 8 datafields", s)
}

View File

@@ -374,7 +374,7 @@ func (issue *Issue) RemoveLabel(doer *User, label *Label) error {
return err
}
if has, err := HasAccess(doer, issue.Repo, AccessModeWrite); err != nil {
if has, err := HasAccess(doer.ID, issue.Repo, AccessModeWrite); err != nil {
return err
} else if !has {
return ErrLabelNotExist{}
@@ -415,7 +415,7 @@ func (issue *Issue) ClearLabels(doer *User) (err error) {
return err
}
if has, err := hasAccess(sess, doer, issue.Repo, AccessModeWrite); err != nil {
if has, err := hasAccess(sess, doer.ID, issue.Repo, AccessModeWrite); err != nil {
return err
} else if !has {
return ErrLabelNotExist{}
@@ -733,7 +733,7 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
return nil
}
// ChangeAssignee changes the Asssignee field of this issue.
// ChangeAssignee changes the Assignee field of this issue.
func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
var oldAssigneeID = issue.AssigneeID
issue.AssigneeID = assigneeID
@@ -809,23 +809,14 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
}
}
if opts.Issue.AssigneeID > 0 {
assignee, err := getUserByID(e, opts.Issue.AssigneeID)
if err != nil && !IsErrUserNotExist(err) {
return fmt.Errorf("getUserByID: %v", err)
if assigneeID := opts.Issue.AssigneeID; assigneeID > 0 {
valid, err := hasAccess(e, assigneeID, opts.Repo, AccessModeWrite)
if err != nil {
return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", assigneeID, opts.Repo.ID, err)
}
// Assume assignee is invalid and drop silently.
opts.Issue.AssigneeID = 0
if assignee != nil {
valid, err := hasAccess(e, assignee, opts.Repo, AccessModeWrite)
if err != nil {
return fmt.Errorf("hasAccess [user_id: %d, repo_id: %d]: %v", assignee.ID, opts.Repo.ID, err)
}
if valid {
opts.Issue.AssigneeID = assignee.ID
opts.Issue.Assignee = assignee
}
if !valid {
opts.Issue.AssigneeID = 0
opts.Issue.Assignee = nil
}
}
@@ -1011,6 +1002,16 @@ func GetIssueByID(id int64) (*Issue, error) {
return getIssueByID(x, id)
}
func getIssuesByIDs(e Engine, issueIDs []int64) ([]*Issue, error) {
issues := make([]*Issue, 0, 10)
return issues, e.In("id", issueIDs).Find(&issues)
}
// GetIssuesByIDs return issues with the given IDs.
func GetIssuesByIDs(issueIDs []int64) ([]*Issue, error) {
return getIssuesByIDs(x, issueIDs)
}
// IssuesOptions represents options of an issue.
type IssuesOptions struct {
RepoID int64
@@ -1133,6 +1134,24 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) {
return issues, nil
}
// GetParticipantsByIssueID returns all users who are participated in comments of an issue.
func GetParticipantsByIssueID(issueID int64) ([]*User, error) {
userIDs := make([]int64, 0, 5)
if err := x.Table("comment").Cols("poster_id").
Where("issue_id = ?", issueID).
And("type = ?", CommentTypeComment).
Distinct("poster_id").
Find(&userIDs); err != nil {
return nil, fmt.Errorf("get poster IDs: %v", err)
}
if len(userIDs) == 0 {
return nil, nil
}
users := make([]*User, 0, len(userIDs))
return users, x.In("id", userIDs).Find(&users)
}
// UpdateIssueMentions extracts mentioned people from content and
// updates issue-user relations for them.
func UpdateIssueMentions(e Engine, issueID int64, mentions []string) error {

View File

@@ -59,10 +59,10 @@ func (issues IssueList) loadPosters(e Engine) error {
return nil
}
postgerIDs := issues.getPosterIDs()
posterMaps := make(map[int64]*User, len(postgerIDs))
posterIDs := issues.getPosterIDs()
posterMaps := make(map[int64]*User, len(posterIDs))
err := e.
In("id", postgerIDs).
In("id", posterIDs).
Find(&posterMaps)
if err != nil {
return err

65
models/issue_list_test.go Normal file
View File

@@ -0,0 +1,65 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIssueList_LoadRepositories(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
issueList := IssueList{
AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue),
AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue),
AssertExistsAndLoadBean(t, &Issue{ID: 4}).(*Issue),
}
repos, err := issueList.LoadRepositories()
assert.NoError(t, err)
assert.Len(t, repos, 2)
for _, issue := range issueList {
assert.EqualValues(t, issue.RepoID, issue.Repo.ID)
}
}
func TestIssueList_LoadAttributes(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
issueList := IssueList{
AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue),
AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue),
AssertExistsAndLoadBean(t, &Issue{ID: 4}).(*Issue),
}
assert.NoError(t, issueList.LoadAttributes())
for _, issue := range issueList {
assert.EqualValues(t, issue.RepoID, issue.Repo.ID)
for _, label := range issue.Labels {
assert.EqualValues(t, issue.RepoID, label.RepoID)
AssertExistsAndLoadBean(t, &IssueLabel{IssueID: issue.ID, LabelID: label.ID})
}
if issue.PosterID > 0 {
assert.EqualValues(t, issue.PosterID, issue.Poster.ID)
}
if issue.AssigneeID > 0 {
assert.EqualValues(t, issue.AssigneeID, issue.Assignee.ID)
}
if issue.MilestoneID > 0 {
assert.EqualValues(t, issue.MilestoneID, issue.Milestone.ID)
}
if issue.IsPull {
assert.EqualValues(t, issue.ID, issue.PullRequest.IssueID)
}
for _, attachment := range issue.Attachments {
assert.EqualValues(t, issue.ID, attachment.IssueID)
}
for _, comment := range issue.Comments {
assert.EqualValues(t, issue.ID, comment.IssueID)
}
}
}

View File

@@ -19,15 +19,27 @@ func (issue *Issue) mailSubject() string {
}
// mailIssueCommentToParticipants can be used for both new issue creation and comment.
// This function sends two list of emails:
// 1. Repository watchers and users who are participated in comments.
// 2. Users who are not in 1. but get mentioned in current issue/comment.
func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string) error {
if !setting.Service.EnableNotifyMail {
return nil
}
// Mail watchers.
watchers, err := GetWatchers(issue.RepoID)
if err != nil {
return fmt.Errorf("GetWatchers [%d]: %v", issue.RepoID, err)
return fmt.Errorf("GetWatchers [repo_id: %d]: %v", issue.RepoID, err)
}
participants, err := GetParticipantsByIssueID(issue.ID)
if err != nil {
return fmt.Errorf("GetParticipantsByIssueID [issue_id: %d]: %v", issue.ID, err)
}
// In case the issue poster is not watching the repository,
// even if we have duplicated in watchers, can be safely filtered out.
if issue.PosterID != doer.ID {
participants = append(participants, issue.Poster)
}
tos := make([]string, 0, len(watchers)) // List of email addresses.
@@ -48,6 +60,16 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string)
tos = append(tos, to.Email)
names = append(names, to.Name)
}
for i := range participants {
if participants[i].ID == doer.ID {
continue
} else if com.IsSliceContainsStr(names, participants[i].Name) {
continue
}
tos = append(tos, participants[i].Email)
names = append(names, participants[i].Name)
}
SendIssueCommentMail(issue, doer, tos)
// Mail mentioned people and exclude watchers.

View File

@@ -5,6 +5,7 @@
package models
import (
"sort"
"testing"
"github.com/stretchr/testify/assert"
@@ -42,3 +43,44 @@ func TestIssueAPIURL(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/issues/1", issue.APIURL())
}
func TestGetIssuesByIDs(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(expectedIssueIDs []int64, nonExistentIssueIDs []int64) {
issues, err := GetIssuesByIDs(append(expectedIssueIDs, nonExistentIssueIDs...))
assert.NoError(t, err)
actualIssueIDs := make([]int64, len(issues))
for i, issue := range issues {
actualIssueIDs[i] = issue.ID
}
assert.Equal(t, expectedIssueIDs, actualIssueIDs)
}
testSuccess([]int64{1, 2, 3}, []int64{})
testSuccess([]int64{1, 2, 3}, []int64{NonexistentID})
}
func TestGetParticipantsByIssueID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
checkPartecipants := func(issueID int64, userIDs []int) {
partecipants, err := GetParticipantsByIssueID(issueID)
if assert.NoError(t, err) {
partecipantsIDs := make([]int, len(partecipants))
for i, u := range partecipants {
partecipantsIDs[i] = int(u.ID)
}
sort.Ints(partecipantsIDs)
sort.Ints(userIDs)
assert.Equal(t, userIDs, partecipantsIDs)
}
}
// User 1 is issue1 poster (see fixtures/issue.yml)
// User 2 only labeled issue1 (see fixtures/comment.yml)
// Users 3 and 5 made actual comments (see fixtures/comment.yml)
checkPartecipants(1, []int{3, 5})
}

71
models/issue_watch.go Normal file
View File

@@ -0,0 +1,71 @@
package models
import (
"time"
)
// IssueWatch is connection request for receiving issue notification.
type IssueWatch struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(watch) NOT NULL"`
IssueID int64 `xorm:"UNIQUE(watch) NOT NULL"`
IsWatching bool `xorm:"NOT NULL"`
Created time.Time `xorm:"-"`
CreatedUnix int64 `xorm:"NOT NULL"`
}
// BeforeInsert is invoked from XORM before inserting an object of this type.
func (iw *IssueWatch) BeforeInsert() {
iw.CreatedUnix = time.Now().Unix()
}
// CreateOrUpdateIssueWatch set watching for a user and issue
func CreateOrUpdateIssueWatch(userID, issueID int64, isWatching bool) error {
s := x.NewSession()
defer s.Close()
if err := s.Begin(); err != nil {
return err
}
iw, exists, err := getIssueWatch(s, userID, issueID)
if err != nil {
return err
}
if !exists {
iw = &IssueWatch{
UserID: userID,
IssueID: issueID,
IsWatching: isWatching,
}
if _, err := s.Insert(iw); err != nil {
return err
}
} else {
iw.IsWatching = isWatching
if _, err := s.Id(iw.ID).Update(iw); err != nil {
return err
}
}
if err := s.Commit(); err != nil {
return err
}
return nil
}
// GetIssueWatch returns an issue watch by user and issue
func GetIssueWatch(userID, issueID int64) (iw *IssueWatch, exists bool, err error) {
iw, exists, err = getIssueWatch(x, userID, issueID)
return
}
func getIssueWatch(e Engine, userID, issueID int64) (iw *IssueWatch, exists bool, err error) {
iw = new(IssueWatch)
exists, err = e.
Where("user_id = ?", userID).
And("issue_id = ?", issueID).
Get(iw)
return
}

View File

@@ -92,6 +92,16 @@ var migrations = []Migration{
NewMigration("use new avatar path name for security reason", useNewNameAvatars),
// v21 -> v22
NewMigration("rewrite authorized_keys file via new format", useNewPublickeyFormat),
// v22 -> v23
NewMigration("generate and migrate wiki Git hooks", generateAndMigrateWikiGitHooks),
// v23 -> v24
NewMigration("add user openid table", addUserOpenID),
// v24 -> v25
NewMigration("change the key_id and primary_key_id type", changeGPGKeysColumns),
// v25 -> v26
NewMigration("add show field in user openid table", addUserOpenIDShow),
// v26 -> v27
NewMigration("generate and migrate repo and wiki Git hooks", generateAndMigrateGitHookChains),
}
// Migrate database to current version

View File

@@ -28,7 +28,7 @@ type UserV14 struct {
DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"`
}
// TableName will be invoked by XORM to customrize the table name
// TableName will be invoked by XORM to customize the table name
func (*UserV14) TableName() string {
return "user"
}

View File

@@ -59,6 +59,12 @@ func addUnitsToTables(x *xorm.Engine) error {
}
var repoUnit RepoUnit
if exist, err := sess.IsTableExist(&repoUnit); err != nil {
return fmt.Errorf("IsExist RepoUnit: %v", err)
} else if exist {
return nil
}
if err := sess.CreateTable(&repoUnit); err != nil {
return fmt.Errorf("CreateTable RepoUnit: %v", err)
}

View File

@@ -60,8 +60,14 @@ func generateAndMigrateGitHooks(x *xorm.Engine) (err error) {
oldHookPath := filepath.Join(hookDir, hookName)
newHookPath := filepath.Join(hookDir, hookName+".d", "gitea")
if err = os.MkdirAll(filepath.Join(hookDir, hookName+".d"), os.ModePerm); err != nil {
return fmt.Errorf("create hooks dir '%s': %v", filepath.Join(hookDir, hookName+".d"), err)
customHooksDir := filepath.Join(hookDir, hookName+".d")
// if it's exist, that means you have upgraded ever
if com.IsExist(customHooksDir) {
continue
}
if err = os.MkdirAll(customHooksDir, os.ModePerm); err != nil {
return fmt.Errorf("create hooks dir '%s': %v", customHooksDir, err)
}
// WARNING: Old server-side hooks will be moved to sub directory with the same name

View File

@@ -13,6 +13,7 @@ import (
"path/filepath"
"strconv"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/go-xorm/xorm"
@@ -40,7 +41,8 @@ func useNewNameAvatars(x *xorm.Engine) error {
for _, name := range names {
userID, err := strconv.ParseInt(name, 10, 64)
if err != nil {
return err
log.Warn("ignore avatar %s rename: %v", name, err)
continue
}
var user User

View File

@@ -11,6 +11,7 @@ import (
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
)
@@ -21,6 +22,10 @@ const (
func useNewPublickeyFormat(x *xorm.Engine) error {
fpath := filepath.Join(setting.SSH.RootPath, "authorized_keys")
if !com.IsExist(fpath) {
return nil
}
tmpPath := fpath + ".tmp"
f, err := os.OpenFile(tmpPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {

94
models/migrations/v22.go Normal file
View File

@@ -0,0 +1,94 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
)
func generateAndMigrateWikiGitHooks(x *xorm.Engine) (err error) {
type Repository struct {
ID int64
OwnerID int64
Name string
}
type User struct {
ID int64
Name string
}
var (
hookNames = []string{"pre-receive", "update", "post-receive"}
hookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\nORI_DIR=`pwd`\nSHELL_FOLDER=$(cd \"$(dirname \"$0\")\";pwd)\ncd \"$ORI_DIR\"\nfor i in `ls \"$SHELL_FOLDER/pre-receive.d\"`; do\n sh \"$SHELL_FOLDER/pre-receive.d/$i\"\ndone", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\nORI_DIR=`pwd`\nSHELL_FOLDER=$(cd \"$(dirname \"$0\")\";pwd)\ncd \"$ORI_DIR\"\nfor i in `ls \"$SHELL_FOLDER/update.d\"`; do\n sh \"$SHELL_FOLDER/update.d/$i\" $1 $2 $3\ndone", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\nORI_DIR=`pwd`\nSHELL_FOLDER=$(cd \"$(dirname \"$0\")\";pwd)\ncd \"$ORI_DIR\"\nfor i in `ls \"$SHELL_FOLDER/post-receive.d\"`; do\n sh \"$SHELL_FOLDER/post-receive.d/$i\"\ndone", setting.ScriptType),
}
giteaHookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
}
)
return x.Where("id > 0").Iterate(new(Repository),
func(idx int, bean interface{}) error {
repo := bean.(*Repository)
user := new(User)
has, err := x.Where("id = ?", repo.OwnerID).Get(user)
if err != nil {
return fmt.Errorf("query owner of repository [repo_id: %d, owner_id: %d]: %v", repo.ID, repo.OwnerID, err)
} else if !has {
return nil
}
repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name)) + ".wiki.git"
if !com.IsExist(repoPath) {
return nil
}
hookDir := filepath.Join(repoPath, "hooks")
for i, hookName := range hookNames {
oldHookPath := filepath.Join(hookDir, hookName)
newHookPath := filepath.Join(hookDir, hookName+".d", "gitea")
customHooksDir := filepath.Join(hookDir, hookName+".d")
// if it's exist, that means you have upgraded ever
if com.IsExist(customHooksDir) {
continue
}
if err = os.MkdirAll(customHooksDir, os.ModePerm); err != nil {
return fmt.Errorf("create hooks dir '%s': %v", customHooksDir, err)
}
// WARNING: Old server-side hooks will be moved to sub directory with the same name
if hookName != "update" && com.IsExist(oldHookPath) {
newPlace := filepath.Join(hookDir, hookName+".d", hookName)
if err = os.Rename(oldHookPath, newPlace); err != nil {
return fmt.Errorf("Remove old hook file '%s' to '%s': %v", oldHookPath, newPlace, err)
}
}
if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil {
return fmt.Errorf("write old hook file '%s': %v", oldHookPath, err)
}
if err = ioutil.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0777); err != nil {
return fmt.Errorf("write new hook file '%s': %v", oldHookPath, err)
}
}
return nil
})
}

25
models/migrations/v23.go Normal file
View File

@@ -0,0 +1,25 @@
// Copyright 2017 Gitea. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"github.com/go-xorm/xorm"
)
// UserOpenID is the list of all OpenID identities of a user.
type UserOpenID struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"INDEX NOT NULL"`
URI string `xorm:"UNIQUE NOT NULL"`
}
func addUserOpenID(x *xorm.Engine) error {
if err := x.Sync2(new(UserOpenID)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
return nil
}

50
models/migrations/v24.go Normal file
View File

@@ -0,0 +1,50 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"time"
"github.com/go-xorm/xorm"
)
func changeGPGKeysColumns(x *xorm.Engine) error {
// EmailAddress is the list of all email addresses of a user. Can contain the
// primary email address, but is not obligatory.
type EmailAddress struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"INDEX NOT NULL"`
Email string `xorm:"UNIQUE NOT NULL"`
IsActivated bool
IsPrimary bool `xorm:"-"`
}
// GPGKey represents a GPG key.
type GPGKey struct {
ID int64 `xorm:"pk autoincr"`
OwnerID int64 `xorm:"INDEX NOT NULL"`
KeyID string `xorm:"INDEX CHAR(16) NOT NULL"`
PrimaryKeyID string `xorm:"CHAR(16)"`
Content string `xorm:"TEXT NOT NULL"`
Created time.Time `xorm:"-"`
CreatedUnix int64
Expired time.Time `xorm:"-"`
ExpiredUnix int64
Added time.Time `xorm:"-"`
AddedUnix int64
SubsKey []*GPGKey `xorm:"-"`
Emails []*EmailAddress
CanSign bool
CanEncryptComms bool
CanEncryptStorage bool
CanCertify bool
}
if err := x.DropTables(new(GPGKey)); err != nil {
return err
}
return x.Sync(new(GPGKey))
}

18
models/migrations/v25.go Normal file
View File

@@ -0,0 +1,18 @@
// Copyright 2017 Gitea. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"fmt"
"github.com/go-xorm/xorm"
)
func addUserOpenIDShow(x *xorm.Engine) error {
if err := x.Sync2(new(UserOpenID)); err != nil {
return fmt.Errorf("Sync2: %v", err)
}
return nil
}

87
models/migrations/v26.go Normal file
View File

@@ -0,0 +1,87 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
"github.com/go-xorm/xorm"
)
func generateAndMigrateGitHookChains(x *xorm.Engine) (err error) {
type Repository struct {
ID int64
OwnerID int64
Name string
}
type User struct {
ID int64
Name string
}
var (
hookNames = []string{"pre-receive", "update", "post-receive"}
hookTpl = fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType)
)
return x.Where("id > 0").Iterate(new(Repository),
func(idx int, bean interface{}) error {
repo := bean.(*Repository)
user := new(User)
has, err := x.Where("id = ?", repo.OwnerID).Get(user)
if err != nil {
return fmt.Errorf("query owner of repository [repo_id: %d, owner_id: %d]: %v", repo.ID, repo.OwnerID, err)
} else if !has {
return nil
}
repoPaths := []string{
filepath.Join(setting.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name)) + ".git",
filepath.Join(setting.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name)) + ".wiki.git",
}
for _, repoPath := range repoPaths {
if com.IsExist(repoPath) {
hookDir := filepath.Join(repoPath, "hooks")
for _, hookName := range hookNames {
oldHookPath := filepath.Join(hookDir, hookName)
// compare md5sums of hooks
if com.IsExist(oldHookPath) {
f, err := os.Open(oldHookPath)
if err != nil {
return fmt.Errorf("cannot open old hook file '%s': %v", oldHookPath, err)
}
defer f.Close()
h := md5.New()
if _, err := io.Copy(h, f); err != nil {
return fmt.Errorf("cannot read old hook file '%s': %v", oldHookPath, err)
}
if hex.EncodeToString(h.Sum(nil)) == "6718ef67d0834e0a7908259acd566e3f" {
return nil
}
}
if err = ioutil.WriteFile(oldHookPath, []byte(hookTpl), 0777); err != nil {
return fmt.Errorf("write old hook file '%s': %v", oldHookPath, err)
}
}
}
}
return nil
})
}

View File

@@ -111,10 +111,13 @@ func init() {
new(IssueUser),
new(LFSMetaObject),
new(TwoFactor),
new(GPGKey),
new(RepoUnit),
new(RepoRedirect),
new(ExternalLoginUser),
new(ProtectedBranch),
new(UserOpenID),
new(IssueWatch),
)
gonicNames := []string{"SSL", "UID"}

View File

@@ -11,15 +11,45 @@ import (
)
func Test_parsePostgreSQLHostPort(t *testing.T) {
test := func(input, expectedHost, expectedPort string) {
host, port := parsePostgreSQLHostPort(input)
assert.Equal(t, expectedHost, host)
assert.Equal(t, expectedPort, port)
tests := []struct {
HostPort string
Host string
Port string
}{
{
HostPort: "127.0.0.1:1234",
Host: "127.0.0.1",
Port: "1234",
},
{
HostPort: "127.0.0.1",
Host: "127.0.0.1",
Port: "5432",
},
{
HostPort: "[::1]:1234",
Host: "[::1]",
Port: "1234",
},
{
HostPort: "[::1]",
Host: "[::1]",
Port: "5432",
},
{
HostPort: "/tmp/pg.sock:1234",
Host: "/tmp/pg.sock",
Port: "1234",
},
{
HostPort: "/tmp/pg.sock",
Host: "/tmp/pg.sock",
Port: "5432",
},
}
for _, test := range tests {
host, port := parsePostgreSQLHostPort(test.HostPort)
assert.Equal(t, test.Host, host)
assert.Equal(t, test.Port, port)
}
test("127.0.0.1:1234", "127.0.0.1", "1234")
test("127.0.0.1", "127.0.0.1", "5432")
test("[::1]:1234", "[::1]", "1234")
test("[::1]", "[::1]", "5432")
test("/tmp/pg.sock:1234", "/tmp/pg.sock", "1234")
test("/tmp/pg.sock", "/tmp/pg.sock", "5432")
}

View File

@@ -139,18 +139,19 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e
}
}
if err = t.getMembers(e); err != nil {
return fmt.Errorf("get team members: %v", err)
teamUsers, err := getTeamUsersByTeamID(e, t.ID)
if err != nil {
return fmt.Errorf("getTeamUsersByTeamID: %v", err)
}
for _, u := range t.Members {
has, err := hasAccess(e, u, repo, AccessModeRead)
for _, teamUser := range teamUsers {
has, err := hasAccess(e, teamUser.UID, repo, AccessModeRead)
if err != nil {
return err
} else if has {
continue
}
if err = watchRepo(e, u.ID, repo.ID, false); err != nil {
if err = watchRepo(e, teamUser.UID, repo.ID, false); err != nil {
return err
}
}
@@ -399,20 +400,25 @@ func IsTeamMember(orgID, teamID, userID int64) bool {
return isTeamMember(x, orgID, teamID, userID)
}
func getTeamMembers(e Engine, teamID int64) (_ []*User, err error) {
func getTeamUsersByTeamID(e Engine, teamID int64) ([]*TeamUser, error) {
teamUsers := make([]*TeamUser, 0, 10)
if err = e.
return teamUsers, e.
Where("team_id=?", teamID).
Find(&teamUsers); err != nil {
Find(&teamUsers)
}
func getTeamMembers(e Engine, teamID int64) (_ []*User, err error) {
teamUsers, err := getTeamUsersByTeamID(e, teamID)
if err != nil {
return nil, fmt.Errorf("get team-users: %v", err)
}
members := make([]*User, 0, len(teamUsers))
for i := range teamUsers {
member := new(User)
if _, err = e.Id(teamUsers[i].UID).Get(member); err != nil {
return nil, fmt.Errorf("get user '%d': %v", teamUsers[i].UID, err)
members := make([]*User, len(teamUsers))
for i, teamUser := range teamUsers {
member, err := getUserByID(e, teamUser.UID)
if err != nil {
return nil, fmt.Errorf("get user '%d': %v", teamUser.UID, err)
}
members = append(members, member)
members[i] = member
}
return members, nil
}

View File

@@ -117,15 +117,15 @@ func TestTeam_HasRepository(t *testing.T) {
func TestTeam_AddRepository(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSucess := func(teamID, repoID int64) {
testSuccess := func(teamID, repoID int64) {
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
repo := AssertExistsAndLoadBean(t, &Repository{ID: repoID}).(*Repository)
assert.NoError(t, team.AddRepository(repo))
AssertExistsAndLoadBean(t, &TeamRepo{TeamID: teamID, RepoID: repoID})
CheckConsistencyFor(t, &Team{ID: teamID}, &Repository{ID: repoID})
}
testSucess(2, 3)
testSucess(2, 5)
testSuccess(2, 3)
testSuccess(2, 5)
team := AssertExistsAndLoadBean(t, &Team{ID: 1}).(*Team)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
@@ -243,7 +243,7 @@ func TestDeleteTeam(t *testing.T) {
// check that team members don't have "leftover" access to repos
user := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 3}).(*Repository)
accessMode, err := AccessLevel(user, repo)
accessMode, err := AccessLevel(user.ID, repo)
assert.NoError(t, err)
assert.True(t, accessMode < AccessModeWrite)
}

View File

@@ -73,7 +73,7 @@ func (r *Release) loadAttributes(e Engine) error {
return nil
}
// LoadAttributes load repo and publisher attributes for a realease
// LoadAttributes load repo and publisher attributes for a release
func (r *Release) LoadAttributes() error {
return r.loadAttributes(x)
}
@@ -365,7 +365,7 @@ func DeleteReleaseByID(id int64, u *User, delTag bool) error {
return fmt.Errorf("GetRepositoryByID: %v", err)
}
has, err := HasAccess(u, repo, AccessModeWrite)
has, err := HasAccess(u.ID, repo, AccessModeWrite)
if err != nil {
return fmt.Errorf("HasAccess: %v", err)
} else if !has {

View File

@@ -531,7 +531,7 @@ func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) strin
// HasAccess returns true when user has access to this repository
func (repo *Repository) HasAccess(u *User) bool {
has, _ := HasAccess(u, repo, AccessModeRead)
has, _ := HasAccess(u.ID, repo, AccessModeRead)
return has
}
@@ -553,7 +553,7 @@ func (repo *Repository) CanBeForked() bool {
// CanEnablePulls returns true if repository meets the requirements of accepting pulls.
func (repo *Repository) CanEnablePulls() bool {
return !repo.IsMirror
return !repo.IsMirror && !repo.IsBare
}
// AllowsPulls returns true if repository meets the requirements of accepting pulls and has them enabled.
@@ -845,11 +845,7 @@ func cleanUpMigrateGitConfig(configPath string) error {
func createDelegateHooks(repoPath string) (err error) {
var (
hookNames = []string{"pre-receive", "update", "post-receive"}
hookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\nORI_DIR=`pwd`\nSHELL_FOLDER=$(cd \"$(dirname \"$0\")\";pwd)\ncd \"$ORI_DIR\"\nfor i in `ls \"$SHELL_FOLDER/pre-receive.d\"`; do\n sh \"$SHELL_FOLDER/pre-receive.d/$i\"\ndone", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\nORI_DIR=`pwd`\nSHELL_FOLDER=$(cd \"$(dirname \"$0\")\";pwd)\ncd \"$ORI_DIR\"\nfor i in `ls \"$SHELL_FOLDER/update.d\"`; do\n sh \"$SHELL_FOLDER/update.d/$i\" $1 $2 $3\ndone", setting.ScriptType),
fmt.Sprintf("#!/usr/bin/env %s\nORI_DIR=`pwd`\nSHELL_FOLDER=$(cd \"$(dirname \"$0\")\";pwd)\ncd \"$ORI_DIR\"\nfor i in `ls \"$SHELL_FOLDER/post-receive.d\"`; do\n sh \"$SHELL_FOLDER/post-receive.d/$i\"\ndone", setting.ScriptType),
}
hookTpl = fmt.Sprintf("#!/usr/bin/env %s\ndata=$(cat)\nexitcodes=\"\"\nhookname=$(basename $0)\nGIT_DIR=${GIT_DIR:-$(dirname $0)}\n\nfor hook in ${GIT_DIR}/hooks/${hookname}.d/*; do\ntest -x \"${hook}\" || continue\necho \"${data}\" | \"${hook}\"\nexitcodes=\"${exitcodes} $?\"\ndone\n\nfor i in ${exitcodes}; do\n[ ${i} -eq 0 ] || exit ${i}\ndone\n", setting.ScriptType)
giteaHookTpls = []string{
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf),
@@ -868,7 +864,7 @@ func createDelegateHooks(repoPath string) (err error) {
}
// WARNING: This will override all old server-side hooks
if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil {
if err = ioutil.WriteFile(oldHookPath, []byte(hookTpl), 0777); err != nil {
return fmt.Errorf("write old hook file '%s': %v", oldHookPath, err)
}
@@ -1585,7 +1581,7 @@ func DeleteRepository(uid, repoID int64) error {
attachments := make([]*Attachment, 0, 5)
if err = sess.
In("issue_id=?", issueIDs).
In("issue_id", issueIDs).
Find(&attachments); err != nil {
return err
}
@@ -1909,6 +1905,11 @@ func SyncRepositoryHooks() error {
if err := createDelegateHooks(bean.(*Repository).RepoPath()); err != nil {
return fmt.Errorf("SyncRepositoryHook: %v", err)
}
if bean.(*Repository).HasWiki() {
if err := createDelegateHooks(bean.(*Repository).WikiPath()); err != nil {
return fmt.Errorf("SyncRepositoryHook: %v", err)
}
}
return nil
})
}

View File

@@ -21,26 +21,19 @@ func (repos RepositoryList) loadAttributes(e Engine) error {
}
// Load owners.
set := make(map[int64]*User)
set := make(map[int64]struct{})
for i := range repos {
set[repos[i].OwnerID] = nil
set[repos[i].OwnerID] = struct{}{}
}
userIDs := make([]int64, 0, len(set))
for userID := range set {
userIDs = append(userIDs, userID)
}
users := make([]*User, 0, len(userIDs))
users := make(map[int64]*User, len(set))
if err := e.
Where("id > 0").
In("id", userIDs).
In("id", keysInt64(set)).
Find(&users); err != nil {
return fmt.Errorf("find users: %v", err)
}
for i := range users {
set[users[i].ID] = users[i]
}
for i := range repos {
repos[i].Owner = set[repos[i].OwnerID]
repos[i].Owner = users[repos[i].OwnerID]
}
return nil
}

View File

@@ -794,7 +794,7 @@ func DeleteDeployKey(doer *User, id int64) error {
if err != nil {
return fmt.Errorf("GetRepositoryByID: %v", err)
}
yes, err := HasAccess(doer, repo, AccessModeAdmin)
yes, err := HasAccess(doer.ID, repo, AccessModeAdmin)
if err != nil {
return fmt.Errorf("HasAccess: %v", err)
} else if !yes {

View File

@@ -478,7 +478,7 @@ func (u *User) DeleteAvatar() error {
// IsAdminOfRepo returns true if user has admin or higher access of repository.
func (u *User) IsAdminOfRepo(repo *Repository) bool {
has, err := HasAccess(u, repo, AccessModeAdmin)
has, err := HasAccess(u.ID, repo, AccessModeAdmin)
if err != nil {
log.Error(3, "HasAccess: %v", err)
}
@@ -487,7 +487,7 @@ func (u *User) IsAdminOfRepo(repo *Repository) bool {
// IsWriterOfRepo returns true if user has write access to given repository.
func (u *User) IsWriterOfRepo(repo *Repository) bool {
has, err := HasAccess(u, repo, AccessModeWrite)
has, err := HasAccess(u.ID, repo, AccessModeWrite)
if err != nil {
log.Error(3, "HasAccess: %v", err)
}
@@ -541,7 +541,7 @@ func (u *User) GetOrgRepositoryIDs() ([]int64, error) {
GroupBy("repository.id").Find(&ids)
}
// GetAccessRepoIDs returns all repsitories IDs where user's or user is a team member orgnizations
// GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations
func (u *User) GetAccessRepoIDs() ([]int64, error) {
ids, err := u.GetRepositoryIDs()
if err != nil {
@@ -596,7 +596,7 @@ func (u *User) ShortName(length int) string {
return base.EllipsisString(u.Name, length)
}
// IsMailable checks if a user is elegible
// IsMailable checks if a user is eligible
// to receive emails.
func (u *User) IsMailable() bool {
return u.IsActive
@@ -615,7 +615,7 @@ func IsUserExist(uid int64, name string) (bool, error) {
Get(&User{LowerName: strings.ToLower(name)})
}
// GetUserSalt returns a ramdom user salt token.
// GetUserSalt returns a random user salt token.
func GetUserSalt() (string, error) {
return base.GetRandomString(10)
}
@@ -630,7 +630,7 @@ func NewGhostUser() *User {
}
var (
reservedUsernames = []string{"assets", "css", "img", "js", "less", "plugins", "debug", "raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "new", ".", ".."}
reservedUsernames = []string{"assets", "css", "explore", "img", "js", "less", "plugins", "debug", "raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "new", ".", ".."}
reservedUserPatterns = []string{"*.keys"}
)
@@ -964,6 +964,7 @@ func deleteUser(e *xorm.Session, u *User) error {
&Action{UserID: u.ID},
&IssueUser{UID: u.ID},
&EmailAddress{UID: u.ID},
&UserOpenID{UID: u.ID},
); err != nil {
return fmt.Errorf("deleteBeans: %v", err)
}
@@ -989,7 +990,7 @@ func deleteUser(e *xorm.Session, u *User) error {
}
// ***** START: ExternalLoginUser *****
if err = RemoveAllAccountLinks(u); err != nil {
if err = removeAllAccountLinks(e, u); err != nil {
return fmt.Errorf("ExternalLoginUser: %v", err)
}
// ***** END: ExternalLoginUser *****
@@ -1103,7 +1104,7 @@ func GetUserByID(id int64) (*User, error) {
// GetAssigneeByID returns the user with write access of repository by given ID.
func GetAssigneeByID(repo *Repository, userID int64) (*User, error) {
has, err := HasAccess(&User{ID: userID}, repo, AccessModeWrite)
has, err := HasAccess(userID, repo, AccessModeWrite)
if err != nil {
return nil, err
} else if !has {
@@ -1292,78 +1293,6 @@ func SearchUserByName(opts *SearchUserOptions) (users []*User, _ int64, _ error)
return users, count, sess.Find(&users)
}
// ___________ .__ .__
// \_ _____/___ | | | | ______ _ __
// | __)/ _ \| | | | / _ \ \/ \/ /
// | \( <_> ) |_| |_( <_> ) /
// \___ / \____/|____/____/\____/ \/\_/
// \/
// Follow represents relations of user and his/her followers.
type Follow struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(follow)"`
FollowID int64 `xorm:"UNIQUE(follow)"`
}
// IsFollowing returns true if user is following followID.
func IsFollowing(userID, followID int64) bool {
has, _ := x.Get(&Follow{UserID: userID, FollowID: followID})
return has
}
// FollowUser marks someone be another's follower.
func FollowUser(userID, followID int64) (err error) {
if userID == followID || IsFollowing(userID, followID) {
return nil
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Insert(&Follow{UserID: userID, FollowID: followID}); err != nil {
return err
}
if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", followID); err != nil {
return err
}
if _, err = sess.Exec("UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", userID); err != nil {
return err
}
return sess.Commit()
}
// UnfollowUser unmarks someone as another's follower.
func UnfollowUser(userID, followID int64) (err error) {
if userID == followID || !IsFollowing(userID, followID) {
return nil
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Delete(&Follow{UserID: userID, FollowID: followID}); err != nil {
return err
}
if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?", followID); err != nil {
return err
}
if _, err = sess.Exec("UPDATE `user` SET num_following = num_following - 1 WHERE id = ?", userID); err != nil {
return err
}
return sess.Commit()
}
// GetStarredRepos returns the repos starred by a particular user
func GetStarredRepos(userID int64, private bool) ([]*Repository, error) {
sess := x.Where("star.uid=?", userID).

70
models/user_follow.go Normal file
View File

@@ -0,0 +1,70 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
// Follow represents relations of user and his/her followers.
type Follow struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(follow)"`
FollowID int64 `xorm:"UNIQUE(follow)"`
}
// IsFollowing returns true if user is following followID.
func IsFollowing(userID, followID int64) bool {
has, _ := x.Get(&Follow{UserID: userID, FollowID: followID})
return has
}
// FollowUser marks someone be another's follower.
func FollowUser(userID, followID int64) (err error) {
if userID == followID || IsFollowing(userID, followID) {
return nil
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Insert(&Follow{UserID: userID, FollowID: followID}); err != nil {
return err
}
if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?", followID); err != nil {
return err
}
if _, err = sess.Exec("UPDATE `user` SET num_following = num_following + 1 WHERE id = ?", userID); err != nil {
return err
}
return sess.Commit()
}
// UnfollowUser unmarks someone as another's follower.
func UnfollowUser(userID, followID int64) (err error) {
if userID == followID || !IsFollowing(userID, followID) {
return nil
}
sess := x.NewSession()
defer sessionRelease(sess)
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Delete(&Follow{UserID: userID, FollowID: followID}); err != nil {
return err
}
if _, err = sess.Exec("UPDATE `user` SET num_followers = num_followers - 1 WHERE id = ?", followID); err != nil {
return err
}
if _, err = sess.Exec("UPDATE `user` SET num_following = num_following - 1 WHERE id = ?", userID); err != nil {
return err
}
return sess.Commit()
}

View File

@@ -0,0 +1,45 @@
package models
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsFollowing(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
assert.True(t, IsFollowing(4, 2))
assert.False(t, IsFollowing(2, 4))
assert.False(t, IsFollowing(5, NonexistentID))
assert.False(t, IsFollowing(NonexistentID, 5))
assert.False(t, IsFollowing(NonexistentID, NonexistentID))
}
func TestFollowUser(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(followerID, followedID int64) {
assert.NoError(t, FollowUser(followerID, followedID))
AssertExistsAndLoadBean(t, &Follow{UserID: followerID, FollowID: followedID})
}
testSuccess(4, 2)
testSuccess(5, 2)
assert.NoError(t, FollowUser(2, 2))
CheckConsistencyFor(t, &User{})
}
func TestUnfollowUser(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
testSuccess := func(followerID, followedID int64) {
assert.NoError(t, UnfollowUser(followerID, followedID))
AssertNotExistsBean(t, &Follow{UserID: followerID, FollowID: followedID})
}
testSuccess(4, 2)
testSuccess(5, 2)
testSuccess(2, 2)
CheckConsistencyFor(t, &User{})
}

124
models/user_openid.go Normal file
View File

@@ -0,0 +1,124 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"errors"
"code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/log"
)
var (
// ErrOpenIDNotExist openid is not known
ErrOpenIDNotExist = errors.New("OpenID is unknown")
)
// UserOpenID is the list of all OpenID identities of a user.
type UserOpenID struct {
ID int64 `xorm:"pk autoincr"`
UID int64 `xorm:"INDEX NOT NULL"`
URI string `xorm:"UNIQUE NOT NULL"`
Show bool `xorm:"DEFAULT false"`
}
// GetUserOpenIDs returns all openid addresses that belongs to given user.
func GetUserOpenIDs(uid int64) ([]*UserOpenID, error) {
openids := make([]*UserOpenID, 0, 5)
if err := x.
Where("uid=?", uid).
Asc("id").
Find(&openids); err != nil {
return nil, err
}
return openids, nil
}
func isOpenIDUsed(e Engine, uri string) (bool, error) {
if len(uri) == 0 {
return true, nil
}
return e.Get(&UserOpenID{URI: uri})
}
// IsOpenIDUsed returns true if the openid has been used.
func IsOpenIDUsed(openid string) (bool, error) {
return isOpenIDUsed(x, openid)
}
// NOTE: make sure openid.URI is normalized already
func addUserOpenID(e Engine, openid *UserOpenID) error {
used, err := isOpenIDUsed(e, openid.URI)
if err != nil {
return err
} else if used {
return ErrOpenIDAlreadyUsed{openid.URI}
}
_, err = e.Insert(openid)
return err
}
// AddUserOpenID adds an pre-verified/normalized OpenID URI to given user.
func AddUserOpenID(openid *UserOpenID) error {
return addUserOpenID(x, openid)
}
// DeleteUserOpenID deletes an openid address of given user.
func DeleteUserOpenID(openid *UserOpenID) (err error) {
var deleted int64
// ask to check UID
var address = UserOpenID{
UID: openid.UID,
}
if openid.ID > 0 {
deleted, err = x.Id(openid.ID).Delete(&address)
} else {
deleted, err = x.
Where("openid=?", openid.URI).
Delete(&address)
}
if err != nil {
return err
} else if deleted != 1 {
return ErrOpenIDNotExist
}
return nil
}
// ToggleUserOpenIDVisibility toggles visibility of an openid address of given user.
func ToggleUserOpenIDVisibility(id int64) (err error) {
_, err = x.Exec("update user_open_id set show = not show where id = ?", id)
return err
}
// GetUserByOpenID returns the user object by given OpenID if exists.
func GetUserByOpenID(uri string) (*User, error) {
if len(uri) == 0 {
return nil, ErrUserNotExist{0, uri, 0}
}
uri, err := openid.Normalize(uri)
if err != nil {
return nil, err
}
log.Trace("Normalized OpenID URI: " + uri)
// Otherwise, check in openid table
oid := &UserOpenID{URI: uri}
has, err := x.Get(oid)
if err != nil {
return nil, err
}
if has {
return GetUserByID(oid.UID)
}
return nil, ErrUserNotExist{0, uri, 0}
}

View File

@@ -0,0 +1,82 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetUserOpenIDs(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
oids, err := GetUserOpenIDs(int64(1))
if assert.NoError(t, err) {
assert.Len(t, oids, 2)
assert.Equal(t, oids[0].URI, "https://user1.domain1.tld/")
assert.False(t, oids[0].Show)
assert.Equal(t, oids[1].URI, "http://user1.domain2.tld/")
assert.True(t, oids[1].Show)
}
oids, err = GetUserOpenIDs(int64(2))
if assert.NoError(t, err) {
assert.Len(t, oids, 1)
assert.Equal(t, oids[0].URI, "https://domain1.tld/user2/")
assert.True(t, oids[0].Show)
}
}
func TestGetUserByOpenID(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
user, err := GetUserByOpenID("https://unknown")
if assert.Error(t, err) {
assert.True(t, IsErrUserNotExist(err))
}
user, err = GetUserByOpenID("https://user1.domain1.tld")
if assert.NoError(t, err) {
assert.Equal(t, user.ID, int64(1))
}
user, err = GetUserByOpenID("https://domain1.tld/user2/")
if assert.NoError(t, err) {
assert.Equal(t, user.ID, int64(2))
}
}
func TestToggleUserOpenIDVisibility(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())
oids, err := GetUserOpenIDs(int64(2))
if !assert.NoError(t, err) {
return
}
assert.Len(t, oids, 1)
assert.True(t, oids[0].Show)
err = ToggleUserOpenIDVisibility(oids[0].ID)
if !assert.NoError(t, err) {
return
}
oids, err = GetUserOpenIDs(int64(2))
if assert.NoError(t, err) {
assert.Len(t, oids, 1)
assert.False(t, oids[0].Show)
}
err = ToggleUserOpenIDVisibility(oids[0].ID)
if !assert.NoError(t, err) {
return
}
oids, err = GetUserOpenIDs(int64(2))
if !assert.NoError(t, err) {
return
}
assert.Len(t, oids, 1)
assert.True(t, oids[0].Show)
}

View File

@@ -475,11 +475,11 @@ func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) err
// check if repo belongs to org and append additional webhooks
if repo.MustOwner().IsOrganization() {
// get hooks for org
orgws, err := GetActiveWebhooksByOrgID(repo.OwnerID)
orgHooks, err := GetActiveWebhooksByOrgID(repo.OwnerID)
if err != nil {
return fmt.Errorf("GetActiveWebhooksByOrgID: %v", err)
}
ws = append(ws, orgws...)
ws = append(ws, orgHooks...)
}
if len(ws) == 0 {

View File

@@ -84,7 +84,11 @@ func (repo *Repository) LocalWikiPath() string {
func (repo *Repository) UpdateLocalWiki() error {
// Don't pass branch name here because it fails to clone and
// checkout to a specific branch when wiki is an empty repository.
return UpdateLocalCopyBranch(repo.WikiPath(), repo.LocalWikiPath(), "")
var branch = ""
if com.IsExist(repo.LocalWikiPath()) {
branch = "master"
}
return UpdateLocalCopyBranch(repo.WikiPath(), repo.LocalWikiPath(), branch)
}
func discardLocalWikiChanges(localPath string) error {

View File

@@ -0,0 +1,58 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package openid
import (
"sync"
"time"
"github.com/yohcop/openid-go"
)
type timedDiscoveredInfo struct {
info openid.DiscoveredInfo
time time.Time
}
type timedDiscoveryCache struct {
cache map[string]timedDiscoveredInfo
ttl time.Duration
mutex *sync.Mutex
}
func newTimedDiscoveryCache(ttl time.Duration) *timedDiscoveryCache {
return &timedDiscoveryCache{cache: map[string]timedDiscoveredInfo{}, ttl: ttl, mutex: &sync.Mutex{}}
}
func (s *timedDiscoveryCache) Put(id string, info openid.DiscoveredInfo) {
s.mutex.Lock()
defer s.mutex.Unlock()
s.cache[id] = timedDiscoveredInfo{info: info, time: time.Now()}
}
// Delete timed-out cache entries
func (s *timedDiscoveryCache) cleanTimedOut() {
now := time.Now()
for k, e := range s.cache {
diff := now.Sub(e.time)
if diff > s.ttl {
delete(s.cache, k)
}
}
}
func (s *timedDiscoveryCache) Get(id string) openid.DiscoveredInfo {
s.mutex.Lock()
defer s.mutex.Unlock()
// Delete old cached while we are at it.
s.cleanTimedOut()
if info, has := s.cache[id]; has {
return info.info
}
return nil
}

View File

@@ -0,0 +1,48 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package openid
import (
"testing"
"time"
)
type testDiscoveredInfo struct{}
func (s *testDiscoveredInfo) ClaimedID() string {
return "claimedID"
}
func (s *testDiscoveredInfo) OpEndpoint() string {
return "opEndpoint"
}
func (s *testDiscoveredInfo) OpLocalID() string {
return "opLocalID"
}
func TestTimedDiscoveryCache(t *testing.T) {
dc := newTimedDiscoveryCache(1 * time.Second)
// Put some initial values
dc.Put("foo", &testDiscoveredInfo{}) //openid.opEndpoint: "a", openid.opLocalID: "b", openid.claimedID: "c"})
// Make sure we can retrieve them
if di := dc.Get("foo"); di == nil {
t.Errorf("Expected a result, got nil")
} else if di.OpEndpoint() != "opEndpoint" || di.OpLocalID() != "opLocalID" || di.ClaimedID() != "claimedID" {
t.Errorf("Expected opEndpoint opLocalID claimedID, got %v %v %v", di.OpEndpoint(), di.OpLocalID(), di.ClaimedID())
}
// Attempt to get a non-existent value
if di := dc.Get("bar"); di != nil {
t.Errorf("Expected nil, got %v", di)
}
// Sleep one second and try retrive again
time.Sleep(1 * time.Second)
if di := dc.Get("foo"); di != nil {
t.Errorf("Expected a nil, got a result")
}
}

View File

@@ -0,0 +1,35 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package openid
import (
"github.com/yohcop/openid-go"
"time"
)
// For the demo, we use in-memory infinite storage nonce and discovery
// cache. In your app, do not use this as it will eat up memory and
// never
// free it. Use your own implementation, on a better database system.
// If you have multiple servers for example, you may need to share at
// least
// the nonceStore between them.
var nonceStore = openid.NewSimpleNonceStore()
var discoveryCache = newTimedDiscoveryCache(24 * time.Hour)
// Verify handles response from OpenID provider
func Verify(fullURL string) (id string, err error) {
return openid.Verify(fullURL, discoveryCache, nonceStore)
}
// Normalize normalizes an OpenID URI
func Normalize(url string) (id string, err error) {
return openid.Normalize(url)
}
// RedirectURL redirects browser
func RedirectURL(id, callbackURL, realm string) (string, error) {
return openid.RedirectURL(id, callbackURL, realm)
}

View File

@@ -21,7 +21,7 @@ type CreateOrgForm struct {
OrgName string `binding:"Required;AlphaDashDot;MaxSize(35)" locale:"org.org_name_holder"`
}
// Validate valideates the fields
// Validate validates the fields
func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -36,7 +36,7 @@ type UpdateOrgSettingForm struct {
MaxRepoCreation int
}
// Validate valideates the fields
// Validate validates the fields
func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -55,7 +55,7 @@ type CreateTeamForm struct {
Permission string
}
// Validate valideates the fields
// Validate validates the fields
func (f *CreateTeamForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}

View File

@@ -33,7 +33,7 @@ type CreateRepoForm struct {
Readme string
}
// Validate valideates the fields
// Validate validates the fields
func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -50,7 +50,7 @@ type MigrateRepoForm struct {
Description string `json:"description" binding:"MaxSize(255)"`
}
// Validate valideates the fields
// Validate validates the fields
func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -105,7 +105,7 @@ type RepoSettingForm struct {
EnablePulls bool
}
// Validate valideates the fields
// Validate validates the fields
func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -149,7 +149,7 @@ type NewWebhookForm struct {
WebhookForm
}
// Validate valideates the fields
// Validate validates the fields
func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -164,7 +164,7 @@ type NewSlackHookForm struct {
WebhookForm
}
// Validate valideates the fields
// Validate validates the fields
func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -186,7 +186,7 @@ type CreateIssueForm struct {
Files []string
}
// Validate valideates the fields
// Validate validates the fields
func (f *CreateIssueForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -198,7 +198,7 @@ type CreateCommentForm struct {
Files []string
}
// Validate valideates the fields
// Validate validates the fields
func (f *CreateCommentForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -217,7 +217,7 @@ type CreateMilestoneForm struct {
Deadline string
}
// Validate valideates the fields
// Validate validates the fields
func (f *CreateMilestoneForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -236,7 +236,7 @@ type CreateLabelForm struct {
Color string `binding:"Required;Size(7)" locale:"repo.issues.label_color"`
}
// Validate valideates the fields
// Validate validates the fields
func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -246,7 +246,7 @@ type InitializeLabelsForm struct {
TemplateName string `binding:"Required"`
}
// Validate valideates the fields
// Validate validates the fields
func (f *InitializeLabelsForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -269,7 +269,7 @@ type NewReleaseForm struct {
Files []string
}
// Validate valideates the fields
// Validate validates the fields
func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -283,7 +283,7 @@ type EditReleaseForm struct {
Files []string
}
// Validate valideates the fields
// Validate validates the fields
func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -303,7 +303,7 @@ type NewWikiForm struct {
Message string
}
// Validate valideates the fields
// Validate validates the fields
// FIXME: use code generation to generate this method.
func (f *NewWikiForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
@@ -327,7 +327,7 @@ type EditRepoFileForm struct {
LastCommit string
}
// Validate valideates the fields
// Validate validates the fields
func (f *EditRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -337,7 +337,7 @@ type EditPreviewDiffForm struct {
Content string
}
// Validate valideates the fields
// Validate validates the fields
func (f *EditPreviewDiffForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -360,7 +360,7 @@ type UploadRepoFileForm struct {
Files []string
}
// Validate valideates the fields
// Validate validates the fields
func (f *UploadRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -370,7 +370,7 @@ type RemoveUploadFileForm struct {
File string `binding:"Required;MaxSize(50)"`
}
// Validate valideates the fields
// Validate validates the fields
func (f *RemoveUploadFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -390,7 +390,7 @@ type DeleteRepoFileForm struct {
NewBranchName string `binding:"AlphaDashDot;MaxSize(100)"`
}
// Validate valideates the fields
// Validate validates the fields
func (f *DeleteRepoFileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}

View File

@@ -53,7 +53,7 @@ type InstallForm struct {
AdminEmail string `binding:"OmitEmpty;MinSize(3);MaxSize(254);Include(@)" locale:"install.admin_email"`
}
// Validate valideates the fields
// Validate validates the fields
func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -78,7 +78,7 @@ func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) bindi
return validate(errs, ctx.Data, f, ctx.Locale)
}
// SignInForm form for signing in
// SignInForm form for signing in with user/password
type SignInForm struct {
UserName string `binding:"Required;MaxSize(254)"`
Password string `binding:"Required;MaxSize(255)"`
@@ -107,7 +107,7 @@ type UpdateProfileForm struct {
Location string `binding:"MaxSize(50)"`
}
// Validate valideates the fields
// Validate validates the fields
func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -126,7 +126,7 @@ type AvatarForm struct {
Federavatar bool
}
// Validate valideates the fields
// Validate validates the fields
func (f *AvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -136,7 +136,7 @@ type AddEmailForm struct {
Email string `binding:"Required;Email;MaxSize(254)"`
}
// Validate valideates the fields
// Validate validates the fields
func (f *AddEmailForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -148,18 +148,28 @@ type ChangePasswordForm struct {
Retype string `form:"retype"`
}
// Validate valideates the fields
// Validate validates the fields
func (f *ChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
// AddOpenIDForm is for changing openid uri
type AddOpenIDForm struct {
Openid string `binding:"Required;MaxSize(256)"`
}
// Validate validates the fields
func (f *AddOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
// AddSSHKeyForm form for adding SSH key
type AddSSHKeyForm struct {
Title string `binding:"Required;MaxSize(50)"`
Content string `binding:"Required"`
}
// Validate valideates the fields
// Validate validates the fields
func (f *AddSSHKeyForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
@@ -179,7 +189,7 @@ type TwoFactorAuthForm struct {
Passcode string `binding:"Required"`
}
// Validate valideates the fields
// Validate validates the fields
func (f *TwoFactorAuthForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}

View File

@@ -0,0 +1,43 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"github.com/go-macaron/binding"
"gopkg.in/macaron.v1"
)
// SignInOpenIDForm form for signing in with OpenID
type SignInOpenIDForm struct {
Openid string `binding:"Required;MaxSize(256)"`
Remember bool
}
// Validate valideates the fields
func (f *SignInOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
// SignUpOpenIDForm form for signin up with OpenID
type SignUpOpenIDForm struct {
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
Email string `binding:"Required;Email;MaxSize(254)"`
}
// Validate valideates the fields
func (f *SignUpOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}
// ConnectOpenIDForm form for connecting an existing account to an OpenID URI
type ConnectOpenIDForm struct {
UserName string `binding:"Required;MaxSize(254)"`
Password string `binding:"Required;MaxSize(255)"`
}
// Validate valideates the fields
func (f *ConnectOpenIDForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
return validate(errs, ctx.Data, f, ctx.Locale)
}

View File

@@ -43,7 +43,7 @@ func (ctx *APIContext) Error(status int, title string, obj interface{}) {
})
}
// SetLinkHeader sets pagination link header by given totol number and page size.
// SetLinkHeader sets pagination link header by given total number and page size.
func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
page := paginater.New(total, pageSize, ctx.QueryInt("page"), 0)
links := make([]string, 0, 4)

View File

@@ -164,7 +164,7 @@ func Contexter() macaron.Handler {
ctx.Data["PageStartTime"] = time.Now()
// Get user from session if logined.
// Get user from session if logged in.
ctx.User, ctx.IsBasicAuth = auth.SignedInUser(ctx.Context, ctx.Session)
if ctx.User != nil {
@@ -197,6 +197,7 @@ func Contexter() macaron.Handler {
ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding
ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion
ctx.Data["EnableOpenIDSignIn"] = setting.EnableOpenIDSignIn
c.Map(ctx)
}

View File

@@ -12,7 +12,6 @@ import (
"code.gitea.io/git"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/Unknwon/com"
editorconfig "gopkg.in/editorconfig/editorconfig-core-go.v1"
@@ -27,7 +26,7 @@ type PullRequest struct {
HeadInfo string // [<user>:]<branch>
}
// Repository contains informations to operate a repository
// Repository contains information to operate a repository
type Repository struct {
AccessMode models.AccessMode
IsWatching bool
@@ -154,15 +153,8 @@ func RedirectToRepo(ctx *Context, redirectRepoID int64) {
}
// RepoAssignment returns a macaron to handle repository assignment
func RepoAssignment(args ...bool) macaron.Handler {
func RepoAssignment() macaron.Handler {
return func(ctx *Context) {
var (
displayBare bool // To display bare page if it is a bare repo.
)
if len(args) >= 1 {
displayBare = args[0]
}
var (
owner *models.User
err error
@@ -219,7 +211,11 @@ func RepoAssignment(args ...bool) macaron.Handler {
if ctx.IsSigned && ctx.User.IsAdmin {
ctx.Repo.AccessMode = models.AccessModeOwner
} else {
mode, err := models.AccessLevel(ctx.User, repo)
var userID int64
if ctx.User != nil {
userID = ctx.User.ID
}
mode, err := models.AccessLevel(userID, repo)
if err != nil {
ctx.Handle(500, "AccessLevel", err)
return
@@ -290,15 +286,7 @@ func RepoAssignment(args ...bool) macaron.Handler {
// repo is bare and display enable
if ctx.Repo.Repository.IsBare {
log.Debug("Bare repository: %s", ctx.Repo.RepoLink)
// NOTE: to prevent templating error
ctx.Data["BranchName"] = ""
if displayBare {
if !ctx.Repo.IsAdmin() {
ctx.Flash.Info(ctx.Tr("repo.repo_is_empty"), true)
}
ctx.HTML(200, "repo/bare")
}
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
return
}
@@ -330,7 +318,7 @@ func RepoAssignment(args ...bool) macaron.Handler {
}
}
// People who have push access or have fored repository can propose a new pull request.
// People who have push access or have forked repository can propose a new pull request.
if ctx.Repo.IsWriter() || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID)) {
// Pull request is allowed if this is a fork repository
// and base repository accepts pull requests.

View File

@@ -438,7 +438,7 @@ func (r *Request) ToXML(v interface{}) error {
return err
}
// Response executes request client gets response mannually.
// Response executes request client gets response manually.
func (r *Request) Response() (*http.Response, error) {
return r.getResponse()
}

View File

@@ -20,7 +20,7 @@ type ContentStore struct {
BasePath string
}
// Get takes a Meta object and retreives the content from the store, returning
// Get takes a Meta object and retrieves the content from the store, returning
// it as an io.Reader. If fromByte > 0, the reader starts from that byte
func (s *ContentStore) Get(meta *models.LFSMetaObject, fromByte int64) (io.ReadCloser, error) {
path := filepath.Join(s.BasePath, transformKey(meta.Oid))

View File

@@ -50,7 +50,7 @@ type BatchResponse struct {
Objects []*Representation `json:"objects"`
}
// Representation is object medata as seen by clients of the lfs server.
// Representation is object metadata as seen by clients of the lfs server.
type Representation struct {
Oid string `json:"oid"`
Size int64 `json:"size"`
@@ -463,7 +463,7 @@ func authenticate(ctx *context.Context, repository *models.Repository, authoriza
}
if ctx.IsSigned {
accessCheck, _ := models.HasAccess(ctx.User, repository, accessMode)
accessCheck, _ := models.HasAccess(ctx.User.ID, repository, accessMode)
return accessCheck
}
@@ -499,7 +499,7 @@ func authenticate(ctx *context.Context, repository *models.Repository, authoriza
return false
}
accessCheck, _ := models.HasAccess(userModel, repository, accessMode)
accessCheck, _ := models.HasAccess(userModel.ID, repository, accessMode)
return accessCheck
}

View File

@@ -42,7 +42,7 @@ func (cw *ConnWriter) WriteMsg(msg string, skip, level int) error {
if cw.Level > level {
return nil
}
if cw.neddedConnectOnMsg() {
if cw.neededConnectOnMsg() {
if err := cw.connect(); err != nil {
return err
}
@@ -87,7 +87,7 @@ func (cw *ConnWriter) connect() error {
return nil
}
func (cw *ConnWriter) neddedConnectOnMsg() bool {
func (cw *ConnWriter) neededConnectOnMsg() bool {
if cw.Reconnect {
cw.Reconnect = false
return true

View File

@@ -84,7 +84,7 @@ func Info(format string, v ...interface{}) {
}
}
// Warn records warnning log
// Warn records warning log
func Warn(format string, v ...interface{}) {
for _, logger := range loggers {
logger.Warn(format, v...)
@@ -275,7 +275,7 @@ func (l *Logger) StartLogger() {
}
}
// Flush flushs all chan data.
// Flush flushes all chan data.
func (l *Logger) Flush() {
for _, l := range l.outputs {
l.Flush()
@@ -321,7 +321,7 @@ func (l *Logger) Info(format string, v ...interface{}) {
l.writerMsg(0, INFO, msg)
}
// Warn records warnning log
// Warn records warning log
func (l *Logger) Warn(format string, v ...interface{}) {
msg := fmt.Sprintf("[W] "+format, v...)
l.writerMsg(0, WARN, msg)

View File

@@ -101,7 +101,7 @@ func (l *XORMLogBridge) Infof(format string, v ...interface{}) {
}
}
// Warn show warnning log
// Warn show warning log
func (l *XORMLogBridge) Warn(v ...interface{}) {
if l.level <= core.LOG_WARNING {
msg := fmt.Sprint(v...)

View File

@@ -146,7 +146,7 @@ func (s *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
}
}
// If not using SMTPS, alway use STARTTLS if available
// If not using SMTPS, always use STARTTLS if available
hasStartTLS, _ := client.Extension("STARTTLS")
if !isSecureConn && hasStartTLS {
if err = client.StartTLS(tlsconfig); err != nil {

View File

@@ -76,7 +76,7 @@ func License(name string) ([]byte, error) {
return fileFromDir(path.Join("license", name))
}
// Labels eads the content of a specific labels from static or custom path.
// Labels reads the content of a specific labels from static or custom path.
func Labels(name string) ([]byte, error) {
return fileFromDir(path.Join("label", name))
}

View File

@@ -72,7 +72,7 @@ func License(name string) ([]byte, error) {
return fileFromDir(path.Join("license", name))
}
// Labels eads the content of a specific labels from static or custom path.
// Labels reads the content of a specific labels from static or custom path.
func Labels(name string) ([]byte, error) {
return fileFromDir(path.Join("label", name))
}

View File

@@ -15,6 +15,7 @@ import (
"os/exec"
"path"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
@@ -120,6 +121,12 @@ var (
MinPasswordLength int
ImportLocalPaths bool
// OpenID settings
EnableOpenIDSignIn bool
EnableOpenIDSignUp bool
OpenIDWhitelist []*regexp.Regexp
OpenIDBlacklist []*regexp.Regexp
// Database settings
UseSQLite3 bool
UseMySQL bool
@@ -249,7 +256,7 @@ var (
},
}
// Markdown sttings
// Markdown settings
Markdown = struct {
EnableHardLineBreak bool
CustomURLSchemes []string `ini:"CUSTOM_URL_SCHEMES"`
@@ -420,7 +427,7 @@ var (
Names []string
dateLangs map[string]string
// Highlight settings are loaded in modules/template/hightlight.go
// Highlight settings are loaded in modules/template/highlight.go
// Other settings
ShowFooterBranding bool
@@ -537,10 +544,6 @@ func NewContext() {
Cfg = ini.Empty()
if err != nil {
log.Fatal(4, "Failed to parse 'app.ini': %v", err)
}
CustomPath = os.Getenv("GITEA_CUSTOM")
if len(CustomPath) == 0 {
// For backward compatibility
@@ -755,6 +758,24 @@ please consider changing to GITEA_CUSTOM`)
MinPasswordLength = sec.Key("MIN_PASSWORD_LENGTH").MustInt(6)
ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false)
sec = Cfg.Section("openid")
EnableOpenIDSignIn = sec.Key("ENABLE_OPENID_SIGNIN").MustBool(true)
EnableOpenIDSignUp = sec.Key("ENABLE_OPENID_SIGNUP").MustBool(true)
pats := sec.Key("WHITELISTED_URIS").Strings(" ")
if len(pats) != 0 {
OpenIDWhitelist = make([]*regexp.Regexp, len(pats))
for i, p := range pats {
OpenIDWhitelist[i] = regexp.MustCompilePOSIX(p)
}
}
pats = sec.Key("BLACKLISTED_URIS").Strings(" ")
if len(pats) != 0 {
OpenIDBlacklist = make([]*regexp.Regexp, len(pats))
for i, p := range pats {
OpenIDBlacklist[i] = regexp.MustCompilePOSIX(p)
}
}
sec = Cfg.Section("attachment")
AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments"))
if !filepath.IsAbs(AttachmentPath) {

View File

@@ -39,7 +39,7 @@ func NewExclusivePool() *ExclusivePool {
}
// CheckIn checks in an instance to the pool and hangs while instance
// with same indentity is using the lock.
// with same identity is using the lock.
func (p *ExclusivePool) CheckIn(identity string) {
p.lock.Lock()

View File

@@ -36,7 +36,7 @@ func (q *UniqueQueue) Queue() <-chan string {
return q.queue
}
// Exist returns true if there is an instance with given indentity
// Exist returns true if there is an instance with given identity
// exists in the queue.
func (q *UniqueQueue) Exist(id interface{}) bool {
return q.table.IsRunning(com.ToStr(id))

View File

@@ -2,10 +2,18 @@
bin/
bin-debug/
bin-release/
[Oo]bj/ # FlashDevelop obj
[Bb]in/ # FlashDevelop bin
# Other files and folders
.settings/
# Executables
*.swf
*.air
*.ipa
*.apk
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
# should NOT be excluded as they contain compiler settings and other important
# information for Eclipse / Flash Builder.

View File

@@ -2,7 +2,7 @@
*.apk
*.ap_
# Files for the Dalvik VM
# Files for the ART/Dalvik VM
*.dex
# Java class files
@@ -11,6 +11,7 @@
# Generated files
bin/
gen/
out/
# Gradle files
.gradle/
@@ -30,3 +31,25 @@ proguard/
# Android Studio captures folder
captures/
# Intellij
*.iml
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
.idea/dictionaries
.idea/libraries
# Keystore files
*.jks
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Google Services (e.g. APIs or Firebase)
google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json

View File

@@ -0,0 +1 @@
*.retry

View File

@@ -1,14 +1,33 @@
# http://www.gnu.org/software/automake
Makefile.in
/ar-lib
/mdate-sh
/py-compile
/test-driver
/ylwrap
# http://www.gnu.org/software/autoconf
/autom4te.cache
/autoscan.log
/autoscan-*.log
/aclocal.m4
/compile
/config.guess
/config.h.in
/config.sub
/configure
/configure.scan
/depcomp
/install-sh
/missing
/stamp-h1
# https://www.gnu.org/software/libtool/
/ltmain.sh
# http://www.gnu.org/software/texinfo
/texinfo.tex

2
options/gitignore/Bazaar Normal file
View File

@@ -0,0 +1,2 @@
.bzr/
.bzrignore

View File

@@ -1,9 +1,17 @@
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
@@ -30,3 +38,14 @@
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
modules.order
Module.symvers
Mkfile.old
dkms.conf

Some files were not shown because too many files have changed in this diff Show More