Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] GPG Verification (#425) #476

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions models/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,25 @@ func (err ErrKeyNameAlreadyUsed) Error() string {
return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name)
}

// ErrGPGKeyAccessDenied represents a "GPGKeyAccessDenied" kind of Error.
type ErrGPGKeyAccessDenied struct {
UserID int64
KeyID int64
Note string
}

// IsErrGPGKeyAccessDenied checks if an error is a ErrGPGKeyAccessDenied.
func IsErrGPGKeyAccessDenied(err error) bool {
_, ok := err.(ErrGPGKeyAccessDenied)
return ok
}

// Error pretty-prints an error of type ErrGPGKeyAccessDenied.
func (err ErrGPGKeyAccessDenied) Error() string {
return fmt.Sprintf("user does not have access to the key [user_id: %d, key_id: %d, note: %s]",
err.UserID, err.KeyID, err.Note)
}

// ErrKeyAccessDenied represents a "KeyAccessDenied" kind of error.
type ErrKeyAccessDenied struct {
UserID int64
Expand All @@ -242,6 +261,7 @@ func IsErrKeyAccessDenied(err error) bool {
return ok
}

// Error pretty-prints an error of type ErrKeyAccessDenied
func (err ErrKeyAccessDenied) Error() string {
return fmt.Sprintf("user does not have access to the key [user_id: %d, key_id: %d, note: %s]",
err.UserID, err.KeyID, err.Note)
Expand Down
105 changes: 105 additions & 0 deletions models/gpg_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// 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 models

import (
"time"

"github.com/gogits/gogs/modules/log"
)

//TODO maybe refector with ssh-key ?
//TODO database behind

// PublicGPGKey represents a GPG key.
type PublicGPGKey struct {
ID int64 `xorm:"pk autoincr"`
OwnerID int64 `xorm:"INDEX NOT NULL"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If every user only has one GPG key?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

humm? AFAIK this allows for a one-to-many setup, so a single user can have many keys.

Name string `xorm:"NOT NULL"`
Fingerprint string `xorm:"NOT NULL"`
Content string `xorm:"TEXT NOT NULL"`

Created time.Time `xorm:"-"`
}

// ListPublicGPGKeys returns a list of public keys belongs to given user.
func ListPublicGPGKeys(uid int64) ([]*PublicGPGKey, error) {
keys := make([]*PublicGPGKey, 0, 5)
return keys, x.Where("owner_id=?", uid).Find(&keys)
}

// GetPublicGPGKeyByID returns public key by given ID.
func GetPublicGPGKeyByID(keyID int64) (*PublicGPGKey, error) {
key := new(PublicGPGKey)
has, err := x.Id(keyID).Get(key)
if err != nil {
return nil, err
} else if !has {
return nil, ErrKeyNotExist{keyID}
}
return key, nil
}

// CheckPublicGPGKeyString checks if the given public key string is a valid GPG key.
// The function returns the actual public key line on success.
func CheckPublicGPGKeyString(content string) (_ string, err error) {
//TODO Implement
return "", nil
}

// AddPublicGPGKey adds new public key to database.
func AddPublicGPGKey(ownerID int64, name, content string) (*PublicKey, error) {
log.Trace(content)
//TODO Implement
return nil, nil
}

// DeletePublicGPGKey deletes GPG key information in database.
func DeletePublicGPGKey(doer *User, id int64) (err error) {
//TODO Implement
return nil
}

/* TODO
// CheckCommitWithSign checks if author's signature of commit is corresponsind to a user.
func CheckCommitWithSign(c *git.Commit) *User {
u, err := GetUserByEmail(c.Author.Email)
if err != nil {
return nil
}
ks, err := ListPublicGPGKeys(u.ID)
if err != nil {
return nil
}
return u
}

// CheckCommitsWithSign checks if author's signature of commits are corresponding to users.
func CheckCommitsWithSign(oldCommits *list.List) *list.List {
var (
u *User
emails = map[string]*User{}
newCommits = list.New()
e = oldCommits.Front()
)
for e != nil {
c := e.Value.(*git.Commit)

if v, ok := emails[c.Author.Email]; !ok {
u, _ = GetUserByEmail(c.Author.Email)
emails[c.Author.Email] = u
} else {
u = v
}

newCommits.PushBack(UserCommit{
User: u,
Commit: c,
})
e = e.Next()
}
return newCommits
}
*/
7 changes: 7 additions & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,13 @@ func RegisterRoutes(m *macaron.Macaron) {
})

m.Get("/subscriptions", user.GetMyWatchedRepos)

m.Group("/gpg_keys", func() {
m.Combo("").Get(user.ListMyPublicGPGKeys).
Post(bind(api.CreateKeyOption{}), user.CreatePublicGPGKey) //TODO use specific api descriptor
m.Combo("/:id").Get(user.GetPublicGPGKey).
Delete(user.DeletePublicGPGKey)
})
}, reqToken())

// Repositories
Expand Down
20 changes: 20 additions & 0 deletions routers/api/v1/convert/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ func ToCommit(c *git.Commit) *api.PayloadCommit {
Email: c.Committer.Email,
UserName: committerUsername,
},
/* TODO in api "github.com/gogits/go-gogs-client"
Verification: &api.PayloadVerification{
Verified: true || c.Verification.Verified, //TODO check sign
Reason: "Not implemented", //TODO check sign
Signature: c.Verification.Signature,
Payload: c.Verification.Payload,
},
*/
Timestamp: c.Author.When,
}
}
Expand All @@ -73,6 +81,18 @@ func ToPublicKey(apiLink string, key *models.PublicKey) *api.PublicKey {
}
}

// ToPublicGPGKey converts models.PublicGPGKey to api.PublicGPGKey
//TODO be more specific to GPG key with a api.PublicGPGKey ?
func ToPublicGPGKey(apiLink string, key *models.PublicGPGKey) *api.PublicKey {
return &api.PublicKey{
ID: key.ID,
Key: key.Content,
URL: apiLink + com.ToStr(key.ID),
Title: key.Name,
Created: key.Created,
}
}

// ToHook convert models.Webhook to api.Hook
func ToHook(repoLink string, w *models.Webhook) *api.Hook {
config := map[string]string{
Expand Down
10 changes: 10 additions & 0 deletions routers/api/v1/repo/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ func GetDeployKey(ctx *context.APIContext) {
ctx.JSON(200, convert.ToDeployKey(apiLink, key))
}

// HandleCheckGPGKeyStringError handle check GPGKey error
func HandleCheckGPGKeyStringError(ctx *context.APIContext, err error) {
//TODO Implement
}

// HandleAddGPGKeyError handle add GPGKey error
func HandleAddGPGKeyError(ctx *context.APIContext, err error) {
//TODO Implement
}

// HandleCheckKeyStringError handle check key error
func HandleCheckKeyStringError(ctx *context.APIContext, err error) {
if models.IsErrKeyUnableVerify(err) {
Expand Down
93 changes: 93 additions & 0 deletions routers/api/v1/user/gpg_keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2015 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 user

import (
api "github.com/gogits/go-gogs-client"

"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/context"
"github.com/gogits/gogs/modules/setting"
"github.com/gogits/gogs/routers/api/v1/convert"
"github.com/gogits/gogs/routers/api/v1/repo"
)

func composePublicGPGKeysAPILink() string {
return setting.AppUrl + "api/v1/user/gpg_keys/"
}

func listPublicGPGKeys(ctx *context.APIContext, uid int64) {
keys, err := models.ListPublicGPGKeys(uid)
if err != nil {
ctx.Error(500, "ListPublicGPGKeys", err)
return
}

apiLink := composePublicGPGKeysAPILink()
apiKeys := make([]*api.PublicKey, len(keys))
for i := range keys {
apiKeys[i] = convert.ToPublicGPGKey(apiLink, keys[i])
}

ctx.JSON(200, &apiKeys)
}

// https://github.com/gogits/go-gogs-client/wiki/Users-Public-GPG-Keys#list-your-public-keys
func ListMyPublicGPGKeys(ctx *context.APIContext) {
listPublicGPGKeys(ctx, ctx.User.ID)
}

// https://github.com/gogits/go-gogs-client/wiki/Users-Public-GPG-Keys#get-a-single-public-key
func GetPublicGPGKey(ctx *context.APIContext) {
key, err := models.GetPublicGPGKeyByID(ctx.ParamsInt64(":id"))
if err != nil {
if models.IsErrKeyNotExist(err) {
ctx.Status(404)
} else {
ctx.Error(500, "GetPublicGPGKeyByID", err)
}
return
}

apiLink := composePublicGPGKeysAPILink()
ctx.JSON(200, convert.ToPublicGPGKey(apiLink, key))
}

// CreateUserPublicGPGKey creates new public GPG key to given user by ID.
func CreateUserPublicGPGKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) {
content, err := models.CheckPublicGPGKeyString(form.Key)
if err != nil {
repo.HandleCheckGPGKeyStringError(ctx, err)
return
}

key, err := models.AddPublicGPGKey(uid, form.Title, content)
if err != nil {
repo.HandleAddGPGKeyError(ctx, err)
return
}
apiLink := composePublicKeysAPILink()
ctx.JSON(201, convert.ToPublicKey(apiLink, key))
}

//TODO Update api
// https://github.com/gogits/go-gogs-client/wiki/Users-Public-GPG-Keys#create-a-public-key
func CreatePublicGPGKey(ctx *context.APIContext, form api.CreateKeyOption) {
CreateUserPublicGPGKey(ctx, form, ctx.User.ID)
}

// https://github.com/gogits/go-gogs-client/wiki/Users-Public-Keys#delete-a-public-key
func DeletePublicGPGKey(ctx *context.APIContext) {
if err := models.DeletePublicGPGKey(ctx.User, ctx.ParamsInt64(":id")); err != nil {
if models.IsErrGPGKeyAccessDenied(err) {
ctx.Error(403, "", "You do not have access to this key")
} else {
ctx.Error(500, "DeletePublicGPGKey", err)
}
return
}

ctx.Status(204)
}
1 change: 1 addition & 0 deletions routers/repo/commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func Commits(ctx *context.Context) {
}
commits = renderIssueLinks(commits, ctx.Repo.RepoLink)
commits = models.ValidateCommitsWithEmails(commits)
commits = models.CheckCommitsWithSign(commits)
ctx.Data["Commits"] = commits

ctx.Data["Username"] = ctx.Repo.Owner.Name
Expand Down
1 change: 1 addition & 0 deletions routers/repo/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
entries.Sort()

ctx.Data["Files"], err = entries.GetCommitsInfo(ctx.Repo.Commit, ctx.Repo.TreePath)
//TODO commits = models.CheckCommitsWithSign(commits)
if err != nil {
ctx.Handle(500, "GetCommitsInfo", err)
return
Expand Down
9 changes: 8 additions & 1 deletion templates/repo/commits_table.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,14 @@
</td>

<td class="message collapsing has-emoji">
<a rel="nofollow" class="ui sha label" href="{{AppSubUrl}}/{{$.Username}}/{{$.Reponame}}/commit/{{.ID}}">{{ShortSha .ID.String}}</a>
<a rel="nofollow" class="ui sha label" href="{{AppSubUrl}}/{{$.Username}}/{{$.Reponame}}/commit/{{.ID}}">
{{ShortSha .ID.String}}
{{if .Verification}}
<div class="ui detail icon button" style="background: #FFF;margin: -6px -10px -4px 0px;padding: 5px 3px 5px 6px;border-left: 1px solid #BBB;" data-tooltip="{{ .Verification.Signature }}" data-position="right center">
<i class="check {{if .Verification.Verified }}green{{end}} icon"></i>
</div>
{{end}}
</a>
<span {{if gt .ParentCount 1}}class="grey text"{{end}}>{{RenderCommitMessage false .Summary $.RepoLink $.Repository.ComposeMetas}}</span>
</td>
<td class="grey text right aligned">{{TimeSince .Author.When $.Lang}}</td>
Expand Down
8 changes: 7 additions & 1 deletion templates/repo/view_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@
</td>
{{end}}
<td class="message collapsing has-emoji">
<a rel="nofollow" class="ui sha label" href="{{$.RepoLink}}/commit/{{$commit.ID}}">{{ShortSha $commit.ID.String}}</a>
<a rel="nofollow" class="ui sha label" href="{{$.RepoLink}}/commit/{{$commit.ID}}">{{ShortSha $commit.ID.String}}
{{if $commit.Verification}}
<div class="ui detail icon button" style="background: #FFF;margin: -6px -10px -4px 0px;padding: 5px 3px 5px 6px;border-left: 1px solid #BBB;" data-tooltip="{{ $commit.Verification.Signature }}" data-position="right center">
<i class="check {{if $commit.Verification.Verified }}green{{end}} icon"></i>
</div>
{{end}}
</a>
{{RenderCommitMessage false $commit.Summary $.RepoLink $.Repository.ComposeMetas}}
</td>
<td class="text grey right age">{{TimeSince $commit.Committer.When $.Lang}}</td>
Expand Down