From be0ba9ea88aff8a658d0495d36accf944b74888d Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 19 Feb 2014 17:50:53 +0800 Subject: [PATCH] add ssh supports(unfinished) --- conf/app.ini | 2 +- gogs.go | 65 ++++++++++--------- models/models.go | 8 ++- models/repo.go | 15 +++-- models/user.go | 13 ++++ serve.go | 164 +++++++++++++++++++++++++++++++++++++++++++++++ utils/conf.go | 26 +++++++- web.go | 51 +++++++++++++++ 8 files changed, 305 insertions(+), 39 deletions(-) create mode 100644 serve.go create mode 100644 web.go diff --git a/conf/app.ini b/conf/app.ini index fe279cb5..c8fba31d 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -9,4 +9,4 @@ DB_TYPE = mysql HOST = NAME = gogs USER = root -PASSWD = root \ No newline at end of file +PASSWD = diff --git a/gogs.go b/gogs.go index 140ee651..a0ed460d 100644 --- a/gogs.go +++ b/gogs.go @@ -1,44 +1,49 @@ -// 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. +// Copyright 2013-2014 gogs authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// gogs(Go Git Service) is a Go clone of Github. package main import ( - "fmt" - "net/http" + "os" + "runtime" - "github.com/codegangsta/martini" - "github.com/martini-contrib/render" - - "github.com/gogits/gogs/routers" - "github.com/gogits/gogs/routers/user" - "github.com/gogits/gogs/utils" - "github.com/gogits/gogs/utils/log" + "github.com/codegangsta/cli" ) +// +build go1.1 + +// Test that go1.1 tag above is included in builds. main.go refers to this definition. +const go11tag = true + const APP_VER = "0.0.0.0218" func init() { - + runtime.GOMAXPROCS(runtime.NumCPU()) } func main() { - log.Info("%s %s", utils.Cfg.MustValue("", "APP_NAME"), APP_VER) - - m := martini.Classic() - - // Middleware. - m.Use(render.Renderer()) - - // Routers. - m.Get("/", routers.Dashboard) - m.Get("/user/signin", user.SignIn) - m.Any("/user/signup", user.SignUp) - - listenAddr := fmt.Sprintf("%s:%s", - utils.Cfg.MustValue("server", "HTTP_ADDR"), - utils.Cfg.MustValue("server", "HTTP_PORT", "3000")) - log.Info("Listen: %s", listenAddr) - http.ListenAndServe(listenAddr, m) + app := cli.NewApp() + app.Name = "gogs" + app.Usage = "Go Git Service" + app.Version = APP_VER + app.Commands = []cli.Command{ + CmdWeb, + CmdServ, + } + app.Flags = append(app.Flags, []cli.Flag{ + cli.BoolFlag{"noterm", "disable color output"}, + }...) + app.Run(os.Args) } diff --git a/models/models.go b/models/models.go index b4e69d94..55e441c5 100644 --- a/models/models.go +++ b/models/models.go @@ -17,7 +17,7 @@ import ( var ( orm *xorm.Engine - repoRootPath string + RepoRootPath string ) type Members struct { @@ -71,5 +71,9 @@ func setEngine() { func init() { setEngine() - orm.Sync(new(User)) + err := orm.Sync(new(User), new(PublicKey), new(Repo), new(Access)) + if err != nil { + log.Error("sync database struct error: %s", err) + os.Exit(1) + } } diff --git a/models/repo.go b/models/repo.go index 489530f2..1eeeaa63 100644 --- a/models/repo.go +++ b/models/repo.go @@ -29,15 +29,22 @@ type Repo struct { // check if repository is exist func IsRepositoryExist(user *User, reposName string) (bool, error) { repo := Repo{OwnerId: user.Id} - // TODO: get repository by nocase name - return orm.Where("lower_name = ?", strings.ToLower(reposName)).Get(&repo) + has, err := orm.Where("lower_name = ?", strings.ToLower(reposName)).Get(&repo) + if err != nil { + return has, err + } + s, err := os.Stat(filepath.Join(RepoRootPath, user.Name, reposName)) + if err != nil { + return false, err + } + return s.IsDir(), nil } // // create a repository for a user or orgnaziation // func CreateRepository(user *User, reposName string) (*Repo, error) { - p := filepath.Join(repoRootPath, user.Name) + p := filepath.Join(RepoRootPath, user.Name) os.MkdirAll(p, os.ModePerm) f := filepath.Join(p, reposName+".git") _, err := git.InitRepository(f, false) @@ -108,7 +115,7 @@ func DeleteRepository(user *User, reposName string) (err error) { session.Rollback() return err } - if err = os.RemoveAll(filepath.Join(repoRootPath, user.Name, reposName+".git")); err != nil { + if err = os.RemoveAll(filepath.Join(RepoRootPath, user.Name, reposName+".git")); err != nil { // TODO: log and delete manully return err } diff --git a/models/user.go b/models/user.go index bc9b8fc7..6cf7f674 100644 --- a/models/user.go +++ b/models/user.go @@ -123,6 +123,19 @@ func (user *User) EncodePasswd() error { return err } +func GetUserByKeyId(keyId int64) (*User, error) { + user := new(User) + has, err := orm.Sql("select a.* from user as a, public_key as b where a.id = b.owner_id and b.id=?", keyId).Get(user) + if err != nil { + return nil, err + } + if !has { + err = errors.New("not exist key owner") + return nil, err + } + return user, nil +} + // LoginUserPlain validates user by raw user name and password. func LoginUserPlain(name, passwd string) (*User, error) { user := User{Name: name, Passwd: passwd} diff --git a/serve.go b/serve.go new file mode 100644 index 00000000..066faa30 --- /dev/null +++ b/serve.go @@ -0,0 +1,164 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + + "github.com/codegangsta/cli" + "github.com/gogits/gogs/models" + "github.com/gogits/gogs/utils/log" +) + +var ( + COMMANDS_READONLY = map[string]int{ + "git-upload-pack": models.AU_WRITABLE, + "git upload-pack": models.AU_WRITABLE, + } + + COMMANDS_WRITE = map[string]int{ + "git-receive-pack": models.AU_READABLE, + "git receive-pack": models.AU_READABLE, + } +) + +var CmdServ = cli.Command{ + Name: "serv", + Usage: "just run", + Description: ` +gogs serv`, + Action: runServ, + Flags: []cli.Flag{ + //cli.BoolFlag{"update, u", "update pakcage(s) and dependencies if any"}, + //cli.BoolFlag{"verbose, v", "show process details"}, + }, +} + +func In(b string, sl map[string]int) bool { + _, e := sl[b] + return e +} + +func runServ(*cli.Context) { + keys := strings.Split(os.Args[2], "-") + if len(keys) != 2 { + fmt.Println("auth file format error") + return + } + + keyId, err := strconv.ParseInt(keys[1], 10, 64) + if err != nil { + fmt.Println("auth file format error") + return + } + user, err := models.GetUserByKeyId(keyId) + if err != nil { + fmt.Println("You have no right to access") + return + } + + cmd := os.Getenv("SSH_ORIGINAL_COMMAND") + if cmd == "" { + fmt.Printf("Hi %s! You've successfully authenticated, but Gogits does not provide shell access.\n", user.Name) + return + } + + f, _ := os.Create("test2.log") + f.WriteString(cmd) + f.Close() + + log.Info("cmd is %s", cmd) + + verb, args := parseCmd(cmd) + rr := strings.SplitN(strings.Trim(args, "'"), "/", 1) + if len(rr) != 2 { + fmt.Printf("Unavilable repository") + return + } + repoName := rr[1] + if strings.HasSuffix(repoName, ".git") { + repoName = repoName[:len(repoName)-4] + } + isWrite := In(verb, COMMANDS_WRITE) + isRead := In(verb, COMMANDS_READONLY) + switch { + case isWrite: + has, err := models.HasAccess(user.Name, repoName, COMMANDS_WRITE[verb]) + if err != nil { + fmt.Println("Inernel error") + return + } + if !has { + fmt.Println("You have no right to access this repository") + return + } + case isRead: + has, err := models.HasAccess(user.Name, repoName, COMMANDS_READONLY[verb]) + if err != nil { + fmt.Println("Inernel error") + return + } + if !has { + has, err = models.HasAccess(user.Name, repoName, COMMANDS_WRITE[verb]) + if err != nil { + fmt.Println("Inernel error") + return + } + } + if !has { + fmt.Println("You have no right to access this repository") + return + } + default: + fmt.Println("Unknown command") + return + } + + isExist, err := models.IsRepositoryExist(user, repoName) + if err != nil { + fmt.Println("Inernel error") + return + } + + if !isExist { + if isRead { + fmt.Println("Repository is not exist") + return + } else if isWrite { + _, err := models.CreateRepository(user, repoName) + if err != nil { + fmt.Println("Create repository failed") + return + } + } + } + + fullPath := filepath.Join(models.RepoRootPath, user.Name, repoName+".git") + newcmd := fmt.Sprintf("%s '%s'", verb, fullPath) + fmt.Println(newcmd) + gitcmd := exec.Command("git", "shell", "-c", newcmd) + gitcmd.Stdout = os.Stdout + gitcmd.Stderr = os.Stderr + + err = gitcmd.Run() + if err != nil { + log.Error("execute command error: %s", err) + } +} + +func parseCmd(cmd string) (string, string) { + ss := strings.SplitN(cmd, " ", 1) + if len(ss) != 2 { + return "", "" + } + verb, args := ss[0], ss[1] + if verb == "git" { + ss = strings.SplitN(args, " ", 1) + args = ss[1] + verb = fmt.Sprintf("%s %s", verb, ss[0]) + } + return verb, args +} diff --git a/utils/conf.go b/utils/conf.go index 0b1a3990..75bd2462 100644 --- a/utils/conf.go +++ b/utils/conf.go @@ -7,17 +7,39 @@ package utils import ( "fmt" "os" + "os/exec" + "path" + "path/filepath" "github.com/Unknwon/goconfig" ) var Cfg *goconfig.ConfigFile +func exeDir() (string, error) { + file, err := exec.LookPath(os.Args[0]) + if err != nil { + return "", err + } + p, err := filepath.Abs(file) + if err != nil { + return "", err + } + return path.Dir(p), nil +} + func init() { var err error - Cfg, err = goconfig.LoadConfigFile("conf/app.ini") + workDir, err := exeDir() if err != nil { - fmt.Println("Cannot load config file 'app.ini'") + fmt.Printf("Fail to get work directory: %s\n", err) + os.Exit(2) + } + + cfgPath := filepath.Join(workDir, "conf", "app.ini") + Cfg, err = goconfig.LoadConfigFile(cfgPath) + if err != nil { + fmt.Printf("Cannot load config file '%s'\n", cfgPath) os.Exit(2) } Cfg.BlockMode = false diff --git a/web.go b/web.go new file mode 100644 index 00000000..f64b6063 --- /dev/null +++ b/web.go @@ -0,0 +1,51 @@ +// 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. + +package main + +import ( + "fmt" + "net/http" + + "github.com/codegangsta/cli" + "github.com/codegangsta/martini" + "github.com/martini-contrib/render" + + "github.com/gogits/gogs/routers" + "github.com/gogits/gogs/routers/user" + "github.com/gogits/gogs/utils" + "github.com/gogits/gogs/utils/log" +) + +var CmdWeb = cli.Command{ + Name: "web", + Usage: "just run", + Description: ` +gogs web`, + Action: runWeb, + Flags: []cli.Flag{ + //cli.BoolFlag{"update, u", "update pakcage(s) and dependencies if any"}, + //cli.BoolFlag{"verbose, v", "show process details"}, + }, +} + +func runWeb(*cli.Context) { + log.Info("%s %s", utils.Cfg.MustValue("", "APP_NAME"), APP_VER) + + m := martini.Classic() + + // Middleware. + m.Use(render.Renderer()) + + // Routers. + m.Get("/", routers.Dashboard) + m.Get("/user/signin", user.SignIn) + m.Any("/user/signup", user.SignUp) + + listenAddr := fmt.Sprintf("%s:%s", + utils.Cfg.MustValue("server", "HTTP_ADDR"), + utils.Cfg.MustValue("server", "HTTP_PORT", "3000")) + log.Info("Listen: %s", listenAddr) + http.ListenAndServe(listenAddr, m) +}