Browse Source

Merge pull request #68 from gogits/dev

Dev
pull/71/merge
无闻 11 years ago
parent
commit
2577940c30
  1. 13
      .fswatch.json
  2. 42
      .gopmfile
  3. 6
      README.md
  4. 4
      README_ZH.md
  5. 2
      bee.json
  6. 14
      conf/app.ini
  7. 2
      gogs.go
  8. 11
      models/access.go
  9. 19
      models/models.go
  10. 33
      models/oauth2.go
  11. 4
      models/publickey.go
  12. 57
      models/repo.go
  13. 75
      models/user.go
  14. 92
      modules/base/conf.go
  15. 49
      modules/base/markdown.go
  16. 110
      modules/base/template.go
  17. 146
      modules/base/tool.go
  18. 2
      modules/log/log.go
  19. 53
      modules/mailer/mail.go
  20. 74
      modules/oauth2/oauth2.go
  21. 70
      public/css/gogs.css
  22. 47
      public/js/app.js
  23. 2
      routers/api/v1/miscellaneous.go
  24. 6
      routers/install.go
  25. 36
      routers/repo/issue.go
  26. 8
      routers/repo/release.go
  27. 99
      routers/repo/repo.go
  28. 7
      routers/user/setting.go
  29. 115
      routers/user/social.go
  30. 86
      routers/user/user.go
  31. 54
      serve.go
  32. 15
      start.sh
  33. 2
      templates/issue/create.tmpl
  34. 2
      templates/issue/view.tmpl
  35. 33
      templates/mail/auth/reset_passwd.tmpl
  36. 25
      templates/mail/auth/reset_password.html
  37. 66
      templates/release/new.tmpl
  38. 5
      templates/repo/setting.tmpl
  39. 2
      templates/repo/toolbar.tmpl
  40. 6
      templates/status/401.tmpl
  41. 30
      templates/user/forgot_passwd.tmpl
  42. 26
      templates/user/reset_passwd.tmpl
  43. 5
      templates/user/setting.tmpl
  44. 7
      templates/user/signin.tmpl
  45. 56
      update.go
  46. 50
      web.go

13
.fswatch.json

@ -0,0 +1,13 @@
{
"paths": ["."],
"depth": 2,
"exclude": [],
"include": ["\\.go$"],
"command": [
"bash", "-c", "go build && ./gogs web"
],
"env": {
"POWERED_BY": "github.com/shxsun/fswatch"
},
"enable-restart": true
}

42
.gopmfile

@ -1,28 +1,26 @@
[target] [target]
path=github.com/gogits/gogs path = github.com/gogits/gogs
[deps] [deps]
github.com/codegangsta/cli= github.com/codegangsta/cli =
github.com/go-martini/martini= github.com/go-martini/martini =
github.com/Unknwon/com= github.com/Unknwon/com =
github.com/Unknwon/cae= github.com/Unknwon/cae =
github.com/Unknwon/goconfig= github.com/Unknwon/goconfig =
github.com/dchest/scrypt= github.com/nfnt/resize =
github.com/nfnt/resize= github.com/lunny/xorm =
github.com/lunny/xorm= github.com/go-sql-driver/mysql =
github.com/go-sql-driver/mysql= github.com/lib/pq =
github.com/lib/pq= github.com/qiniu/log =
github.com/gogits/logs= code.google.com/p/goauth2 =
github.com/gogits/binding= github.com/gogits/logs =
github.com/gogits/git= github.com/gogits/binding =
github.com/gogits/gfm= github.com/gogits/git =
github.com/gogits/cache= github.com/gogits/gfm =
github.com/gogits/session= github.com/gogits/cache =
github.com/gogits/webdav= github.com/gogits/session =
github.com/martini-contrib/oauth2= github.com/gogits/webdav =
github.com/martini-contrib/sessions=
code.google.com/p/goauth2=
[res] [res]
include=templates|public|conf include = templates|public|conf

6
README.md

@ -5,9 +5,9 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language
![Demo](http://gowalker.org/public/gogs_demo.gif) ![Demo](http://gowalker.org/public/gogs_demo.gif)
##### Current version: 0.2.0 Alpha ##### Current version: 0.2.2 Alpha
#### Due to testing purpose, data of [try.gogits.org](http://try.gogits.org) has been reset in March 29, 2014 and will reset multiple times after. Please do NOT put your important data on the site. #### Due to testing purpose, data of [try.gogits.org](http://try.gogits.org) has been reset in April 6, 2014 and will reset multiple times after. Please do NOT put your important data on the site.
#### Other language version #### Other language version
@ -31,7 +31,7 @@ More importantly, Gogs only needs one binary to setup your own project hosting o
- Activity timeline - Activity timeline
- SSH/HTTPS(Clone only) protocol support. - SSH/HTTPS(Clone only) protocol support.
- Register/delete/rename account. - Register/delete/rename account.
- Create/delete/watch/rename public repository. - Create/delete/watch/rename/transfer public repository.
- Repository viewer. - Repository viewer.
- Issue tracker. - Issue tracker.
- Gravatar and cache support. - Gravatar and cache support.

4
README_ZH.md

@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
![Demo](http://gowalker.org/public/gogs_demo.gif) ![Demo](http://gowalker.org/public/gogs_demo.gif)
##### 当前版本:0.2.0 Alpha ##### 当前版本:0.2.2 Alpha
## 开发目的 ## 开发目的
@ -25,7 +25,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
- 活动时间线 - 活动时间线
- SSH/HTTPS(仅限 Clone) 协议支持 - SSH/HTTPS(仅限 Clone) 协议支持
- 注册/删除/重命名用户 - 注册/删除/重命名用户
- 创建/删除/关注/重命名公开仓库 - 创建/删除/关注/重命名/转移公开仓库
- 仓库浏览器 - 仓库浏览器
- Bug 追踪系统 - Bug 追踪系统
- Gravatar 以及缓存支持 - Gravatar 以及缓存支持

2
bee.json

@ -13,6 +13,8 @@
"others": [ "others": [
"modules", "modules",
"$GOPATH/src/github.com/gogits/binding", "$GOPATH/src/github.com/gogits/binding",
"$GOPATH/src/github.com/gogits/webdav",
"$GOPATH/src/github.com/gogits/logs",
"$GOPATH/src/github.com/gogits/git", "$GOPATH/src/github.com/gogits/git",
"$GOPATH/src/github.com/gogits/gfm" "$GOPATH/src/github.com/gogits/gfm"
] ]

14
conf/app.ini

@ -12,10 +12,13 @@ LANG_IGNS = Google Go|C|C++|Python|Ruby|C Sharp
LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|BSD (3-Clause) License LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|BSD (3-Clause) License
[server] [server]
PROTOCOL = http
DOMAIN = localhost DOMAIN = localhost
ROOT_URL = http://%(DOMAIN)s:%(HTTP_PORT)s/ ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
HTTP_ADDR = HTTP_ADDR =
HTTP_PORT = 3000 HTTP_PORT = 3000
CERT_FILE = cert.pem
KEY_FILE = key.pem
[database] [database]
; Either "mysql", "postgres" or "sqlite3"(binary release only), it's your choice ; Either "mysql", "postgres" or "sqlite3"(binary release only), it's your choice
@ -69,6 +72,15 @@ FROM =
USER = USER =
PASSWD = PASSWD =
[oauth]
ENABLED = false
[oauth.github]
ENABLED =
CLIENT_ID =
CLIENT_SECRET =
SCOPES = https://api.github.com/user
[cache] [cache]
; Either "memory", "redis", or "memcache", default is "memory" ; Either "memory", "redis", or "memcache", default is "memory"
ADAPTER = memory ADAPTER = memory

2
gogs.go

@ -19,7 +19,7 @@ import (
// Test that go1.2 tag above is included in builds. main.go refers to this definition. // Test that go1.2 tag above is included in builds. main.go refers to this definition.
const go12tag = true const go12tag = true
const APP_VER = "0.2.0.0403 Alpha" const APP_VER = "0.2.2.0407 Alpha"
func init() { func init() {
base.AppVer = APP_VER base.AppVer = APP_VER

11
models/access.go

@ -7,6 +7,8 @@ package models
import ( import (
"strings" "strings"
"time" "time"
"github.com/lunny/xorm"
) )
// Access types. // Access types.
@ -40,6 +42,15 @@ func UpdateAccess(access *Access) error {
return err return err
} }
// UpdateAccess updates access information with session for rolling back.
func UpdateAccessWithSession(sess *xorm.Session, access *Access) error {
if _, err := sess.Id(access.Id).Update(access); err != nil {
sess.Rollback()
return err
}
return nil
}
// HasAccess returns true if someone can read or write to given repository. // HasAccess returns true if someone can read or write to given repository.
func HasAccess(userName, repoName string, mode int) (bool, error) { func HasAccess(userName, repoName string, mode int) (bool, error) {
return orm.Get(&Access{ return orm.Get(&Access{

19
models/models.go

@ -18,7 +18,9 @@ import (
) )
var ( var (
orm *xorm.Engine orm *xorm.Engine
tables []interface{}
HasEngine bool HasEngine bool
DbCfg struct { DbCfg struct {
@ -28,6 +30,11 @@ var (
UseSQLite3 bool UseSQLite3 bool
) )
func init() {
tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch),
new(Action), new(Access), new(Issue), new(Comment), new(Oauth2))
}
func LoadModelsConfig() { func LoadModelsConfig() {
DbCfg.Type = base.Cfg.MustValue("database", "DB_TYPE") DbCfg.Type = base.Cfg.MustValue("database", "DB_TYPE")
if DbCfg.Type == "sqlite3" { if DbCfg.Type == "sqlite3" {
@ -58,9 +65,7 @@ func NewTestEngine(x *xorm.Engine) (err error) {
if err != nil { if err != nil {
return fmt.Errorf("models.init(fail to conntect database): %v", err) return fmt.Errorf("models.init(fail to conntect database): %v", err)
} }
return x.Sync(tables...)
return x.Sync(new(User), new(PublicKey), new(Repository), new(Watch),
new(Action), new(Access), new(Issue), new(Comment))
} }
func SetEngine() (err error) { func SetEngine() (err error) {
@ -102,9 +107,9 @@ func SetEngine() (err error) {
func NewEngine() (err error) { func NewEngine() (err error) {
if err = SetEngine(); err != nil { if err = SetEngine(); err != nil {
return err return err
} else if err = orm.Sync(new(User), new(PublicKey), new(Repository), new(Watch), }
new(Action), new(Access), new(Issue), new(Comment)); err != nil { if err = orm.Sync(tables...); err != nil {
return fmt.Errorf("sync database struct error: %v", err) return fmt.Errorf("sync database struct error: %v\n", err)
} }
return nil return nil
} }

33
models/oauth2.go

@ -1,6 +1,6 @@
package models package models
import "time" import "fmt"
// OT: Oauth2 Type // OT: Oauth2 Type
const ( const (
@ -10,9 +10,30 @@ const (
) )
type Oauth2 struct { type Oauth2 struct {
Uid int64 `xorm:"pk"` // userId Uid int64 `xorm:"pk"` // userId
Type int `xorm:"pk unique(oauth)"` // twitter,github,google... Type int `xorm:"pk unique(oauth)"` // twitter,github,google...
Identity string `xorm:"pk unique(oauth)"` // id.. Identity string `xorm:"pk unique(oauth)"` // id..
Token string `xorm:"VARCHAR(200) not null"` Token string `xorm:"VARCHAR(200) not null"`
RefreshTime time.Time `xorm:"created"` //RefreshTime time.Time `xorm:"created"`
}
func AddOauth2(oa *Oauth2) (err error) {
if _, err = orm.Insert(oa); err != nil {
return err
}
return nil
}
func GetOauth2User(identity string) (u *User, err error) {
oa := &Oauth2{}
oa.Identity = identity
exists, err := orm.Get(oa)
if err != nil {
return
}
if !exists {
err = fmt.Errorf("not exists oauth2: %s", identity)
return
}
return GetUserById(oa.Uid)
} }

4
models/publickey.go

@ -77,8 +77,8 @@ func init() {
// PublicKey represents a SSH key of user. // PublicKey represents a SSH key of user.
type PublicKey struct { type PublicKey struct {
Id int64 Id int64
OwnerId int64 `xorm:" index not null"` OwnerId int64 `xorm:"unique(s) index not null"`
Name string `xorm:" not null"` //UNIQUE(s) Name string `xorm:"unique(s) not null"`
Fingerprint string Fingerprint string
Content string `xorm:"TEXT not null"` Content string `xorm:"TEXT not null"`
Created time.Time `xorm:"created"` Created time.Time `xorm:"created"`

57
models/repo.go

@ -138,11 +138,8 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
IsPrivate: private, IsPrivate: private,
IsBare: repoLang == "" && license == "" && !initReadme, IsBare: repoLang == "" && license == "" && !initReadme,
} }
repoPath := RepoPath(user.Name, repoName) repoPath := RepoPath(user.Name, repoName)
if err = initRepository(repoPath, user, repo, initReadme, repoLang, license); err != nil {
return nil, err
}
sess := orm.NewSession() sess := orm.NewSession()
defer sess.Close() defer sess.Close()
sess.Begin() sess.Begin()
@ -207,6 +204,10 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
log.Error("repo.CreateRepository(WatchRepo): %v", err) log.Error("repo.CreateRepository(WatchRepo): %v", err)
} }
if err = initRepository(repoPath, user, repo, initReadme, repoLang, license); err != nil {
return nil, err
}
return repo, nil return repo, nil
} }
@ -332,6 +333,11 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
return nil return nil
} }
// for update use
os.Setenv("userName", user.Name)
os.Setenv("userId", base.ToStr(user.Id))
os.Setenv("repoName", repo.Name)
// Apply changes and commit. // Apply changes and commit.
return initRepoCommit(tmpDir, user.NewGitSig()) return initRepoCommit(tmpDir, user.NewGitSig())
} }
@ -381,45 +387,62 @@ func TransferOwnership(user *User, newOwner string, repo *Repository) (err error
if err = orm.Find(&accesses, &Access{RepoName: user.LowerName + "/" + repo.LowerName}); err != nil { if err = orm.Find(&accesses, &Access{RepoName: user.LowerName + "/" + repo.LowerName}); err != nil {
return err return err
} }
sess := orm.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
for i := range accesses { for i := range accesses {
accesses[i].RepoName = newUser.LowerName + "/" + repo.LowerName accesses[i].RepoName = newUser.LowerName + "/" + repo.LowerName
if accesses[i].UserName == user.LowerName { if accesses[i].UserName == user.LowerName {
accesses[i].UserName = newUser.LowerName accesses[i].UserName = newUser.LowerName
} }
if err = UpdateAccess(&accesses[i]); err != nil { if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
return err return err
} }
} }
// Update repository. // Update repository.
repo.OwnerId = newUser.Id repo.OwnerId = newUser.Id
if _, err := orm.Id(repo.Id).Update(repo); err != nil { if _, err := sess.Id(repo.Id).Update(repo); err != nil {
sess.Rollback()
return err return err
} }
// Update user repository number. // Update user repository number.
rawSql := "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?" rawSql := "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?"
if _, err = orm.Exec(rawSql, newUser.Id); err != nil { if _, err = sess.Exec(rawSql, newUser.Id); err != nil {
sess.Rollback()
return err return err
} }
rawSql = "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?" rawSql = "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?"
if _, err = orm.Exec(rawSql, user.Id); err != nil { if _, err = sess.Exec(rawSql, user.Id); err != nil {
sess.Rollback()
return err return err
} }
// Add watch of new owner to repository. // Add watch of new owner to repository.
if !IsWatching(newUser.Id, repo.Id) { if !IsWatching(newUser.Id, repo.Id) {
if err = WatchRepo(newUser.Id, repo.Id, true); err != nil { if err = WatchRepo(newUser.Id, repo.Id, true); err != nil {
sess.Rollback()
return err return err
} }
} }
if err = TransferRepoAction(user, newUser, repo); err != nil { if err = TransferRepoAction(user, newUser, repo); err != nil {
sess.Rollback()
return err return err
} }
// Change repository directory name. // Change repository directory name.
return os.Rename(RepoPath(user.Name, repo.Name), RepoPath(newUser.Name, repo.Name)) if err = os.Rename(RepoPath(user.Name, repo.Name), RepoPath(newUser.Name, repo.Name)); err != nil {
sess.Rollback()
return err
}
return sess.Commit()
} }
// ChangeRepositoryName changes all corresponding setting from old repository name to new one. // ChangeRepositoryName changes all corresponding setting from old repository name to new one.
@ -429,15 +452,27 @@ func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error)
if err = orm.Find(&accesses, &Access{RepoName: strings.ToLower(userName + "/" + oldRepoName)}); err != nil { if err = orm.Find(&accesses, &Access{RepoName: strings.ToLower(userName + "/" + oldRepoName)}); err != nil {
return err return err
} }
sess := orm.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
for i := range accesses { for i := range accesses {
accesses[i].RepoName = userName + "/" + newRepoName accesses[i].RepoName = userName + "/" + newRepoName
if err = UpdateAccess(&accesses[i]); err != nil { if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
return err return err
} }
} }
// Change repository directory name. // Change repository directory name.
return os.Rename(RepoPath(userName, oldRepoName), RepoPath(userName, newRepoName)) if err = os.Rename(RepoPath(userName, oldRepoName), RepoPath(userName, newRepoName)); err != nil {
sess.Rollback()
return err
}
return sess.Commit()
} }
func UpdateRepository(repo *Repository) error { func UpdateRepository(repo *Repository) error {

75
models/user.go

@ -5,6 +5,7 @@
package models package models
import ( import (
"crypto/sha256"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
@ -13,8 +14,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/dchest/scrypt"
"github.com/gogits/git" "github.com/gogits/git"
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
@ -62,6 +61,7 @@ type User struct {
IsActive bool IsActive bool
IsAdmin bool IsAdmin bool
Rands string `xorm:"VARCHAR(10)"` Rands string `xorm:"VARCHAR(10)"`
Salt string `xorm:"VARCHAR(10)"`
Created time.Time `xorm:"created"` Created time.Time `xorm:"created"`
Updated time.Time `xorm:"updated"` Updated time.Time `xorm:"updated"`
} }
@ -89,10 +89,9 @@ func (user *User) NewGitSig() *git.Signature {
} }
// EncodePasswd encodes password to safe format. // EncodePasswd encodes password to safe format.
func (user *User) EncodePasswd() error { func (user *User) EncodePasswd() {
newPasswd, err := scrypt.Key([]byte(user.Passwd), []byte(base.SecretKey), 16384, 8, 1, 64) newPasswd := base.PBKDF2([]byte(user.Passwd), []byte(user.Salt), 10000, 50, sha256.New)
user.Passwd = fmt.Sprintf("%x", newPasswd) user.Passwd = fmt.Sprintf("%x", newPasswd)
return err
} }
// Member represents user is member of organization. // Member represents user is member of organization.
@ -148,9 +147,9 @@ func RegisterUser(user *User) (*User, error) {
user.Avatar = base.EncodeMd5(user.Email) user.Avatar = base.EncodeMd5(user.Email)
user.AvatarEmail = user.Email user.AvatarEmail = user.Email
user.Rands = GetUserSalt() user.Rands = GetUserSalt()
if err = user.EncodePasswd(); err != nil { user.Salt = GetUserSalt()
return nil, err user.EncodePasswd()
} else if _, err = orm.Insert(user); err != nil { if _, err = orm.Insert(user); err != nil {
return nil, err return nil, err
} else if err = os.MkdirAll(UserPath(user.Name), os.ModePerm); err != nil { } else if err = os.MkdirAll(UserPath(user.Name), os.ModePerm); err != nil {
if _, err := orm.Id(user.Id).Delete(&User{}); err != nil { if _, err := orm.Id(user.Id).Delete(&User{}); err != nil {
@ -218,11 +217,18 @@ func ChangeUserName(user *User, newUserName string) (err error) {
if err = orm.Find(&accesses, &Access{UserName: user.LowerName}); err != nil { if err = orm.Find(&accesses, &Access{UserName: user.LowerName}); err != nil {
return err return err
} }
sess := orm.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
for i := range accesses { for i := range accesses {
accesses[i].UserName = newUserName accesses[i].UserName = newUserName
if strings.HasPrefix(accesses[i].RepoName, user.LowerName+"/") { if strings.HasPrefix(accesses[i].RepoName, user.LowerName+"/") {
accesses[i].RepoName = strings.Replace(accesses[i].RepoName, user.LowerName, newUserName, 1) accesses[i].RepoName = strings.Replace(accesses[i].RepoName, user.LowerName, newUserName, 1)
if err = UpdateAccess(&accesses[i]); err != nil { if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
return err return err
} }
} }
@ -241,14 +247,19 @@ func ChangeUserName(user *User, newUserName string) (err error) {
for j := range accesses { for j := range accesses {
accesses[j].RepoName = newUserName + "/" + repos[i].LowerName accesses[j].RepoName = newUserName + "/" + repos[i].LowerName
if err = UpdateAccess(&accesses[j]); err != nil { if err = UpdateAccessWithSession(sess, &accesses[j]); err != nil {
return err return err
} }
} }
} }
// Change user directory name. // Change user directory name.
return os.Rename(UserPath(user.LowerName), UserPath(newUserName)) if err = os.Rename(UserPath(user.LowerName), UserPath(newUserName)); err != nil {
sess.Rollback()
return err
}
return sess.Commit()
} }
// UpdateUser updates user's information. // UpdateUser updates user's information.
@ -355,20 +366,50 @@ func GetUserByName(name string) (*User, error) {
return user, nil return user, nil
} }
// LoginUserPlain validates user by raw user name and password. // GetUserEmailsByNames returns a slice of e-mails corresponds to names.
func LoginUserPlain(name, passwd string) (*User, error) { func GetUserEmailsByNames(names []string) []string {
user := User{LowerName: strings.ToLower(name), Passwd: passwd} mails := make([]string, 0, len(names))
if err := user.EncodePasswd(); err != nil { for _, name := range names {
u, err := GetUserByName(name)
if err != nil {
continue
}
mails = append(mails, u.Email)
}
return mails
}
// GetUserByEmail returns the user object by given e-mail if exists.
func GetUserByEmail(email string) (*User, error) {
if len(email) == 0 {
return nil, ErrUserNotExist
}
user := &User{Email: strings.ToLower(email)}
has, err := orm.Get(user)
if err != nil {
return nil, err return nil, err
} else if !has {
return nil, ErrUserNotExist
} }
return user, nil
}
// LoginUserPlain validates user by raw user name and password.
func LoginUserPlain(name, passwd string) (*User, error) {
user := User{LowerName: strings.ToLower(name)}
has, err := orm.Get(&user) has, err := orm.Get(&user)
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } else if !has {
err = ErrUserNotExist return nil, ErrUserNotExist
}
newUser := &User{Passwd: passwd, Salt: user.Salt}
newUser.EncodePasswd()
if user.Passwd != newUser.Passwd {
return nil, ErrUserNotExist
} }
return &user, err return &user, nil
} }
// Follow is connection request for receiving user notifycation. // Follow is connection request for receiving user notifycation.

92
modules/base/conf.go

@ -14,6 +14,7 @@ import (
"github.com/Unknwon/com" "github.com/Unknwon/com"
"github.com/Unknwon/goconfig" "github.com/Unknwon/goconfig"
qlog "github.com/qiniu/log"
"github.com/gogits/cache" "github.com/gogits/cache"
"github.com/gogits/session" "github.com/gogits/session"
@ -21,13 +22,22 @@ import (
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
) )
// Mailer represents a mail service. // Mailer represents mail service.
type Mailer struct { type Mailer struct {
Name string Name string
Host string Host string
User, Passwd string User, Passwd string
} }
// Oauther represents oauth service.
type Oauther struct {
GitHub struct {
Enabled bool
ClientId, ClientSecret string
Scopes string
}
}
var ( var (
AppVer string AppVer string
AppName string AppName string
@ -44,8 +54,9 @@ var (
CookieUserName string CookieUserName string
CookieRememberName string CookieRememberName string
Cfg *goconfig.ConfigFile Cfg *goconfig.ConfigFile
MailService *Mailer MailService *Mailer
OauthService *Oauther
LogMode string LogMode string
LogConfig string LogConfig string
@ -105,16 +116,14 @@ func newLogService() {
LogMode = Cfg.MustValue("log", "MODE", "console") LogMode = Cfg.MustValue("log", "MODE", "console")
modeSec := "log." + LogMode modeSec := "log." + LogMode
if _, err := Cfg.GetSection(modeSec); err != nil { if _, err := Cfg.GetSection(modeSec); err != nil {
fmt.Printf("Unknown log mode: %s\n", LogMode) qlog.Fatalf("Unknown log mode: %s\n", LogMode)
os.Exit(2)
} }
// Log level. // Log level.
levelName := Cfg.MustValue("log."+LogMode, "LEVEL", "Trace") levelName := Cfg.MustValue("log."+LogMode, "LEVEL", "Trace")
level, ok := logLevels[levelName] level, ok := logLevels[levelName]
if !ok { if !ok {
fmt.Printf("Unknown log level: %s\n", levelName) qlog.Fatalf("Unknown log level: %s\n", levelName)
os.Exit(2)
} }
// Generate log configuration. // Generate log configuration.
@ -151,6 +160,7 @@ func newLogService() {
Cfg.MustValue(modeSec, "CONN")) Cfg.MustValue(modeSec, "CONN"))
} }
log.Info("%s %s", AppName, AppVer)
log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig) log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig)
log.Info("Log Mode: %s(%s)", strings.Title(LogMode), levelName) log.Info("Log Mode: %s(%s)", strings.Title(LogMode), levelName)
} }
@ -164,16 +174,14 @@ func newCacheService() {
case "redis", "memcache": case "redis", "memcache":
CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, Cfg.MustValue("cache", "HOST")) CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, Cfg.MustValue("cache", "HOST"))
default: default:
fmt.Printf("Unknown cache adapter: %s\n", CacheAdapter) qlog.Fatalf("Unknown cache adapter: %s\n", CacheAdapter)
os.Exit(2)
} }
var err error var err error
Cache, err = cache.NewCache(CacheAdapter, CacheConfig) Cache, err = cache.NewCache(CacheAdapter, CacheConfig)
if err != nil { if err != nil {
fmt.Printf("Init cache system failed, adapter: %s, config: %s, %v\n", qlog.Fatalf("Init cache system failed, adapter: %s, config: %s, %v\n",
CacheAdapter, CacheConfig, err) CacheAdapter, CacheConfig, err)
os.Exit(2)
} }
log.Info("Cache Service Enabled") log.Info("Cache Service Enabled")
@ -199,9 +207,8 @@ func newSessionService() {
var err error var err error
SessionManager, err = session.NewManager(SessionProvider, *SessionConfig) SessionManager, err = session.NewManager(SessionProvider, *SessionConfig)
if err != nil { if err != nil {
fmt.Printf("Init session system failed, provider: %s, %v\n", qlog.Fatalf("Init session system failed, provider: %s, %v\n",
SessionProvider, err) SessionProvider, err)
os.Exit(2)
} }
log.Info("Session Service Enabled") log.Info("Session Service Enabled")
@ -209,15 +216,17 @@ func newSessionService() {
func newMailService() { func newMailService() {
// Check mailer setting. // Check mailer setting.
if Cfg.MustBool("mailer", "ENABLED") { if !Cfg.MustBool("mailer", "ENABLED") {
MailService = &Mailer{ return
Name: Cfg.MustValue("mailer", "NAME", AppName), }
Host: Cfg.MustValue("mailer", "HOST"),
User: Cfg.MustValue("mailer", "USER"), MailService = &Mailer{
Passwd: Cfg.MustValue("mailer", "PASSWD"), Name: Cfg.MustValue("mailer", "NAME", AppName),
} Host: Cfg.MustValue("mailer", "HOST"),
log.Info("Mail Service Enabled") User: Cfg.MustValue("mailer", "USER"),
Passwd: Cfg.MustValue("mailer", "PASSWD"),
} }
log.Info("Mail Service Enabled")
} }
func newRegisterMailService() { func newRegisterMailService() {
@ -242,27 +251,44 @@ func newNotifyMailService() {
log.Info("Notify Mail Service Enabled") log.Info("Notify Mail Service Enabled")
} }
func newOauthService() {
if !Cfg.MustBool("oauth", "ENABLED") {
return
}
OauthService = &Oauther{}
oauths := make([]string, 0, 10)
// GitHub.
if Cfg.MustBool("oauth.github", "ENABLED") {
OauthService.GitHub.Enabled = true
OauthService.GitHub.ClientId = Cfg.MustValue("oauth.github", "CLIENT_ID")
OauthService.GitHub.ClientSecret = Cfg.MustValue("oauth.github", "CLIENT_SECRET")
OauthService.GitHub.Scopes = Cfg.MustValue("oauth.github", "SCOPES")
oauths = append(oauths, "GitHub")
}
log.Info("Oauth Service Enabled %s", oauths)
}
func NewConfigContext() { func NewConfigContext() {
//var err error //var err error
workDir, err := ExecDir() workDir, err := ExecDir()
if err != nil { if err != nil {
fmt.Printf("Fail to get work directory: %s\n", err) qlog.Fatalf("Fail to get work directory: %s\n", err)
os.Exit(2)
} }
cfgPath := filepath.Join(workDir, "conf/app.ini") cfgPath := filepath.Join(workDir, "conf/app.ini")
Cfg, err = goconfig.LoadConfigFile(cfgPath) Cfg, err = goconfig.LoadConfigFile(cfgPath)
if err != nil { if err != nil {
fmt.Printf("Cannot load config file(%s): %v\n", cfgPath, err) qlog.Fatalf("Cannot load config file(%s): %v\n", cfgPath, err)
os.Exit(2)
} }
Cfg.BlockMode = false Cfg.BlockMode = false
cfgPath = filepath.Join(workDir, "custom/conf/app.ini") cfgPath = filepath.Join(workDir, "custom/conf/app.ini")
if com.IsFile(cfgPath) { if com.IsFile(cfgPath) {
if err = Cfg.AppendFiles(cfgPath); err != nil { if err = Cfg.AppendFiles(cfgPath); err != nil {
fmt.Printf("Cannot load config file(%s): %v\n", cfgPath, err) qlog.Fatalf("Cannot load config file(%s): %v\n", cfgPath, err)
os.Exit(2)
} }
} }
@ -281,8 +307,7 @@ func NewConfigContext() {
} }
// Does not check run user when the install lock is off. // Does not check run user when the install lock is off.
if InstallLock && RunUser != curUser { if InstallLock && RunUser != curUser {
fmt.Printf("Expect user(%s) but current user is: %s\n", RunUser, curUser) qlog.Fatalf("Expect user(%s) but current user is: %s\n", RunUser, curUser)
os.Exit(2)
} }
LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS") LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
@ -294,13 +319,11 @@ func NewConfigContext() {
// Determine and create root git reposiroty path. // Determine and create root git reposiroty path.
homeDir, err := com.HomeDir() homeDir, err := com.HomeDir()
if err != nil { if err != nil {
fmt.Printf("Fail to get home directory): %v\n", err) qlog.Fatalf("Fail to get home directory): %v\n", err)
os.Exit(2)
} }
RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "git/gogs-repositories")) RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "gogs-repositories"))
if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil { if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
fmt.Printf("Fail to create RepoRootPath(%s): %v\n", RepoRootPath, err) qlog.Fatalf("Fail to create RepoRootPath(%s): %v\n", RepoRootPath, err)
os.Exit(2)
} }
} }
@ -312,4 +335,5 @@ func NewServices() {
newMailService() newMailService()
newRegisterMailService() newRegisterMailService()
newNotifyMailService() newNotifyMailService()
newOauthService()
} }

49
modules/base/markdown.go

@ -6,9 +6,11 @@ package base
import ( import (
"bytes" "bytes"
"fmt"
"net/http" "net/http"
"path" "path"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"github.com/gogits/gfm" "github.com/gogits/gfm"
@ -87,7 +89,52 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte,
options.Renderer.Link(out, link, title, content) options.Renderer.Link(out, link, title, content)
} }
var (
MentionPattern = regexp.MustCompile(`@[0-9a-zA-Z_]{1,}`)
commitPattern = regexp.MustCompile(`(\s|^)https?.*commit/[0-9a-zA-Z]+(#+[0-9a-zA-Z-]*)?`)
issueFullPattern = regexp.MustCompile(`(\s|^)https?.*issues/[0-9]+(#+[0-9a-zA-Z-]*)?`)
issueIndexPattern = regexp.MustCompile(`#[0-9]+`)
)
func RenderSpecialLink(rawBytes []byte, urlPrefix string) []byte {
ms := MentionPattern.FindAll(rawBytes, -1)
for _, m := range ms {
rawBytes = bytes.Replace(rawBytes, m,
[]byte(fmt.Sprintf(`<a href="/user/%s">%s</a>`, m[1:], m)), -1)
}
ms = commitPattern.FindAll(rawBytes, -1)
for _, m := range ms {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "commit/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
}
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(
` <code><a href="%s">%s</a></code>`, m, ShortSha(string(m[i+7:j])))), -1)
}
ms = issueFullPattern.FindAll(rawBytes, -1)
for _, m := range ms {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "issues/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
}
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(
` <a href="%s">#%s</a>`, m, ShortSha(string(m[i+7:j])))), -1)
}
ms = issueIndexPattern.FindAll(rawBytes, -1)
for _, m := range ms {
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(
`<a href="%s/issues/%s">%s</a>`, urlPrefix, m[1:], m)), -1)
}
return rawBytes
}
func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte { func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
// body := RenderSpecialLink(rawBytes, urlPrefix)
// fmt.Println(string(body))
htmlFlags := 0 htmlFlags := 0
// htmlFlags |= gfm.HTML_USE_XHTML // htmlFlags |= gfm.HTML_USE_XHTML
// htmlFlags |= gfm.HTML_USE_SMARTYPANTS // htmlFlags |= gfm.HTML_USE_SMARTYPANTS
@ -116,6 +163,6 @@ func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
extensions |= gfm.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK extensions |= gfm.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK
body := gfm.Markdown(rawBytes, renderer, extensions) body := gfm.Markdown(rawBytes, renderer, extensions)
// fmt.Println(string(body))
return body return body
} }

110
modules/base/template.go

@ -5,7 +5,9 @@
package base package base
import ( import (
"bytes"
"container/list" "container/list"
"encoding/json"
"fmt" "fmt"
"html/template" "html/template"
"strings" "strings"
@ -67,6 +69,10 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
"DateFormat": DateFormat, "DateFormat": DateFormat,
"List": List, "List": List,
"Mail2Domain": func(mail string) string { "Mail2Domain": func(mail string) string {
if !strings.Contains(mail, "@") {
return "try.gogits.org"
}
suffix := strings.SplitN(mail, "@", 2)[1] suffix := strings.SplitN(mail, "@", 2)[1]
domain, ok := mailDomains[suffix] domain, ok := mailDomains[suffix]
if !ok { if !ok {
@ -81,3 +87,107 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
"DiffLineTypeToStr": DiffLineTypeToStr, "DiffLineTypeToStr": DiffLineTypeToStr,
"ShortSha": ShortSha, "ShortSha": ShortSha,
} }
type Actioner interface {
GetOpType() int
GetActUserName() string
GetActEmail() string
GetRepoName() string
GetBranch() string
GetContent() string
}
// ActionIcon accepts a int that represents action operation type
// and returns a icon class name.
func ActionIcon(opType int) string {
switch opType {
case 1: // Create repository.
return "plus-circle"
case 5: // Commit repository.
return "arrow-circle-o-right"
case 6: // Create issue.
return "exclamation-circle"
case 8: // Transfer repository.
return "share"
default:
return "invalid type"
}
}
const (
TPL_CREATE_REPO = `<a href="/user/%s">%s</a> created repository <a href="/%s">%s</a>`
TPL_COMMIT_REPO = `<a href="/user/%s">%s</a> pushed to <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>%s`
TPL_COMMIT_REPO_LI = `<div><img src="%s?s=16" alt="user-avatar"/> <a href="/%s/commit/%s">%s</a> %s</div>`
TPL_CREATE_ISSUE = `<a href="/user/%s">%s</a> opened issue <a href="/%s/issues/%s">%s#%s</a>
<div><img src="%s?s=16" alt="user-avatar"/> %s</div>`
TPL_TRANSFER_REPO = `<a href="/user/%s">%s</a> transfered repository <code>%s</code> to <a href="/%s">%s</a>`
)
type PushCommit struct {
Sha1 string
Message string
AuthorEmail string
AuthorName string
}
type PushCommits struct {
Len int
Commits []*PushCommit
}
// ActionDesc accepts int that represents action operation type
// and returns the description.
func ActionDesc(act Actioner) string {
actUserName := act.GetActUserName()
email := act.GetActEmail()
repoName := act.GetRepoName()
repoLink := actUserName + "/" + repoName
branch := act.GetBranch()
content := act.GetContent()
switch act.GetOpType() {
case 1: // Create repository.
return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, repoLink, repoName)
case 5: // Commit repository.
var push *PushCommits
if err := json.Unmarshal([]byte(content), &push); err != nil {
return err.Error()
}
buf := bytes.NewBuffer([]byte("\n"))
for _, commit := range push.Commits {
buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, AvatarLink(commit.AuthorEmail), repoLink, commit.Sha1, commit.Sha1[:7], commit.Message) + "\n")
}
if push.Len > 3 {
buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits/%s">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len))
}
return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink,
buf.String())
case 6: // Create issue.
infos := strings.SplitN(content, "|", 2)
return fmt.Sprintf(TPL_CREATE_ISSUE, actUserName, actUserName, repoLink, infos[0], repoLink, infos[0],
AvatarLink(email), infos[1])
case 8: // Transfer repository.
newRepoLink := content + "/" + repoName
return fmt.Sprintf(TPL_TRANSFER_REPO, actUserName, actUserName, repoLink, newRepoLink, newRepoLink)
default:
return "invalid type"
}
}
func DiffTypeToStr(diffType int) string {
diffTypes := map[int]string{
1: "add", 2: "modify", 3: "del",
}
return diffTypes[diffType]
}
func DiffLineTypeToStr(diffType int) string {
switch diffType {
case 2:
return "add"
case 3:
return "del"
case 4:
return "tag"
}
return "same"
}

146
modules/base/tool.go

@ -5,13 +5,13 @@
package base package base
import ( import (
"bytes" "crypto/hmac"
"crypto/md5" "crypto/md5"
"crypto/rand" "crypto/rand"
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"encoding/json"
"fmt" "fmt"
"hash"
"math" "math"
"strconv" "strconv"
"strings" "strings"
@ -40,6 +40,44 @@ func GetRandomString(n int, alphabets ...byte) string {
return string(bytes) return string(bytes)
} }
// http://code.google.com/p/go/source/browse/pbkdf2/pbkdf2.go?repo=crypto
func PBKDF2(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
prf := hmac.New(h, password)
hashLen := prf.Size()
numBlocks := (keyLen + hashLen - 1) / hashLen
var buf [4]byte
dk := make([]byte, 0, numBlocks*hashLen)
U := make([]byte, hashLen)
for block := 1; block <= numBlocks; block++ {
// N.B.: || means concatenation, ^ means XOR
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
// U_1 = PRF(password, salt || uint(i))
prf.Reset()
prf.Write(salt)
buf[0] = byte(block >> 24)
buf[1] = byte(block >> 16)
buf[2] = byte(block >> 8)
buf[3] = byte(block)
prf.Write(buf[:4])
dk = prf.Sum(dk)
T := dk[len(dk)-hashLen:]
copy(U, T)
// U_n = PRF(password, U_(n-1))
for n := 2; n <= iter; n++ {
prf.Reset()
prf.Write(U)
U = U[:0]
U = prf.Sum(U)
for x := range U {
T[x] ^= U[x]
}
}
}
return dk[:keyLen]
}
// verify time limit code // verify time limit code
func VerifyTimeLimitCode(data string, minutes int, code string) bool { func VerifyTimeLimitCode(data string, minutes int, code string) bool {
if len(code) <= 18 { if len(code) <= 18 {
@ -474,107 +512,3 @@ func (a argInt) Get(i int, args ...int) (r int) {
} }
return return
} }
type Actioner interface {
GetOpType() int
GetActUserName() string
GetActEmail() string
GetRepoName() string
GetBranch() string
GetContent() string
}
// ActionIcon accepts a int that represents action operation type
// and returns a icon class name.
func ActionIcon(opType int) string {
switch opType {
case 1: // Create repository.
return "plus-circle"
case 5: // Commit repository.
return "arrow-circle-o-right"
case 6: // Create issue.
return "exclamation-circle"
case 8: // Transfer repository.
return "share"
default:
return "invalid type"
}
}
const (
TPL_CREATE_REPO = `<a href="/user/%s">%s</a> created repository <a href="/%s">%s</a>`
TPL_COMMIT_REPO = `<a href="/user/%s">%s</a> pushed to <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>%s`
TPL_COMMIT_REPO_LI = `<div><img src="%s?s=16" alt="user-avatar"/> <a href="/%s/commit/%s">%s</a> %s</div>`
TPL_CREATE_ISSUE = `<a href="/user/%s">%s</a> opened issue <a href="/%s/issues/%s">%s#%s</a>
<div><img src="%s?s=16" alt="user-avatar"/> %s</div>`
TPL_TRANSFER_REPO = `<a href="/user/%s">%s</a> transfered repository <code>%s</code> to <a href="/%s">%s</a>`
)
type PushCommit struct {
Sha1 string
Message string
AuthorEmail string
AuthorName string
}
type PushCommits struct {
Len int
Commits []*PushCommit
}
// ActionDesc accepts int that represents action operation type
// and returns the description.
func ActionDesc(act Actioner) string {
actUserName := act.GetActUserName()
email := act.GetActEmail()
repoName := act.GetRepoName()
repoLink := actUserName + "/" + repoName
branch := act.GetBranch()
content := act.GetContent()
switch act.GetOpType() {
case 1: // Create repository.
return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, repoLink, repoName)
case 5: // Commit repository.
var push *PushCommits
if err := json.Unmarshal([]byte(content), &push); err != nil {
return err.Error()
}
buf := bytes.NewBuffer([]byte("\n"))
for _, commit := range push.Commits {
buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, AvatarLink(commit.AuthorEmail), repoLink, commit.Sha1, commit.Sha1[:7], commit.Message) + "\n")
}
if push.Len > 3 {
buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits/%s">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len))
}
return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink,
buf.String())
case 6: // Create issue.
infos := strings.SplitN(content, "|", 2)
return fmt.Sprintf(TPL_CREATE_ISSUE, actUserName, actUserName, repoLink, infos[0], repoLink, infos[0],
AvatarLink(email), infos[1])
case 8: // Transfer repository.
newRepoLink := content + "/" + repoName
return fmt.Sprintf(TPL_TRANSFER_REPO, actUserName, actUserName, repoLink, newRepoLink, newRepoLink)
default:
return "invalid type"
}
}
func DiffTypeToStr(diffType int) string {
diffTypes := map[int]string{
1: "add", 2: "modify", 3: "del",
}
return diffTypes[diffType]
}
func DiffLineTypeToStr(diffType int) string {
switch diffType {
case 2:
return "add"
case 3:
return "del"
case 4:
return "tag"
}
return "same"
}

2
modules/log/log.go

@ -21,8 +21,6 @@ func init() {
func NewLogger(bufLen int64, mode, config string) { func NewLogger(bufLen int64, mode, config string) {
Mode, Config = mode, config Mode, Config = mode, config
logger = logs.NewLogger(bufLen) logger = logs.NewLogger(bufLen)
logger.EnableFuncCallDepth(true)
logger.SetLogFuncCallDepth(4)
logger.SetLogger(mode, config) logger.SetLogger(mode, config)
} }

53
modules/mailer/mail.go

@ -86,16 +86,36 @@ func SendActiveMail(r *middleware.Render, user *models.User) {
} }
msg := NewMailMessage([]string{user.Email}, subject, body) msg := NewMailMessage([]string{user.Email}, subject, body)
msg.Info = fmt.Sprintf("UID: %d, send email verify mail", user.Id) msg.Info = fmt.Sprintf("UID: %d, send active mail", user.Id)
SendAsync(&msg) SendAsync(&msg)
} }
// SendNotifyMail sends mail notification of all watchers. // Send reset password email.
func SendNotifyMail(user, owner *models.User, repo *models.Repository, issue *models.Issue) error { func SendResetPasswdMail(r *middleware.Render, user *models.User) {
code := CreateUserActiveCode(user, nil)
subject := "Reset your password"
data := GetMailTmplData(user)
data["Code"] = code
body, err := r.HTMLString("mail/auth/reset_passwd", data)
if err != nil {
log.Error("mail.SendResetPasswdMail(fail to render): %v", err)
return
}
msg := NewMailMessage([]string{user.Email}, subject, body)
msg.Info = fmt.Sprintf("UID: %d, send reset password email", user.Id)
SendAsync(&msg)
}
// SendIssueNotifyMail sends mail notification of all watchers of repository.
func SendIssueNotifyMail(user, owner *models.User, repo *models.Repository, issue *models.Issue) ([]string, error) {
watches, err := models.GetWatches(repo.Id) watches, err := models.GetWatches(repo.Id)
if err != nil { if err != nil {
return errors.New("mail.NotifyWatchers(get watches): " + err.Error()) return nil, errors.New("mail.NotifyWatchers(get watches): " + err.Error())
} }
tos := make([]string, 0, len(watches)) tos := make([]string, 0, len(watches))
@ -106,20 +126,37 @@ func SendNotifyMail(user, owner *models.User, repo *models.Repository, issue *mo
} }
u, err := models.GetUserById(uid) u, err := models.GetUserById(uid)
if err != nil { if err != nil {
return errors.New("mail.NotifyWatchers(get user): " + err.Error()) return nil, errors.New("mail.NotifyWatchers(get user): " + err.Error())
} }
tos = append(tos, u.Email) tos = append(tos, u.Email)
} }
if len(tos) == 0 { if len(tos) == 0 {
return nil return tos, nil
} }
subject := fmt.Sprintf("[%s] %s", repo.Name, issue.Name) subject := fmt.Sprintf("[%s] %s", repo.Name, issue.Name)
content := fmt.Sprintf("%s<br>-<br> <a href=\"%s%s/%s/issues/%d\">View it on Gogs</a>.", content := fmt.Sprintf("%s<br>-<br> <a href=\"%s%s/%s/issues/%d\">View it on Gogs</a>.",
issue.Content, base.AppUrl, owner.Name, repo.Name, issue.Index) base.RenderSpecialLink([]byte(issue.Content), owner.Name+"/"+repo.Name),
base.AppUrl, owner.Name, repo.Name, issue.Index)
msg := NewMailMessageFrom(tos, user.Name, subject, content)
msg.Info = fmt.Sprintf("Subject: %s, send issue notify emails", subject)
SendAsync(&msg)
return tos, nil
}
// SendIssueMentionMail sends mail notification for who are mentioned in issue.
func SendIssueMentionMail(user, owner *models.User, repo *models.Repository, issue *models.Issue, tos []string) error {
if len(tos) == 0 {
return nil
}
issueLink := fmt.Sprintf("%s%s/%s/issues/%d", base.AppUrl, owner.Name, repo.Name, issue.Index)
body := fmt.Sprintf(`%s mentioned you.`)
subject := fmt.Sprintf("[%s] %s", repo.Name, issue.Name)
content := fmt.Sprintf("%s<br>-<br> <a href=\"%s\">View it on Gogs</a>.", body, issueLink)
msg := NewMailMessageFrom(tos, user.Name, subject, content) msg := NewMailMessageFrom(tos, user.Name, subject, content)
msg.Info = fmt.Sprintf("Subject: %s, send notify emails", subject) msg.Info = fmt.Sprintf("Subject: %s, send issue mention emails", subject)
SendAsync(&msg) SendAsync(&msg)
return nil return nil
} }

74
modules/oauth2/oauth2.go

@ -26,13 +26,16 @@ import (
"code.google.com/p/goauth2/oauth" "code.google.com/p/goauth2/oauth"
"github.com/go-martini/martini" "github.com/go-martini/martini"
"github.com/martini-contrib/sessions"
"github.com/gogits/session"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware"
) )
const ( const (
codeRedirect = 302 keyToken = "oauth2_token"
keyToken = "oauth2_token" keyNextPage = "next"
keyNextPage = "next"
) )
var ( var (
@ -142,23 +145,23 @@ func NewOAuth2Provider(opts *Options) martini.Handler {
Transport: http.DefaultTransport, Transport: http.DefaultTransport,
} }
return func(s sessions.Session, c martini.Context, w http.ResponseWriter, r *http.Request) { return func(c martini.Context, ctx *middleware.Context) {
if r.Method == "GET" { if ctx.Req.Method == "GET" {
switch r.URL.Path { switch ctx.Req.URL.Path {
case PathLogin: case PathLogin:
login(transport, s, w, r) login(transport, ctx)
case PathLogout: case PathLogout:
logout(transport, s, w, r) logout(transport, ctx)
case PathCallback: case PathCallback:
handleOAuth2Callback(transport, s, w, r) handleOAuth2Callback(transport, ctx)
} }
} }
tk := unmarshallToken(s) tk := unmarshallToken(ctx.Session)
if tk != nil { if tk != nil {
// check if the access token is expired // check if the access token is expired
if tk.IsExpired() && tk.Refresh() == "" { if tk.IsExpired() && tk.Refresh() == "" {
s.Delete(keyToken) ctx.Session.Delete(keyToken)
tk = nil tk = nil
} }
} }
@ -172,49 +175,56 @@ func NewOAuth2Provider(opts *Options) martini.Handler {
// Sample usage: // Sample usage:
// m.Get("/login-required", oauth2.LoginRequired, func() ... {}) // m.Get("/login-required", oauth2.LoginRequired, func() ... {})
var LoginRequired martini.Handler = func() martini.Handler { var LoginRequired martini.Handler = func() martini.Handler {
return func(s sessions.Session, c martini.Context, w http.ResponseWriter, r *http.Request) { return func(c martini.Context, ctx *middleware.Context) {
token := unmarshallToken(s) token := unmarshallToken(ctx.Session)
if token == nil || token.IsExpired() { if token == nil || token.IsExpired() {
next := url.QueryEscape(r.URL.RequestURI()) next := url.QueryEscape(ctx.Req.URL.RequestURI())
http.Redirect(w, r, PathLogin+"?next="+next, codeRedirect) ctx.Redirect(PathLogin + "?next=" + next)
return
} }
} }
}() }()
func login(t *oauth.Transport, s sessions.Session, w http.ResponseWriter, r *http.Request) { func login(t *oauth.Transport, ctx *middleware.Context) {
next := extractPath(r.URL.Query().Get(keyNextPage)) next := extractPath(ctx.Query(keyNextPage))
if s.Get(keyToken) == nil { if ctx.Session.Get(keyToken) == nil {
// User is not logged in. // User is not logged in.
http.Redirect(w, r, t.Config.AuthCodeURL(next), codeRedirect) ctx.Redirect(t.Config.AuthCodeURL(next))
return return
} }
// No need to login, redirect to the next page. // No need to login, redirect to the next page.
http.Redirect(w, r, next, codeRedirect) ctx.Redirect(next)
} }
func logout(t *oauth.Transport, s sessions.Session, w http.ResponseWriter, r *http.Request) { func logout(t *oauth.Transport, ctx *middleware.Context) {
next := extractPath(r.URL.Query().Get(keyNextPage)) next := extractPath(ctx.Query(keyNextPage))
s.Delete(keyToken) ctx.Session.Delete(keyToken)
http.Redirect(w, r, next, codeRedirect) ctx.Redirect(next)
} }
func handleOAuth2Callback(t *oauth.Transport, s sessions.Session, w http.ResponseWriter, r *http.Request) { func handleOAuth2Callback(t *oauth.Transport, ctx *middleware.Context) {
next := extractPath(r.URL.Query().Get("state")) if errMsg := ctx.Query("error_description"); len(errMsg) > 0 {
code := r.URL.Query().Get("code") log.Error("oauth2.handleOAuth2Callback: %s", errMsg)
return
}
next := extractPath(ctx.Query("state"))
code := ctx.Query("code")
tk, err := t.Exchange(code) tk, err := t.Exchange(code)
if err != nil { if err != nil {
// Pass the error message, or allow dev to provide its own // Pass the error message, or allow dev to provide its own
// error handler. // error handler.
http.Redirect(w, r, PathError, codeRedirect) log.Error("oauth2.handleOAuth2Callback(token.Exchange): %v", err)
// ctx.Redirect(PathError)
return return
} }
// Store the credentials in the session. // Store the credentials in the session.
val, _ := json.Marshal(tk) val, _ := json.Marshal(tk)
s.Set(keyToken, val) ctx.Session.Set(keyToken, val)
http.Redirect(w, r, next, codeRedirect) ctx.Redirect(next)
} }
func unmarshallToken(s sessions.Session) (t *token) { func unmarshallToken(s session.SessionStore) (t *token) {
if s.Get(keyToken) == nil { if s.Get(keyToken) == nil {
return return
} }

70
public/css/gogs.css

@ -1305,3 +1305,73 @@ html, body {
#release .release-item .info .avatar { #release .release-item .info .avatar {
vertical-align: middle; vertical-align: middle;
} }
#release-new-form {
margin-top: 24px;
}
#release-new-form .target-at {
margin: 0 1em;
}
#release-new-form .target-text {
color: #888;
}
#release-new-target-branch-list {
padding-top: 0;
padding-bottom: 0;
min-width: 200px;
}
#release-new-target-branch-list ul {
margin-bottom: 0;
}
#release-new-target-branch-list li {
padding: 8px 20px;
}
#release-new-target-branch-list li a {
margin-left: 0;
background-color: transparent;
padding: 0;
}
#release-new-target-branch-list li a:hover {
background-image: none;
}
#release-new-target-branch-list li:hover {
background-color: #0093c4;
}
#release-new-target-branch-list li:hover a {
color: #FFF;
}
#release-new-title {
width: 50%;
}
#release-new-content-div {
margin-top: 16px;
padding-left: 0;
}
#release-new-content-div .md-help {
margin-top: 6px;
}
#release-textarea .form-group {
display: block;
}
#release-new-content {
width: 100%;
margin: 16px 0;
}
#release-preview{
margin: 6px 0;
}

47
public/js/app.js

@ -354,6 +354,7 @@ function initRegister() {
} }
function initUserSetting() { function initUserSetting() {
// ssh confirmation
$('#ssh-keys .delete').confirmation({ $('#ssh-keys .delete').confirmation({
singleton: true, singleton: true,
onConfirm: function (e, $this) { onConfirm: function (e, $this) {
@ -366,6 +367,18 @@ function initUserSetting() {
}); });
} }
}); });
// profile form
(function () {
$('#user-setting-username').on("keyup", function () {
var $this = $(this);
if ($this.val() != $this.attr('title')) {
$this.next('.help-block').toggleShow();
} else {
$this.next('.help-block').toggleHide();
}
});
}())
} }
function initRepository() { function initRepository() {
@ -383,7 +396,7 @@ function initRepository() {
$clone.find('span.clone-url').text($this.data('link')); $clone.find('span.clone-url').text($this.data('link'));
} }
}).eq(0).trigger("click"); }).eq(0).trigger("click");
$("#repo-clone").on("shown.bs.dropdown",function () { $("#repo-clone").on("shown.bs.dropdown", function () {
Gogits.bindCopy("[data-init=copy]"); Gogits.bindCopy("[data-init=copy]");
}); });
Gogits.bindCopy("[data-init=copy]:visible"); Gogits.bindCopy("[data-init=copy]:visible");
@ -438,6 +451,18 @@ function initRepository() {
$item.find(".bar .add").css("width", addPercent + "%"); $item.find(".bar .add").css("width", addPercent + "%");
}); });
}()); }());
// repo setting form
(function () {
$('#repo-setting-name').on("keyup", function () {
var $this = $(this);
if ($this.val() != $this.attr('title')) {
$this.next('.help-block').toggleShow();
} else {
$this.next('.help-block').toggleHide();
}
});
}())
} }
function initInstall() { function initInstall() {
@ -520,6 +545,23 @@ function initIssue() {
} }
function initRelease() {
// release new ajax preview
(function () {
$('[data-ajax-name=release-preview]').on("click", function () {
var $this = $(this);
$this.toggleAjax(function (json) {
if (json.ok) {
$($this.data("preview")).html(json.content);
}
})
});
$('.release-write a[data-toggle]').on("click", function () {
$('.release-preview-content').html("loading...");
});
}())
}
(function ($) { (function ($) {
$(function () { $(function () {
initCore(); initCore();
@ -539,5 +581,8 @@ function initIssue() {
if ($('#issue').length) { if ($('#issue').length) {
initIssue(); initIssue();
} }
if ($('#release').length) {
initRelease();
}
}); });
})(jQuery); })(jQuery);

2
routers/api/v1/miscellaneous.go

@ -13,6 +13,6 @@ func Markdown(ctx *middleware.Context) {
content := ctx.Query("content") content := ctx.Query("content")
ctx.Render.JSON(200, map[string]interface{}{ ctx.Render.JSON(200, map[string]interface{}{
"ok": true, "ok": true,
"content": string(base.RenderMarkdown([]byte(content), "")), "content": string(base.RenderMarkdown([]byte(content), ctx.Query("repoLink"))),
}) })
} }

6
routers/install.go

@ -6,13 +6,13 @@ package routers
import ( import (
"errors" "errors"
"fmt"
"os" "os"
"strings" "strings"
"github.com/Unknwon/goconfig" "github.com/Unknwon/goconfig"
"github.com/go-martini/martini" "github.com/go-martini/martini"
"github.com/lunny/xorm" "github.com/lunny/xorm"
qlog "github.com/qiniu/log"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth" "github.com/gogits/gogs/modules/auth"
@ -43,8 +43,7 @@ func GlobalInit() {
if base.InstallLock { if base.InstallLock {
if err := models.NewEngine(); err != nil { if err := models.NewEngine(); err != nil {
fmt.Println(err) qlog.Fatal(err)
os.Exit(2)
} }
models.HasEngine = true models.HasEngine = true
@ -183,6 +182,7 @@ func Install(ctx *middleware.Context, form auth.InstallForm) {
if _, err := models.RegisterUser(&models.User{Name: form.AdminName, Email: form.AdminEmail, Passwd: form.AdminPasswd, if _, err := models.RegisterUser(&models.User{Name: form.AdminName, Email: form.AdminEmail, Passwd: form.AdminPasswd,
IsAdmin: true, IsActive: true}); err != nil { IsAdmin: true, IsActive: true}); err != nil {
if err != models.ErrUserAlreadyExist { if err != models.ErrUserAlreadyExist {
base.InstallLock = false
ctx.RenderWithErr("Admin account setting is invalid: "+err.Error(), "install", &form) ctx.RenderWithErr("Admin account setting is invalid: "+err.Error(), "install", &form)
return return
} }

36
routers/repo/issue.go

@ -9,6 +9,7 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/Unknwon/com"
"github.com/go-martini/martini" "github.com/go-martini/martini"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
@ -99,7 +100,7 @@ func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
issue, err := models.CreateIssue(ctx.User.Id, ctx.Repo.Repository.Id, form.MilestoneId, form.AssigneeId, issue, err := models.CreateIssue(ctx.User.Id, ctx.Repo.Repository.Id, form.MilestoneId, form.AssigneeId,
ctx.Repo.Repository.NumIssues, form.IssueName, form.Labels, form.Content, false) ctx.Repo.Repository.NumIssues, form.IssueName, form.Labels, form.Content, false)
if err != nil { if err != nil {
ctx.Handle(200, "issue.CreateIssue", err) ctx.Handle(200, "issue.CreateIssue(CreateIssue)", err)
return return
} }
@ -107,14 +108,31 @@ func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
if err = models.NotifyWatchers(&models.Action{ActUserId: ctx.User.Id, ActUserName: ctx.User.Name, ActEmail: ctx.User.Email, if err = models.NotifyWatchers(&models.Action{ActUserId: ctx.User.Id, ActUserName: ctx.User.Name, ActEmail: ctx.User.Email,
OpType: models.OP_CREATE_ISSUE, Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name), OpType: models.OP_CREATE_ISSUE, Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name),
RepoId: ctx.Repo.Repository.Id, RepoName: ctx.Repo.Repository.Name, RefName: ""}); err != nil { RepoId: ctx.Repo.Repository.Id, RepoName: ctx.Repo.Repository.Name, RefName: ""}); err != nil {
ctx.Handle(200, "issue.CreateIssue", err) ctx.Handle(200, "issue.CreateIssue(NotifyWatchers)", err)
return return
} }
// Mail watchers. // Mail watchers and mentions.
if base.Service.NotifyMail { if base.Service.NotifyMail {
if err = mailer.SendNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue); err != nil { tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
ctx.Handle(200, "issue.CreateIssue", err) if err != nil {
ctx.Handle(200, "issue.CreateIssue(SendIssueNotifyMail)", err)
return
}
tos = append(tos, ctx.User.LowerName)
ms := base.MentionPattern.FindAllString(issue.Content, -1)
newTos := make([]string, 0, len(ms))
for _, m := range ms {
if com.IsSliceContainsStr(tos, m[1:]) {
continue
}
newTos = append(newTos, m[1:])
}
if err = mailer.SendIssueMentionMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository,
issue, models.GetUserEmailsByNames(newTos)); err != nil {
ctx.Handle(200, "issue.CreateIssue(SendIssueMentionMail)", err)
return return
} }
} }
@ -147,7 +165,7 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
return return
} }
issue.Poster = u issue.Poster = u
issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), "")) issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink))
// Get comments. // Get comments.
comments, err := models.GetIssueComments(issue.Id) comments, err := models.GetIssueComments(issue.Id)
@ -164,7 +182,7 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
return return
} }
comments[i].Poster = u comments[i].Poster = u
comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), "")) comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink))
} }
ctx.Data["Title"] = issue.Name ctx.Data["Title"] = issue.Name
@ -193,7 +211,7 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
return return
} }
if ctx.User.Id != issue.PosterId { if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner {
ctx.Handle(404, "issue.UpdateIssue", nil) ctx.Handle(404, "issue.UpdateIssue", nil)
return return
} }
@ -211,7 +229,7 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
ctx.JSON(200, map[string]interface{}{ ctx.JSON(200, map[string]interface{}{
"ok": true, "ok": true,
"title": issue.Name, "title": issue.Name,
"content": string(base.RenderMarkdown([]byte(issue.Content), "")), "content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)),
}) })
} }

8
routers/repo/release.go

@ -12,6 +12,7 @@ import (
func Releases(ctx *middleware.Context) { func Releases(ctx *middleware.Context) {
ctx.Data["Title"] = "Releases" ctx.Data["Title"] = "Releases"
ctx.Data["IsRepoToolbarReleases"] = true ctx.Data["IsRepoToolbarReleases"] = true
ctx.Data["IsRepoReleaseNew"] = false
tags, err := models.GetTags(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) tags, err := models.GetTags(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
if err != nil { if err != nil {
ctx.Handle(404, "repo.Releases(GetTags)", err) ctx.Handle(404, "repo.Releases(GetTags)", err)
@ -20,3 +21,10 @@ func Releases(ctx *middleware.Context) {
ctx.Data["Releases"] = tags ctx.Data["Releases"] = tags
ctx.HTML(200, "release/list") ctx.HTML(200, "release/list")
} }
func ReleasesNew(ctx *middleware.Context) {
ctx.Data["Title"] = "New Release"
ctx.Data["IsRepoToolbarReleases"] = true
ctx.Data["IsRepoReleaseNew"] = true
ctx.HTML(200, "release/new")
}

99
routers/repo/repo.go

@ -5,6 +5,8 @@
package repo package repo
import ( import (
"encoding/base64"
"errors"
"fmt" "fmt"
"path" "path"
"path/filepath" "path/filepath"
@ -237,16 +239,109 @@ func SingleDownload(ctx *middleware.Context, params martini.Params) {
ctx.Res.Write(data) ctx.Res.Write(data)
} }
func Http(ctx *middleware.Context, params martini.Params) { func basicEncode(username, password string) string {
// TODO: access check auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}
func basicDecode(encoded string) (user string, name string, err error) {
var s []byte
s, err = base64.StdEncoding.DecodeString(encoded)
if err != nil {
return
}
a := strings.Split(string(s), ":")
if len(a) == 2 {
user, name = a[0], a[1]
} else {
err = errors.New("decode failed")
}
return
}
func authRequired(ctx *middleware.Context) {
ctx.ResponseWriter.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
ctx.Data["ErrorMsg"] = "no basic auth and digit auth"
ctx.HTML(401, fmt.Sprintf("status/401"))
}
func Http(ctx *middleware.Context, params martini.Params) {
username := params["username"] username := params["username"]
reponame := params["reponame"] reponame := params["reponame"]
if strings.HasSuffix(reponame, ".git") { if strings.HasSuffix(reponame, ".git") {
reponame = reponame[:len(reponame)-4] reponame = reponame[:len(reponame)-4]
} }
//fmt.Println("req:", ctx.Req.Header)
repoUser, err := models.GetUserByName(username)
if err != nil {
ctx.Handle(500, "repo.GetUserByName", nil)
return
}
repo, err := models.GetRepositoryByName(repoUser.Id, reponame)
if err != nil {
ctx.Handle(500, "repo.GetRepositoryByName", nil)
return
}
isPull := webdav.IsPullMethod(ctx.Req.Method)
var askAuth = !(!repo.IsPrivate && isPull)
//authRequired(ctx)
//return
// check access
if askAuth {
// check digit auth
// check basic auth
baHead := ctx.Req.Header.Get("Authorization")
if baHead == "" {
authRequired(ctx)
return
}
auths := strings.Fields(baHead)
if len(auths) != 2 || auths[0] != "Basic" {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}
authUsername, passwd, err := basicDecode(auths[1])
if err != nil {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}
authUser, err := models.GetUserByName(authUsername)
if err != nil {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}
newUser := &models.User{Passwd: passwd}
newUser.EncodePasswd()
if authUser.Passwd != newUser.Passwd {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}
var tp = models.AU_WRITABLE
if isPull {
tp = models.AU_READABLE
}
has, err := models.HasAccess(authUsername, username+"/"+reponame, tp)
if err != nil || !has {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}
}
dir := models.RepoPath(username, reponame) dir := models.RepoPath(username, reponame)
prefix := path.Join("/", username, params["reponame"]) prefix := path.Join("/", username, params["reponame"])
server := webdav.NewServer( server := webdav.NewServer(
dir, prefix, true) dir, prefix, true)

7
routers/user/setting.go

@ -73,11 +73,7 @@ func SettingPassword(ctx *middleware.Context, form auth.UpdatePasswdForm) {
user := ctx.User user := ctx.User
newUser := &models.User{Passwd: form.NewPasswd} newUser := &models.User{Passwd: form.NewPasswd}
if err := newUser.EncodePasswd(); err != nil { newUser.EncodePasswd()
ctx.Handle(200, "setting.SettingPassword", err)
return
}
if user.Passwd != newUser.Passwd { if user.Passwd != newUser.Passwd {
ctx.Data["HasError"] = true ctx.Data["HasError"] = true
ctx.Data["ErrorMsg"] = "Old password is not correct" ctx.Data["ErrorMsg"] = "Old password is not correct"
@ -85,6 +81,7 @@ func SettingPassword(ctx *middleware.Context, form auth.UpdatePasswdForm) {
ctx.Data["HasError"] = true ctx.Data["HasError"] = true
ctx.Data["ErrorMsg"] = "New password and re-type password are not same" ctx.Data["ErrorMsg"] = "New password and re-type password are not same"
} else { } else {
newUser.Salt = models.GetUserSalt()
user.Passwd = newUser.Passwd user.Passwd = newUser.Passwd
if err := models.UpdateUser(user); err != nil { if err := models.UpdateUser(user); err != nil {
ctx.Handle(200, "setting.SettingPassword", err) ctx.Handle(200, "setting.SettingPassword", err)

115
routers/user/social.go

@ -1,49 +1,122 @@
// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package user package user
import ( import (
"encoding/json" "encoding/json"
"strconv"
"code.google.com/p/goauth2/oauth" "code.google.com/p/goauth2/oauth"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware"
"github.com/gogits/gogs/modules/oauth2" "github.com/gogits/gogs/modules/oauth2"
) )
// github && google && ... type SocialConnector interface {
func SocialSignIn(tokens oauth2.Tokens) { Identity() string
transport := &oauth.Transport{} Type() int
transport.Token = &oauth.Token{ Name() string
AccessToken: tokens.Access(), Email() string
RefreshToken: tokens.Refresh(), Token() string
Expiry: tokens.ExpiryTime(), }
Extra: tokens.ExtraData(),
}
// Github API refer: https://developer.github.com/v3/users/ type SocialGithub struct {
// FIXME: need to judge url data struct {
type GithubUser struct {
Id int `json:"id"` Id int `json:"id"`
Name string `json:"login"` Name string `json:"login"`
Email string `json:"email"` Email string `json:"email"`
} }
WebToken *oauth.Token
}
func (s *SocialGithub) Identity() string {
return strconv.Itoa(s.data.Id)
}
func (s *SocialGithub) Type() int {
return models.OT_GITHUB
}
func (s *SocialGithub) Name() string {
return s.data.Name
}
func (s *SocialGithub) Email() string {
return s.data.Email
}
func (s *SocialGithub) Token() string {
data, _ := json.Marshal(s.WebToken)
return string(data)
}
// Make the request. // Github API refer: https://developer.github.com/v3/users/
func (s *SocialGithub) Update() error {
scope := "https://api.github.com/user" scope := "https://api.github.com/user"
transport := &oauth.Transport{
Token: s.WebToken,
}
log.Debug("update github info")
r, err := transport.Client().Get(scope) r, err := transport.Client().Get(scope)
if err != nil { if err != nil {
log.Error("connect with github error: %s", err) return err
// FIXME: handle error page
return
} }
defer r.Body.Close() defer r.Body.Close()
return json.NewDecoder(r.Body).Decode(&s.data)
}
user := &GithubUser{} // github && google && ...
err = json.NewDecoder(r.Body).Decode(user) func SocialSignIn(ctx *middleware.Context, tokens oauth2.Tokens) {
if err != nil { gh := &SocialGithub{
log.Error("Get: %s", err) WebToken: &oauth.Token{
AccessToken: tokens.Access(),
RefreshToken: tokens.Refresh(),
Expiry: tokens.ExpiryTime(),
Extra: tokens.ExtraData(),
},
} }
log.Info("login: %s", user.Name) if len(tokens.Access()) == 0 {
log.Error("empty access")
return
}
var err error
var u *models.User
if err = gh.Update(); err != nil {
// FIXME: handle error page
log.Error("connect with github error: %s", err)
return
}
var soc SocialConnector = gh
log.Info("login: %s", soc.Name())
// FIXME: login here, user email to check auth, if not registe, then generate a uniq username // FIXME: login here, user email to check auth, if not registe, then generate a uniq username
if u, err = models.GetOauth2User(soc.Identity()); err != nil {
u = &models.User{
Name: soc.Name(),
Email: soc.Email(),
Passwd: "123456",
IsActive: !base.Service.RegisterEmailConfirm,
}
if u, err = models.RegisterUser(u); err != nil {
log.Error("register user: %v", err)
return
}
oa := &models.Oauth2{}
oa.Uid = u.Id
oa.Type = soc.Type()
oa.Token = soc.Token()
oa.Identity = soc.Identity()
log.Info("oa: %v", oa)
if err = models.AddOauth2(oa); err != nil {
log.Error("add oauth2 %v", err)
return
}
}
ctx.Session.Set("userId", u.Id)
ctx.Session.Set("userName", u.Name)
ctx.Redirect("/")
} }

86
routers/user/user.go

@ -78,6 +78,11 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) {
ctx.Data["Title"] = "Log In" ctx.Data["Title"] = "Log In"
if ctx.Req.Method == "GET" { if ctx.Req.Method == "GET" {
if base.OauthService != nil {
ctx.Data["OauthEnabled"] = true
ctx.Data["OauthGitHubEnabled"] = base.OauthService.GitHub.Enabled
}
// Check auto-login. // Check auto-login.
userName := ctx.GetCookie(base.CookieUserName) userName := ctx.GetCookie(base.CookieUserName)
if len(userName) == 0 { if len(userName) == 0 {
@ -403,9 +408,12 @@ func Activate(ctx *middleware.Context) {
if user := models.VerifyUserActiveCode(code); user != nil { if user := models.VerifyUserActiveCode(code); user != nil {
user.IsActive = true user.IsActive = true
user.Rands = models.GetUserSalt() user.Rands = models.GetUserSalt()
models.UpdateUser(user) if err := models.UpdateUser(user); err != nil {
ctx.Handle(404, "user.Activate", err)
return
}
log.Trace("%s User activated: %s", ctx.Req.RequestURI, user.LowerName) log.Trace("%s User activated: %s", ctx.Req.RequestURI, user.Name)
ctx.Session.Set("userId", user.Id) ctx.Session.Set("userId", user.Id)
ctx.Session.Set("userName", user.Name) ctx.Session.Set("userName", user.Name)
@ -416,3 +424,77 @@ func Activate(ctx *middleware.Context) {
ctx.Data["IsActivateFailed"] = true ctx.Data["IsActivateFailed"] = true
ctx.HTML(200, "user/active") ctx.HTML(200, "user/active")
} }
func ForgotPasswd(ctx *middleware.Context) {
ctx.Data["Title"] = "Forgot Password"
if base.MailService == nil {
ctx.Data["IsResetDisable"] = true
ctx.HTML(200, "user/forgot_passwd")
return
}
ctx.Data["IsResetRequest"] = true
if ctx.Req.Method == "GET" {
ctx.HTML(200, "user/forgot_passwd")
return
}
email := ctx.Query("email")
u, err := models.GetUserByEmail(email)
if err != nil {
if err == models.ErrUserNotExist {
ctx.RenderWithErr("This e-mail address does not associate to any account.", "user/forgot_passwd", nil)
} else {
ctx.Handle(404, "user.ResetPasswd(check existence)", err)
}
return
}
mailer.SendResetPasswdMail(ctx.Render, u)
ctx.Data["Email"] = email
ctx.Data["Hours"] = base.Service.ActiveCodeLives / 60
ctx.Data["IsResetSent"] = true
ctx.HTML(200, "user/forgot_passwd")
}
func ResetPasswd(ctx *middleware.Context) {
code := ctx.Query("code")
if len(code) == 0 {
ctx.Error(404)
return
}
ctx.Data["Code"] = code
if ctx.Req.Method == "GET" {
ctx.Data["IsResetForm"] = true
ctx.HTML(200, "user/reset_passwd")
return
}
if u := models.VerifyUserActiveCode(code); u != nil {
// Validate password length.
passwd := ctx.Query("passwd")
if len(passwd) < 6 || len(passwd) > 30 {
ctx.Data["IsResetForm"] = true
ctx.RenderWithErr("Password length should be in 6 and 30.", "user/reset_passwd", nil)
return
}
u.Passwd = passwd
u.Rands = models.GetUserSalt()
u.Salt = models.GetUserSalt()
u.EncodePasswd()
if err := models.UpdateUser(u); err != nil {
ctx.Handle(404, "user.ResetPasswd(UpdateUser)", err)
return
}
log.Trace("%s User password reset: %s", ctx.Req.RequestURI, u.Name)
ctx.Redirect("/user/login")
return
}
ctx.Data["IsResetFailed"] = true
ctx.HTML(200, "user/reset_passwd")
}

54
serve.go

@ -14,7 +14,7 @@ import (
"strings" "strings"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/gogits/gogs/modules/log" qlog "github.com/qiniu/log"
//"github.com/gogits/git" //"github.com/gogits/git"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
@ -44,11 +44,16 @@ gogs serv provide access auth for repositories`,
} }
func newLogger(execDir string) { func newLogger(execDir string) {
level := "0"
logPath := execDir + "/log/serv.log" logPath := execDir + "/log/serv.log"
os.MkdirAll(path.Dir(logPath), os.ModePerm) os.MkdirAll(path.Dir(logPath), os.ModePerm)
log.NewLogger(0, "file", fmt.Sprintf(`{"level":%s,"filename":"%s"}`, level, logPath))
log.Trace("start logging...") f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm)
if err != nil {
qlog.Fatal(err)
}
qlog.SetOutput(f)
qlog.Info("Start logging serv...")
} }
func parseCmd(cmd string) (string, string) { func parseCmd(cmd string) (string, string) {
@ -87,21 +92,18 @@ func runServ(k *cli.Context) {
keys := strings.Split(os.Args[2], "-") keys := strings.Split(os.Args[2], "-")
if len(keys) != 2 { if len(keys) != 2 {
println("auth file format error") println("auth file format error")
log.Error("auth file format error") qlog.Fatal("auth file format error")
return
} }
keyId, err := strconv.ParseInt(keys[1], 10, 64) keyId, err := strconv.ParseInt(keys[1], 10, 64)
if err != nil { if err != nil {
println("auth file format error") println("auth file format error")
log.Error("auth file format error", err) qlog.Fatal("auth file format error", err)
return
} }
user, err := models.GetUserByKeyId(keyId) user, err := models.GetUserByKeyId(keyId)
if err != nil { if err != nil {
println("You have no right to access") println("You have no right to access")
log.Error("SSH visit error: %v", err) qlog.Fatalf("SSH visit error: %v", err)
return
} }
cmd := os.Getenv("SSH_ORIGINAL_COMMAND") cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
@ -115,8 +117,7 @@ func runServ(k *cli.Context) {
rr := strings.SplitN(repoPath, "/", 2) rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 { if len(rr) != 2 {
println("Unavilable repository", args) println("Unavilable repository", args)
log.Error("Unavilable repository %v", args) qlog.Fatalf("Unavilable repository %v", args)
return
} }
repoUserName := rr[0] repoUserName := rr[0]
repoName := rr[1] repoName := rr[1]
@ -129,9 +130,8 @@ func runServ(k *cli.Context) {
repoUser, err := models.GetUserByName(repoUserName) repoUser, err := models.GetUserByName(repoUserName)
if err != nil { if err != nil {
fmt.Println("You have no right to access") println("You have no right to access")
log.Error("Get user failed", err) qlog.Fatal("Get user failed", err)
return
} }
// access check // access check
@ -140,19 +140,16 @@ func runServ(k *cli.Context) {
has, err := models.HasAccess(user.LowerName, path.Join(repoUserName, repoName), models.AU_WRITABLE) has, err := models.HasAccess(user.LowerName, path.Join(repoUserName, repoName), models.AU_WRITABLE)
if err != nil { if err != nil {
println("Inernel error:", err) println("Inernel error:", err)
log.Error(err.Error()) qlog.Fatal(err)
return
} else if !has { } else if !has {
println("You have no right to write this repository") println("You have no right to write this repository")
log.Error("User %s has no right to write repository %s", user.Name, repoPath) qlog.Fatalf("User %s has no right to write repository %s", user.Name, repoPath)
return
} }
case isRead: case isRead:
repo, err := models.GetRepositoryByName(repoUser.Id, repoName) repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
if err != nil { if err != nil {
println("Get repository error:", err) println("Get repository error:", err)
log.Error("Get repository error: " + err.Error()) qlog.Fatal("Get repository error: " + err.Error())
return
} }
if !repo.IsPrivate { if !repo.IsPrivate {
@ -162,26 +159,22 @@ func runServ(k *cli.Context) {
has, err := models.HasAccess(user.Name, repoPath, models.AU_READABLE) has, err := models.HasAccess(user.Name, repoPath, models.AU_READABLE)
if err != nil { if err != nil {
println("Inernel error") println("Inernel error")
log.Error(err.Error()) qlog.Fatal(err)
return
} }
if !has { if !has {
has, err = models.HasAccess(user.Name, repoPath, models.AU_WRITABLE) has, err = models.HasAccess(user.Name, repoPath, models.AU_WRITABLE)
if err != nil { if err != nil {
println("Inernel error") println("Inernel error")
log.Error(err.Error()) qlog.Fatal(err)
return
} }
} }
if !has { if !has {
println("You have no right to access this repository") println("You have no right to access this repository")
log.Error("You have no right to access this repository") qlog.Fatal("You have no right to access this repository")
return
} }
default: default:
println("Unknown command") println("Unknown command")
log.Error("Unknown command") qlog.Fatal("Unknown command")
return
} }
// for update use // for update use
@ -197,7 +190,6 @@ func runServ(k *cli.Context) {
if err = gitcmd.Run(); err != nil { if err = gitcmd.Run(); err != nil {
println("execute command error:", err.Error()) println("execute command error:", err.Error())
log.Error("execute command error: " + err.Error()) qlog.Fatal("execute command error: " + err.Error())
return
} }
} }

15
start.sh

@ -1,6 +1,15 @@
#!/bin/bash - #!/bin/sh -
# Copyright 2014 The Gogs Authors. All rights reserved.
# Use of this source code is governed by a MIT-style
# license that can be found in the LICENSE file.
# #
# start gogs web # start gogs web
# #
cd "$(dirname $0)" IFS='
./gogs web '
PATH=/bin:/usr/bin:/usr/local/bin
HOME=${HOME:?"need \$HOME variable"}
USER=$(whoami)
export USER HOME PATH
cd "$(dirname $0)" && exec ./gogs web

2
templates/issue/create.tmpl

@ -19,7 +19,7 @@
</div> </div>
<ul class="nav nav-tabs" data-init="tabs"> <ul class="nav nav-tabs" data-init="tabs">
<li class="active issue-write"><a href="#issue-textarea" data-toggle="tab">Write</a></li> <li class="active issue-write"><a href="#issue-textarea" data-toggle="tab">Write</a></li>
<li class="issue-preview"><a href="#issue-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repo=repo_id&issue=new" data-ajax-name="issue-preview" data-ajax-method="post" data-preview="#issue-preview">Preview</a></li> <li class="issue-preview"><a href="#issue-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repoLink={{.RepoLink}}" data-ajax-name="issue-preview" data-ajax-method="post" data-preview="#issue-preview">Preview</a></li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane" id="issue-textarea"> <div class="tab-pane" id="issue-textarea">

2
templates/issue/view.tmpl

@ -72,7 +72,7 @@
</div> </div>
<ul class="nav nav-tabs" data-init="tabs"> <ul class="nav nav-tabs" data-init="tabs">
<li class="active issue-write"><a href="#issue-textarea" data-toggle="tab">Write</a></li> <li class="active issue-write"><a href="#issue-textarea" data-toggle="tab">Write</a></li>
<li class="issue-preview"><a href="#issue-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repo=repo_id&issue=issue_id&comment=new" data-ajax-name="issue-preview" data-ajax-method="post" data-preview="#issue-preview">Preview</a></li> <li class="issue-preview"><a href="#issue-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repoLink={{.RepoLink}}" data-ajax-name="issue-preview" data-ajax-method="post" data-preview="#issue-preview">Preview</a></li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane" id="issue-textarea"> <div class="tab-pane" id="issue-textarea">

33
templates/mail/auth/reset_passwd.tmpl

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{{.User.Name}}, please reset your password</title>
</head>
<body style="background:#eee;">
<div style="color:#333; font:12px/1.5 Tahoma,Arial,sans-serif;; text-shadow:1px 1px #fff; padding:0; margin:0;">
<div style="width:600px;margin:0 auto; padding:40px 0 20px;">
<div style="border:1px solid #d9d9d9;border-radius:3px; background:#fff; box-shadow: 0px 2px 5px rgba(0, 0, 0,.05); -webkit-box-shadow: 0px 2px 5px rgba(0, 0, 0,.05);">
<div style="padding: 20px 15px;">
<h1 style="font-size:20px; padding:10px 0 20px; margin:0; border-bottom:1px solid #ddd;"><img src="{{.AppUrl}}/{{.AppLogo}}" style="height: 32px; margin-bottom: -10px;"> <a style="color:#333;text-decoration:none;" target="_blank" href="{{.AppUrl}}">{{.AppName}}</a></h1>
<div style="padding:40px 15px;">
<div style="font-size:16px; padding-bottom:30px; font-weight:bold;">
Hi <span style="color: #00BFFF;">{{.User.Name}}</span>,
</div>
<div style="font-size:14px; padding:0 15px;">
<p style="margin:0;padding:0 0 9px 0;">Please click following link to reset your password within <b>{{.ActiveCodeLives}} hours</b>.</p>
<p style="margin:0;padding:0 0 9px 0;">
<a href="{{.AppUrl}}user/reset_password?code={{.Code}}">{{.AppUrl}}user/reset_password?code={{.Code}}</a>
</p>
<p style="margin:0;padding:0 0 9px 0;">Copy and paste it to your browser if the link is not working.</p>
</div>
</div>
</div>
</div>
<div style="color:#aaa;padding:10px;text-align:center;">
© 2014 <a style="color:#888;text-decoration:none;" target="_blank" href="http://gogits.org">Gogs: Go Git Service</a>
</div>
</div>
</div>
</body>
</html>

25
templates/mail/auth/reset_password.html

@ -1,25 +0,0 @@
{{template "mail/base.html" .}}
{{define "title"}}
{{if eq .Lang "zh-CN"}}
{{.User.NickName}},重置账户密码
{{end}}
{{if eq .Lang "en-US"}}
{{.User.NickName}}, reset your password
{{end}}
{{end}}
{{define "body"}}
{{if eq .Lang "zh-CN"}}
<p style="margin:0;padding:0 0 9px 0;">点击链接重置密码,{{.ResetPwdCodeLives}} 分钟内有效</p>
<p style="margin:0;padding:0 0 9px 0;">
<a href="{{.AppUrl}}reset/{{.Code}}">{{.AppUrl}}reset/{{.Code}}</a>
</p>
<p style="margin:0;padding:0 0 9px 0;">如果链接点击无反应,请复制到浏览器打开。</p>
{{end}}
{{if eq .Lang "en-US"}}
<p style="margin:0;padding:0 0 9px 0;">Please click following link to reset your password in {{.ResetPwdCodeLives}} hours</p>
<p style="margin:0;padding:0 0 9px 0;">
<a href="{{.AppUrl}}reset/{{.Code}}">{{.AppUrl}}reset/{{.Code}}</a>
</p>
<p style="margin:0;padding:0 0 9px 0;">Copy and paste it to your browser if it's not working.</p>
{{end}}
{{end}}

66
templates/release/new.tmpl

@ -0,0 +1,66 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
{{template "repo/nav" .}}
{{template "repo/toolbar" .}}
<div id="body" class="container">
<div id="release">
<h4 id="release-head">New Release</h4>
<form id="release-new-form" action="" class="form form-inline">
<div class="form-group">
<input id="release-tag-name" type="text" class="form-control" placeholder="tag name"/>
<span class="target-at">@</span>
<div class="btn-group" id="release-new-target-select">
<button type="button" class="btn btn-default"><i class="fa fa-code-fork fa-lg fa-m"></i>
<span class="target-text">Target : </span>
<strong id="release-new-target-name"> master</strong>
</button>
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<div class="dropdown-menu clone-group-btn" id="release-new-target-branch-list">
<ul class="list-group">
<li class="list-group-item">
<a href="#" rel="master"><i class="fa fa-code-fork"></i>master</a>
</li>
</ul>
</div>
</div>
<p class="help-block">Choose an existing tag without release notes</p>
</div>
<div class="form-group" style="display: block">
<input class="form-control input-lg" id="release-new-title" name="title" type="text" placeholder="release title"/>
</div>
<div class="form-group col-md-8" style="display: block" id="release-new-content-div">
<div class="md-help pull-right">
Content with <a href="https://help.github.com/articles/markdown-basics">Markdown</a>
</div>
<ul class="nav nav-tabs" data-init="tabs">
<li class="release-write active"><a href="#release-textarea" data-toggle="tab">Write</a></li>
<li class="release-preview"><a href="#release-preview" data-toggle="tab" data-ajax="/api/v1/markdown?repo=repo_id&amp;release=new" data-ajax-name="release-preview" data-ajax-method="post" data-preview="#release-preview">Preview</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="release-textarea">
<div class="form-group">
<textarea class="form-control" name="content" id="release-new-content" rows="10" placeholder="Write some content" data-ajax-rel="release-preview" data-ajax-val="val" data-ajax-field="content"></textarea>
</div>
</div>
<div class="tab-pane release-preview-content" id="release-preview">loading...</div>
</div>
</div>
<div class="text-right form-group col-md-8" style="display: block">
<hr/>
<label for="release-new-pre-release">
<input id="release-new-pre-release" type="checkbox" name="is-pre-release" value="true"/>
<strong>This is a pre-release</strong>
</label>
<p class="help-block">We’ll point out that this release is identified as non-production ready.</p>
</div>
<div class="text-right form-group col-md-8" style="display: block">
<input type="hidden" value="id" name="repo-id">
<button class="btn-success btn">Publish release</button>
<input class="btn btn-default" type="submit" name="is-draft" value="Save Draft"/>
</div>
</form>
</div>
</div>
{{template "base/footer" .}}

5
templates/repo/setting.tmpl

@ -23,9 +23,10 @@
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input type="hidden" name="action" value="update"> <input type="hidden" name="action" value="update">
<div class="form-group"> <div class="form-group">
<label class="col-md-3 text-right">Name</label> <label class="col-md-3 text-right" for="repo-setting-name">Name</label>
<div class="col-md-9"> <div class="col-md-9">
<input class="form-control" name="name" value="{{.Repository.Name}}" title="{{.Repository.Name}}" /> <input class="form-control" name="name" value="{{.Repository.Name}}" title="{{.Repository.Name}}" id="repo-setting-name"/>
<p class="help-block hidden"><span class="text-danger">Cautious : </span>your repository name is changing !</p>
</div> </div>
</div> </div>

2
templates/repo/toolbar.tmpl

@ -15,7 +15,7 @@
{{end}} {{end}}
<li class="{{if .IsRepoToolbarReleases}}active{{end}}"><a href="{{.RepoLink}}/releases">{{if .Repository.NumReleases}}<span class="badge">{{.Repository.NumReleases}}</span> {{end}}Releases</a></li> <li class="{{if .IsRepoToolbarReleases}}active{{end}}"><a href="{{.RepoLink}}/releases">{{if .Repository.NumReleases}}<span class="badge">{{.Repository.NumReleases}}</span> {{end}}Releases</a></li>
{{if .IsRepoToolbarReleases}} {{if .IsRepoToolbarReleases}}
<li class="tmp"><a href="{{.RepoLink}}/releases/new"><button class="btn btn-primary btn-sm">New Release</button></a></li> <li class="tmp">{{if not .IsRepoReleaseNew}}<a href="{{.RepoLink}}/releases/new"><button class="btn btn-primary btn-sm">New Release</button></a>{{end}}</li>
{{end}} {{end}}
<!-- <li class="dropdown"> <!-- <li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a> <a href="#" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a>

6
templates/status/401.tmpl

@ -0,0 +1,6 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div class="container">
401 Unauthorized
</div>
{{template "base/footer" .}}

30
templates/user/forgot_passwd.tmpl

@ -0,0 +1,30 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="body" class="container">
<form action="/user/forget_password" method="post" class="form-horizontal card" id="login-card">
{{.CsrfTokenHtml}}
<h3>Reset Your Password</h3>
<div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div>
{{if .IsResetSent}}
<p>A confirmation e-mail has been sent to <b>{{.Email}}</b>, please check your inbox within {{.Hours}} hours.</p>
<hr/>
<a href="http://{{Mail2Domain .Email}}" class="btn btn-lg btn-success">Sign in to your e-mail</a>
{{else if .IsResetRequest}}
<div class="form-group {{if .Err_Email}}has-error has-feedback{{end}}">
<label class="col-md-3 control-label">Email: </label>
<div class="col-md-7">
<input name="email" class="form-control" placeholder="Type your e-mail address" required="required">
</div>
</div>
<hr/>
<div class="form-group">
<div class="col-md-offset-4 col-md-6">
<button type="submit" class="btn btn-lg btn-primary">Click here to send reset confirmation e-mail</button>
</div>
</div>
{{else if .IsResetDisable}}
<p>Sorry, mail service is not enabled.</p>
{{end}}
</form>
</div>
{{template "base/footer" .}}

26
templates/user/reset_passwd.tmpl

@ -0,0 +1,26 @@
{{template "base/head" .}}
{{template "base/navbar" .}}
<div id="body" class="container">
<form action="/user/reset_password?code={{.Code}}" method="post" class="form-horizontal card" id="login-card">
{{.CsrfTokenHtml}}
<h3>Reset Your Pasword</h3>
<div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div>
{{if .IsResetForm}}
<div class="form-group">
<label class="col-md-4 control-label">Password: </label>
<div class="col-md-6">
<input name="passwd" type="password" class="form-control" placeholder="Type your password" required="required">
</div>
</div>
<hr/>
<div class="form-group">
<div class="col-md-offset-4 col-md-6">
<button type="submit" class="btn btn-lg btn-primary">Click here to reset your password</button>
</div>
</div>
{{else}}
<p>Sorry, your confirmation code has been exipired or not valid.</p>
{{end}}
</form>
</div>
{{template "base/footer" .}}

5
templates/user/setting.tmpl

@ -10,9 +10,10 @@
{{if .IsSuccess}}<p class="alert alert-success">Your profile has been successfully updated.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}} {{if .IsSuccess}}<p class="alert alert-success">Your profile has been successfully updated.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}}
<p>Your Email will be public and used for Account related notifications and any web based operations made via the web.</p> <p>Your Email will be public and used for Account related notifications and any web based operations made via the web.</p>
<div class="form-group"> <div class="form-group">
<label class="col-md-2 control-label">Username<strong class="text-danger">*</strong></label> <label class="col-md-2 control-label" for="user-setting-username">Username<strong class="text-danger">*</strong></label>
<div class="col-md-8"> <div class="col-md-8">
<input name="username" class="form-control" placeholder="Type your user name" required="required" value="{{.SignedUser.Name}}" title="{{.SignedUser.Name}}"> <input name="username" class="form-control" placeholder="Type your user name" required="required" value="{{.SignedUser.Name}}" title="{{.SignedUser.Name}}" id="user-setting-username">
<p class="help-block hidden"><span class="text-danger">Cautious : </span>your username is changing !</p>
</div> </div>
</div> </div>

7
templates/user/signin.tmpl

@ -33,7 +33,7 @@
<div class="form-group"> <div class="form-group">
<div class="col-md-offset-4 col-md-6"> <div class="col-md-offset-4 col-md-6">
<button type="submit" class="btn btn-lg btn-primary">Log In</button> <button type="submit" class="btn btn-lg btn-primary">Log In</button>
<a href="/forget-password/">Forgot your password?</a> <a href="/user/forget_password/">Forgot your password?</a>
</div> </div>
</div> </div>
@ -43,9 +43,12 @@
</div> </div>
</div> </div>
{{if .OauthEnabled}}
<div class="form-group text-center" id="social-login"> <div class="form-group text-center" id="social-login">
<a class="btn btn-danger btn-lg" href="/user/sign_up">Register new account</a> <h4>Log In with Social Accounts</h4>
{{if .OauthGitHubEnabled}}<a href="/user/login/github"><i class="fa fa-github-square fa-3x"></i></a>{{end}}
</div> </div>
{{end}}
</form> </form>
</div> </div>
{{template "base/footer" .}} {{template "base/footer" .}}

56
update.go

@ -6,7 +6,6 @@ package main
import ( import (
"container/list" "container/list"
"fmt"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@ -14,11 +13,11 @@ import (
"strings" "strings"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
qlog "github.com/qiniu/log"
"github.com/gogits/git" "github.com/gogits/git"
"github.com/gogits/gogs/models" "github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
//"github.com/qiniu/log"
) )
var CmdUpdate = cli.Command{ var CmdUpdate = cli.Command{
@ -31,17 +30,22 @@ gogs serv provide access auth for repositories`,
} }
func newUpdateLogger(execDir string) { func newUpdateLogger(execDir string) {
level := "0"
logPath := execDir + "/log/update.log" logPath := execDir + "/log/update.log"
os.MkdirAll(path.Dir(logPath), os.ModePerm) os.MkdirAll(path.Dir(logPath), os.ModePerm)
log.NewLogger(0, "file", fmt.Sprintf(`{"level":%s,"filename":"%s"}`, level, logPath))
log.Trace("start logging...") f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm)
if err != nil {
qlog.Fatal(err)
}
qlog.SetOutput(f)
qlog.Info("Start logging update...")
} }
// for command: ./gogs update // for command: ./gogs update
func runUpdate(c *cli.Context) { func runUpdate(c *cli.Context) {
execDir, _ := base.ExecDir() execDir, _ := base.ExecDir()
newLogger(execDir) newUpdateLogger(execDir)
base.NewConfigContext() base.NewConfigContext()
models.LoadModelsConfig() models.LoadModelsConfig()
@ -54,14 +58,12 @@ func runUpdate(c *cli.Context) {
args := c.Args() args := c.Args()
if len(args) != 3 { if len(args) != 3 {
log.Error("received less 3 parameters") qlog.Fatal("received less 3 parameters")
return
} }
refName := args[0] refName := args[0]
if refName == "" { if refName == "" {
log.Error("refName is empty, shouldn't use") qlog.Fatal("refName is empty, shouldn't use")
return
} }
oldCommitId := args[1] oldCommitId := args[1]
newCommitId := args[2] newCommitId := args[2]
@ -69,8 +71,7 @@ func runUpdate(c *cli.Context) {
isNew := strings.HasPrefix(oldCommitId, "0000000") isNew := strings.HasPrefix(oldCommitId, "0000000")
if isNew && if isNew &&
strings.HasPrefix(newCommitId, "0000000") { strings.HasPrefix(newCommitId, "0000000") {
log.Error("old rev and new rev both 000000") qlog.Fatal("old rev and new rev both 000000")
return
} }
userName := os.Getenv("userName") userName := os.Getenv("userName")
@ -86,20 +87,17 @@ func runUpdate(c *cli.Context) {
repo, err := git.OpenRepository(f) repo, err := git.OpenRepository(f)
if err != nil { if err != nil {
log.Error("runUpdate.Open repoId: %v", err) qlog.Fatalf("runUpdate.Open repoId: %v", err)
return
} }
newOid, err := git.NewOidFromString(newCommitId) newOid, err := git.NewOidFromString(newCommitId)
if err != nil { if err != nil {
log.Error("runUpdate.Ref repoId: %v", err) qlog.Fatalf("runUpdate.Ref repoId: %v", err)
return
} }
newCommit, err := repo.LookupCommit(newOid) newCommit, err := repo.LookupCommit(newOid)
if err != nil { if err != nil {
log.Error("runUpdate.Ref repoId: %v", err) qlog.Fatalf("runUpdate.Ref repoId: %v", err)
return
} }
var l *list.List var l *list.List
@ -107,39 +105,33 @@ func runUpdate(c *cli.Context) {
if isNew { if isNew {
l, err = repo.CommitsBefore(newCommit.Id()) l, err = repo.CommitsBefore(newCommit.Id())
if err != nil { if err != nil {
log.Error("Find CommitsBefore erro:", err) qlog.Fatalf("Find CommitsBefore erro:", err)
return
} }
} else { } else {
oldOid, err := git.NewOidFromString(oldCommitId) oldOid, err := git.NewOidFromString(oldCommitId)
if err != nil { if err != nil {
log.Error("runUpdate.Ref repoId: %v", err) qlog.Fatalf("runUpdate.Ref repoId: %v", err)
return
} }
oldCommit, err := repo.LookupCommit(oldOid) oldCommit, err := repo.LookupCommit(oldOid)
if err != nil { if err != nil {
log.Error("runUpdate.Ref repoId: %v", err) qlog.Fatalf("runUpdate.Ref repoId: %v", err)
return
} }
l = repo.CommitsBetween(newCommit, oldCommit) l = repo.CommitsBetween(newCommit, oldCommit)
} }
if err != nil { if err != nil {
log.Error("runUpdate.Commit repoId: %v", err) qlog.Fatalf("runUpdate.Commit repoId: %v", err)
return
} }
sUserId, err := strconv.Atoi(userId) sUserId, err := strconv.Atoi(userId)
if err != nil { if err != nil {
log.Error("runUpdate.Parse userId: %v", err) qlog.Fatalf("runUpdate.Parse userId: %v", err)
return
} }
repos, err := models.GetRepositoryByName(int64(sUserId), repoName) repos, err := models.GetRepositoryByName(int64(sUserId), repoName)
if err != nil { if err != nil {
log.Error("runUpdate.GetRepositoryByName userId: %v", err) qlog.Fatalf("runUpdate.GetRepositoryByName userId: %v", err)
return
} }
commits := make([]*base.PushCommit, 0) commits := make([]*base.PushCommit, 0)
@ -163,6 +155,6 @@ func runUpdate(c *cli.Context) {
//commits = append(commits, []string{lastCommit.Id().String(), lastCommit.Message()}) //commits = append(commits, []string{lastCommit.Id().String(), lastCommit.Message()})
if err = models.CommitRepoAction(int64(sUserId), userName, actEmail, if err = models.CommitRepoAction(int64(sUserId), userName, actEmail,
repos.Id, repoName, git.BranchName(refName), &base.PushCommits{l.Len(), commits}); err != nil { repos.Id, repoName, git.BranchName(refName), &base.PushCommits{l.Len(), commits}); err != nil {
log.Error("runUpdate.models.CommitRepoAction: %v", err) qlog.Fatalf("runUpdate.models.CommitRepoAction: %v", err)
} }
} }

50
web.go

@ -11,8 +11,7 @@ import (
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/go-martini/martini" "github.com/go-martini/martini"
// "github.com/martini-contrib/oauth2" qlog "github.com/qiniu/log"
// "github.com/martini-contrib/sessions"
"github.com/gogits/binding" "github.com/gogits/binding"
@ -21,6 +20,7 @@ import (
"github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware" "github.com/gogits/gogs/modules/middleware"
"github.com/gogits/gogs/modules/oauth2"
"github.com/gogits/gogs/routers" "github.com/gogits/gogs/routers"
"github.com/gogits/gogs/routers/admin" "github.com/gogits/gogs/routers/admin"
"github.com/gogits/gogs/routers/api/v1" "github.com/gogits/gogs/routers/api/v1"
@ -51,27 +51,25 @@ func newMartini() *martini.ClassicMartini {
} }
func runWeb(*cli.Context) { func runWeb(*cli.Context) {
fmt.Println("Server is running...")
routers.GlobalInit() routers.GlobalInit()
log.Info("%s %s", base.AppName, base.AppVer)
m := newMartini() m := newMartini()
// Middlewares. // Middlewares.
m.Use(middleware.Renderer(middleware.RenderOptions{Funcs: []template.FuncMap{base.TemplateFuncs}})) m.Use(middleware.Renderer(middleware.RenderOptions{Funcs: []template.FuncMap{base.TemplateFuncs}}))
// scope := "https://api.github.com/user"
// oauth2.PathCallback = "/oauth2callback"
// m.Use(sessions.Sessions("my_session", sessions.NewCookieStore([]byte("secret123"))))
// m.Use(oauth2.Github(&oauth2.Options{
// ClientId: "09383403ff2dc16daaa1",
// ClientSecret: "5f6e7101d30b77952aab22b75eadae17551ea6b5",
// RedirectURL: base.AppUrl + oauth2.PathCallback,
// Scopes: []string{scope},
// }))
m.Use(middleware.InitContext()) m.Use(middleware.InitContext())
if base.OauthService != nil {
if base.OauthService.GitHub.Enabled {
m.Use(oauth2.Github(&oauth2.Options{
ClientId: base.OauthService.GitHub.ClientId,
ClientSecret: base.OauthService.GitHub.ClientSecret,
RedirectURL: base.AppUrl + oauth2.PathCallback[1:],
Scopes: []string{base.OauthService.GitHub.Scopes},
}))
}
}
reqSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true}) reqSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true})
ignSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: base.Service.RequireSignInView}) ignSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: base.Service.RequireSignInView})
reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true}) reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true})
@ -92,9 +90,11 @@ func runWeb(*cli.Context) {
m.Get("/avatar/:hash", avt.ServeHTTP) m.Get("/avatar/:hash", avt.ServeHTTP)
m.Group("/user", func(r martini.Router) { m.Group("/user", func(r martini.Router) {
// r.Any("/login/github", user.SocialSignIn)
r.Any("/login", binding.BindIgnErr(auth.LogInForm{}), user.SignIn) r.Any("/login", binding.BindIgnErr(auth.LogInForm{}), user.SignIn)
r.Any("/login/github", oauth2.LoginRequired, user.SocialSignIn)
r.Any("/sign_up", binding.BindIgnErr(auth.RegisterForm{}), user.SignUp) r.Any("/sign_up", binding.BindIgnErr(auth.RegisterForm{}), user.SignUp)
r.Any("/forget_password", user.ForgotPasswd)
r.Any("/reset_password", user.ResetPasswd)
}, reqSignOut) }, reqSignOut)
m.Group("/user", func(r martini.Router) { m.Group("/user", func(r martini.Router) {
r.Any("/logout", user.SignOut) r.Any("/logout", user.SignOut)
@ -148,6 +148,7 @@ func runWeb(*cli.Context) {
r.Get("/issues", repo.Issues) r.Get("/issues", repo.Issues)
r.Get("/issues/:index", repo.ViewIssue) r.Get("/issues/:index", repo.ViewIssue)
r.Get("/releases", repo.Releases) r.Get("/releases", repo.Releases)
r.Any("/releases/new", repo.ReleasesNew)
r.Get("/pulls", repo.Pulls) r.Get("/pulls", repo.Pulls)
r.Get("/branches", repo.Branches) r.Get("/branches", repo.Branches)
}, ignSignIn, middleware.RepoAssignment(true)) }, ignSignIn, middleware.RepoAssignment(true))
@ -169,12 +170,21 @@ func runWeb(*cli.Context) {
// Not found handler. // Not found handler.
m.NotFound(routers.NotFound) m.NotFound(routers.NotFound)
protocol := base.Cfg.MustValue("server", "PROTOCOL", "http")
listenAddr := fmt.Sprintf("%s:%s", listenAddr := fmt.Sprintf("%s:%s",
base.Cfg.MustValue("server", "HTTP_ADDR"), base.Cfg.MustValue("server", "HTTP_ADDR"),
base.Cfg.MustValue("server", "HTTP_PORT", "3000")) base.Cfg.MustValue("server", "HTTP_PORT", "3000"))
log.Info("Listen: %s", listenAddr)
if err := http.ListenAndServe(listenAddr, m); err != nil { if protocol == "http" {
fmt.Println(err.Error()) log.Info("Listen: http://%s", listenAddr)
//log.Critical(err.Error()) // not working now if err := http.ListenAndServe(listenAddr, m); err != nil {
qlog.Error(err.Error())
}
} else if protocol == "https" {
log.Info("Listen: https://%s", listenAddr)
if err := http.ListenAndServeTLS(listenAddr, base.Cfg.MustValue("server", "CERT_FILE"),
base.Cfg.MustValue("server", "KEY_FILE"), m); err != nil {
qlog.Error(err.Error())
}
} }
} }

Loading…
Cancel
Save