implement sync logic

This commit is contained in:
Brad Rydzewski 2017-07-14 15:58:38 -04:00
parent 0d69426b2c
commit 35e0ee2e5f
139 changed files with 1102 additions and 38093 deletions

40
cache/cache.go vendored
View file

@ -1,40 +0,0 @@
package cache
//go:generate mockery -name Cache -output mock -case=underscore
import (
"time"
"github.com/koding/cache"
"golang.org/x/net/context"
)
type Cache interface {
Get(string) (interface{}, error)
Set(string, interface{}) error
Delete(string) error
}
func Get(c context.Context, key string) (interface{}, error) {
return FromContext(c).Get(key)
}
func Set(c context.Context, key string, value interface{}) error {
return FromContext(c).Set(key, value)
}
func Delete(c context.Context, key string) error {
return FromContext(c).Delete(key)
}
// Default creates an in-memory cache with the default
// 30 minute expiration period.
func Default() Cache {
return NewTTL(time.Minute * 30)
}
// NewTTL returns an in-memory cache with the specified
// ttl expiration period.
func NewTTL(t time.Duration) Cache {
return cache.NewMemoryWithTTL(t)
}

34
cache/cache_test.go vendored
View file

@ -1,34 +0,0 @@
package cache
import (
"testing"
"github.com/franela/goblin"
"github.com/gin-gonic/gin"
)
func TestCache(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Cache", func() {
var c *gin.Context
g.BeforeEach(func() {
c = new(gin.Context)
ToContext(c, Default())
})
g.It("Should set and get an item", func() {
Set(c, "foo", "bar")
v, e := Get(c, "foo")
g.Assert(v).Equal("bar")
g.Assert(e == nil).IsTrue()
})
g.It("Should return nil when item not found", func() {
v, e := Get(c, "foo")
g.Assert(v == nil).IsTrue()
g.Assert(e == nil).IsFalse()
})
})
}

23
cache/context.go vendored
View file

@ -1,23 +0,0 @@
package cache
import (
"golang.org/x/net/context"
)
const key = "cache"
// Setter defines a context that enables setting values.
type Setter interface {
Set(string, interface{})
}
// FromContext returns the Cache associated with this context.
func FromContext(c context.Context) Cache {
return c.Value(key).(Cache)
}
// ToContext adds the Cache to this context if it supports
// the Setter interface.
func ToContext(c Setter, cache Cache) {
c.Set(key, cache)
}

99
cache/helper.go vendored
View file

@ -1,99 +0,0 @@
package cache
import (
"fmt"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"golang.org/x/net/context"
)
// GetPerms returns the user permissions repositories from the cache
// associated with the current repository.
func GetPerms(c context.Context, user *model.User, owner, name string) (*model.Perm, error) {
key := fmt.Sprintf("perms:%s:%s/%s",
user.Login,
owner,
name,
)
// if we fetch from the cache we can return immediately
val, err := Get(c, key)
if err == nil {
return val.(*model.Perm), nil
}
// else we try to grab from the remote system and
// populate our cache.
perm, err := remote.Perm(c, user, owner, name)
if err != nil {
return nil, err
}
Set(c, key, perm)
return perm, nil
}
// GetTeamPerms returns the user permissions from the cache
// associated with the current organization.
func GetTeamPerms(c context.Context, user *model.User, org string) (*model.Perm, error) {
key := fmt.Sprintf("perms:%s:%s",
user.Login,
org,
)
// if we fetch from the cache we can return immediately
val, err := Get(c, key)
if err == nil {
return val.(*model.Perm), nil
}
// else we try to grab from the remote system and
// populate our cache.
perm, err := remote.TeamPerm(c, user, org)
if err != nil {
return nil, err
}
Set(c, key, perm)
return perm, nil
}
// GetRepos returns the list of user repositories from the cache
// associated with the current context.
func GetRepos(c context.Context, user *model.User) ([]*model.RepoLite, error) {
key := fmt.Sprintf("repos:%s",
user.Login,
)
// if we fetch from the cache we can return immediately
val, err := Get(c, key)
if err == nil {
return val.([]*model.RepoLite), nil
}
// else we try to grab from the remote system and
// populate our cache.
repos, err := remote.Repos(c, user)
if err != nil {
return nil, err
}
Set(c, key, repos)
return repos, nil
}
// GetRepoMap returns the list of user repositories from the cache
// associated with the current context in a map structure.
func GetRepoMap(c context.Context, user *model.User) (map[string]bool, error) {
repos, err := GetRepos(c, user)
if err != nil {
return nil, err
}
repom := map[string]bool{}
for _, repo := range repos {
repom[repo.FullName] = true
}
return repom, nil
}
// DeleteRepos evicts the cached user repositories from the cache associated
// with the current context.
func DeleteRepos(c context.Context, user *model.User) error {
key := fmt.Sprintf("repos:%s",
user.Login,
)
return Delete(c, key)
}

115
cache/helper_test.go vendored
View file

@ -1,115 +0,0 @@
package cache
import (
"errors"
"fmt"
"testing"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/remote/mock"
"github.com/franela/goblin"
"github.com/gin-gonic/gin"
)
func TestHelper(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("Cache helpers", func() {
var c *gin.Context
var r *mock.Remote
g.BeforeEach(func() {
c = new(gin.Context)
ToContext(c, Default())
r = new(mock.Remote)
remote.ToContext(c, r)
})
g.It("Should get permissions from remote", func() {
r.On("Perm", fakeUser, fakeRepo.Owner, fakeRepo.Name).Return(fakePerm, nil).Once()
p, err := GetPerms(c, fakeUser, fakeRepo.Owner, fakeRepo.Name)
g.Assert(p).Equal(fakePerm)
g.Assert(err).Equal(nil)
})
g.It("Should get permissions from cache", func() {
key := fmt.Sprintf("perms:%s:%s/%s",
fakeUser.Login,
fakeRepo.Owner,
fakeRepo.Name,
)
Set(c, key, fakePerm)
r.On("Perm", fakeUser, fakeRepo.Owner, fakeRepo.Name).Return(nil, fakeErr).Once()
p, err := GetPerms(c, fakeUser, fakeRepo.Owner, fakeRepo.Name)
g.Assert(p).Equal(fakePerm)
g.Assert(err).Equal(nil)
})
g.It("Should get permissions error", func() {
r.On("Perm", fakeUser, fakeRepo.Owner, fakeRepo.Name).Return(nil, fakeErr).Once()
p, err := GetPerms(c, fakeUser, fakeRepo.Owner, fakeRepo.Name)
g.Assert(p == nil).IsTrue()
g.Assert(err).Equal(fakeErr)
})
g.It("Should set and get repos", func() {
r.On("Repos", fakeUser).Return(fakeRepos, nil).Once()
p, err := GetRepos(c, fakeUser)
g.Assert(p).Equal(fakeRepos)
g.Assert(err).Equal(nil)
})
g.It("Should get repos", func() {
key := fmt.Sprintf("repos:%s",
fakeUser.Login,
)
Set(c, key, fakeRepos)
r.On("Repos", fakeUser).Return(nil, fakeErr).Once()
p, err := GetRepos(c, fakeUser)
g.Assert(p).Equal(fakeRepos)
g.Assert(err).Equal(nil)
})
g.It("Should get repos error", func() {
r.On("Repos", fakeUser).Return(nil, fakeErr).Once()
p, err := GetRepos(c, fakeUser)
g.Assert(p == nil).IsTrue()
g.Assert(err).Equal(fakeErr)
})
g.It("Should evict repos", func() {
key := fmt.Sprintf("repos:%s",
fakeUser.Login,
)
Set(c, key, fakeRepos)
repos, err := Get(c, key)
g.Assert(repos != nil).IsTrue()
g.Assert(err == nil).IsTrue()
DeleteRepos(c, fakeUser)
repos, err = Get(c, key)
g.Assert(repos == nil).IsTrue()
})
})
}
var (
fakeErr = errors.New("Not Found")
fakeUser = &model.User{Login: "octocat"}
fakePerm = &model.Perm{true, true, true}
fakeRepo = &model.RepoLite{Owner: "octocat", Name: "Hello-World"}
fakeRepos = []*model.RepoLite{
{Owner: "octocat", Name: "Hello-World"},
{Owner: "octocat", Name: "hello-world"},
{Owner: "octocat", Name: "Spoon-Knife"},
}
)

View file

@ -80,12 +80,6 @@ var flags = []cli.Flag{
Name: "open", Name: "open",
Usage: "open user registration", Usage: "open user registration",
}, },
cli.DurationFlag{
EnvVar: "DRONE_CACHE_TTL",
Name: "cache-ttl",
Usage: "cache duration",
Value: time.Minute * 15,
},
cli.StringSliceFlag{ cli.StringSliceFlag{
EnvVar: "DRONE_ESCALATE", EnvVar: "DRONE_ESCALATE",
Name: "escalate", Name: "escalate",
@ -415,7 +409,6 @@ func server(c *cli.Context) error {
ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, true), ginrus.Ginrus(logrus.StandardLogger(), time.RFC3339, true),
middleware.Version, middleware.Version,
middleware.Config(c), middleware.Config(c),
middleware.Cache(c),
middleware.Store(c, store_), middleware.Store(c, store_),
middleware.Remote(remote_), middleware.Remote(remote_),
) )

View file

@ -1,7 +1,21 @@
package model package model
type Perm struct { // PermStore persists repository permissions information to storage.
Pull bool `json:"pull"` type PermStore interface {
Push bool `json:"push"` PermFind(user *User, repo *Repo) (*Perm, error)
Admin bool `json:"admin"` PermUpsert(perm *Perm) error
PermBatch(perms []*Perm) error
PermDelete(perm *Perm) error
// PermFlush(user *User) error
}
// Perm defines a repository permission for an individual user.
type Perm struct {
UserID int64 `json:"-" meddler:"perm_user_id"`
RepoID int64 `json:"-" meddler:"perm_repo_id"`
Repo string `json:"-" meddler:"-"`
Pull bool `json:"pull" meddler:"perm_pull"`
Push bool `json:"push" meddler:"perm_push"`
Admin bool `json:"admin" meddler:"perm_admin"`
Synced int64 `json:"synced" meddler:"perm_synced"`
} }

View file

@ -27,6 +27,7 @@ type Repo struct {
IsTrusted bool `json:"trusted" meddler:"repo_trusted"` IsTrusted bool `json:"trusted" meddler:"repo_trusted"`
IsStarred bool `json:"starred,omitempty" meddler:"-"` IsStarred bool `json:"starred,omitempty" meddler:"-"`
IsGated bool `json:"gated" meddler:"repo_gated"` IsGated bool `json:"gated" meddler:"repo_gated"`
IsActive bool `json:"active,omitempty" meddler:"repo_active"`
AllowPull bool `json:"allow_pr" meddler:"repo_allow_pr"` AllowPull bool `json:"allow_pr" meddler:"repo_allow_pr"`
AllowPush bool `json:"allow_push" meddler:"repo_allow_push"` AllowPush bool `json:"allow_push" meddler:"repo_allow_push"`
AllowDeploy bool `json:"allow_deploys" meddler:"repo_allow_deploys"` AllowDeploy bool `json:"allow_deploys" meddler:"repo_allow_deploys"`
@ -34,6 +35,7 @@ type Repo struct {
Counter int `json:"last_build" meddler:"repo_counter"` Counter int `json:"last_build" meddler:"repo_counter"`
Config string `json:"config_file" meddler:"repo_config_path"` Config string `json:"config_file" meddler:"repo_config_path"`
Hash string `json:"-" meddler:"repo_hash"` Hash string `json:"-" meddler:"repo_hash"`
Perm *Perm `json:"-" meddler:"-"`
} }
// RepoPatch represents a repository patch object. // RepoPatch represents a repository patch object.

View file

@ -34,6 +34,9 @@ type User struct {
// Activate indicates the user is active in the system. // Activate indicates the user is active in the system.
Active bool `json:"active" meddler:"user_active"` Active bool `json:"active" meddler:"user_active"`
// Synced is the timestamp when the user was synced with the remote system.
Synced int64 `json:"synced" meddler:"user_synced"`
// Admin indicates the user is a system administrator. // Admin indicates the user is a system administrator.
// //
// NOTE: This is sourced from the DRONE_ADMINS environment variable and is no // NOTE: This is sourced from the DRONE_ADMINS environment variable and is no

View file

@ -114,11 +114,6 @@ func (c *config) Teams(u *model.User) ([]*model.Team, error) {
return convertTeamList(resp.Values), nil return convertTeamList(resp.Values), nil
} }
// TeamPerm is not supported by the Bitbucket driver.
func (c *config) TeamPerm(u *model.User, org string) (*model.Perm, error) {
return nil, nil
}
// Repo returns the named Bitbucket repository. // Repo returns the named Bitbucket repository.
func (c *config) Repo(u *model.User, owner, name string) (*model.Repo, error) { func (c *config) Repo(u *model.User, owner, name string) (*model.Repo, error) {
repo, err := c.newClient(u).FindRepo(owner, name) repo, err := c.newClient(u).FindRepo(owner, name)
@ -130,10 +125,10 @@ func (c *config) Repo(u *model.User, owner, name string) (*model.Repo, error) {
// Repos returns a list of all repositories for Bitbucket account, including // Repos returns a list of all repositories for Bitbucket account, including
// organization repositories. // organization repositories.
func (c *config) Repos(u *model.User) ([]*model.RepoLite, error) { func (c *config) Repos(u *model.User) ([]*model.Repo, error) {
client := c.newClient(u) client := c.newClient(u)
var all []*model.RepoLite var all []*model.Repo
accounts := []string{u.Login} accounts := []string{u.Login}
resp, err := client.ListTeams(&internal.ListTeamOpts{ resp, err := client.ListTeams(&internal.ListTeamOpts{
@ -153,7 +148,7 @@ func (c *config) Repos(u *model.User) ([]*model.RepoLite, error) {
return all, err return all, err
} }
for _, repo := range repos { for _, repo := range repos {
all = append(all, convertRepoLite(repo)) all = append(all, convertRepo(repo))
} }
} }
return all, nil return all, nil

View file

@ -110,17 +110,6 @@ func cloneLink(repo *internal.Repo) string {
return clone return clone
} }
// convertRepoLite is a helper function used to convert a Bitbucket repository
// structure to the simplified Drone repository structure.
func convertRepoLite(from *internal.Repo) *model.RepoLite {
return &model.RepoLite{
Owner: strings.Split(from.FullName, "/")[0],
Name: strings.Split(from.FullName, "/")[1],
FullName: from.FullName,
Avatar: from.Owner.Links.Avatar.Href,
}
}
// convertUser is a helper function used to convert a Bitbucket user account // convertUser is a helper function used to convert a Bitbucket user account
// structure to the Drone User structure. // structure to the Drone User structure.
func convertUser(from *internal.Account, token *oauth2.Token) *model.User { func convertUser(from *internal.Account, token *oauth2.Token) *model.User {

View file

@ -49,18 +49,6 @@ func Test_helper(t *testing.T) {
g.Assert(convertDesc(model.StatusError)).Equal(descError) g.Assert(convertDesc(model.StatusError)).Equal(descError)
}) })
g.It("should convert repository lite", func() {
from := &internal.Repo{}
from.FullName = "octocat/hello-world"
from.Owner.Links.Avatar.Href = "http://..."
to := convertRepoLite(from)
g.Assert(to.Avatar).Equal(from.Owner.Links.Avatar.Href)
g.Assert(to.FullName).Equal(from.FullName)
g.Assert(to.Owner).Equal("octocat")
g.Assert(to.Name).Equal("hello-world")
})
g.It("should convert repository", func() { g.It("should convert repository", func() {
from := &internal.Repo{ from := &internal.Repo{
FullName: "octocat/hello-world", FullName: "octocat/hello-world",

View file

@ -140,14 +140,14 @@ func (c *Config) Repo(u *model.User, owner, name string) (*model.Repo, error) {
return convertRepo(repo), nil return convertRepo(repo), nil
} }
func (c *Config) Repos(u *model.User) ([]*model.RepoLite, error) { func (c *Config) Repos(u *model.User) ([]*model.Repo, error) {
repos, err := internal.NewClientWithToken(c.URL, c.Consumer, u.Token).FindRepos() repos, err := internal.NewClientWithToken(c.URL, c.Consumer, u.Token).FindRepos()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var all []*model.RepoLite var all []*model.Repo
for _, repo := range repos { for _, repo := range repos {
all = append(all, convertRepoLite(repo)) all = append(all, convertRepo(repo))
} }
return all, nil return all, nil
@ -233,7 +233,7 @@ func CreateConsumer(URL string, ConsumerKey string, PrivateKey *rsa.PrivateKey)
consumer.HttpClient = &http.Client{ consumer.HttpClient = &http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
}, },
} }
return consumer return consumer

View file

@ -86,19 +86,6 @@ func convertRepo(from *internal.Repo) *model.Repo {
} }
// convertRepoLite is a helper function used to convert a Bitbucket repository
// structure to the simplified Drone repository structure.
func convertRepoLite(from *internal.Repo) *model.RepoLite {
return &model.RepoLite{
Owner: from.Project.Key,
Name: from.Slug,
FullName: from.Project.Key + "/" + from.Slug,
//TODO: find the avatar for the repo
//Avatar: might need another ws call?
}
}
// convertPushHook is a helper function used to convert a Bitbucket push // convertPushHook is a helper function used to convert a Bitbucket push
// hook to the Drone build struct holding commit information. // hook to the Drone build struct holding commit information.
func convertPushHook(hook *internal.PostHook, baseURL string) *model.Build { func convertPushHook(hook *internal.PostHook, baseURL string) *model.Build {

View file

@ -1,11 +1,12 @@
package bitbucketserver package bitbucketserver
import ( import (
"testing"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote/bitbucketserver/internal" "github.com/drone/drone/remote/bitbucketserver/internal"
"github.com/franela/goblin" "github.com/franela/goblin"
"github.com/mrjones/oauth" "github.com/mrjones/oauth"
"testing"
) )
func Test_helper(t *testing.T) { func Test_helper(t *testing.T) {
@ -13,17 +14,6 @@ func Test_helper(t *testing.T) {
g := goblin.Goblin(t) g := goblin.Goblin(t)
g.Describe("Bitbucket Server converter", func() { g.Describe("Bitbucket Server converter", func() {
g.It("should convert repository lite", func() {
from := &internal.Repo{}
from.Project.Key = "octocat"
from.Slug = "hello-world"
to := convertRepoLite(from)
g.Assert(to.FullName).Equal("octocat/hello-world")
g.Assert(to.Owner).Equal("octocat")
g.Assert(to.Name).Equal("hello-world")
})
g.It("should convert repository", func() { g.It("should convert repository", func() {
from := &internal.Repo{ from := &internal.Repo{
Slug: "hello-world", Slug: "hello-world",

View file

@ -200,8 +200,8 @@ func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
// Repos returns a list of all repositories for the Gitea account, including // Repos returns a list of all repositories for the Gitea account, including
// organization repositories. // organization repositories.
func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) { func (c *client) Repos(u *model.User) ([]*model.Repo, error) {
repos := []*model.RepoLite{} repos := []*model.Repo{}
client := c.newClientToken(u.Token) client := c.newClientToken(u.Token)
all, err := client.ListMyRepos() all, err := client.ListMyRepos()
@ -210,7 +210,7 @@ func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) {
} }
for _, repo := range all { for _, repo := range all {
repos = append(repos, toRepoLite(repo)) repos = append(repos, toRepo(repo))
} }
return repos, err return repos, err
} }

View file

@ -12,21 +12,6 @@ import (
"github.com/drone/drone/model" "github.com/drone/drone/model"
) )
// helper function that converts a Gitea repository to a Drone repository.
func toRepoLite(from *gitea.Repository) *model.RepoLite {
name := strings.Split(from.FullName, "/")[1]
avatar := expandAvatar(
from.HTMLURL,
from.Owner.AvatarURL,
)
return &model.RepoLite{
Name: name,
Owner: from.Owner.UserName,
FullName: from.FullName,
Avatar: avatar,
}
}
// helper function that converts a Gitea repository to a Drone repository. // helper function that converts a Gitea repository to a Drone repository.
func toRepo(from *gitea.Repository) *model.Repo { func toRepo(from *gitea.Repository) *model.Repo {
name := strings.Split(from.FullName, "/")[1] name := strings.Split(from.FullName, "/")[1]

View file

@ -177,21 +177,6 @@ func Test_parse(t *testing.T) {
g.Assert(repo.IsPrivate).Equal(from.Private) g.Assert(repo.IsPrivate).Equal(from.Private)
}) })
g.It("Should return a RepoLite struct from a Gitea Repo", func() {
from := gitea.Repository{
FullName: "gophers/hello-world",
Owner: &gitea.User{
UserName: "gordon",
AvatarURL: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87",
},
}
repo := toRepoLite(&from)
g.Assert(repo.FullName).Equal(from.FullName)
g.Assert(repo.Owner).Equal(from.Owner.UserName)
g.Assert(repo.Name).Equal("hello-world")
g.Assert(repo.Avatar).Equal(from.Owner.AvatarURL)
})
g.It("Should correct a malformed avatar url", func() { g.It("Should correct a malformed avatar url", func() {
var urls = []struct { var urls = []struct {

View file

@ -80,6 +80,7 @@ func convertRepo(from *github.Repository, private bool) *model.Repo {
Avatar: *from.Owner.AvatarURL, Avatar: *from.Owner.AvatarURL,
Kind: model.RepoGit, Kind: model.RepoGit,
Branch: defaultBranch, Branch: defaultBranch,
Perm: convertPerm(from),
} }
if from.DefaultBranch != nil { if from.DefaultBranch != nil {
repo.Branch = *from.DefaultBranch repo.Branch = *from.DefaultBranch
@ -114,24 +115,24 @@ func convertTeamPerm(from *github.Membership) *model.Perm {
// convertRepoList is a helper function used to convert a GitHub repository // convertRepoList is a helper function used to convert a GitHub repository
// list to the common Drone repository structure. // list to the common Drone repository structure.
func convertRepoList(from []github.Repository) []*model.RepoLite { func convertRepoList(from []github.Repository, private bool) []*model.Repo {
var repos []*model.RepoLite var repos []*model.Repo
for _, repo := range from { for _, repo := range from {
repos = append(repos, convertRepoLite(repo)) repos = append(repos, convertRepo(&repo, private))
} }
return repos return repos
} }
// convertRepoLite is a helper function used to convert a GitHub repository // // convertRepoLite is a helper function used to convert a GitHub repository
// structure to the common Drone repository structure. // // structure to the common Drone repository structure.
func convertRepoLite(from github.Repository) *model.RepoLite { // func convertRepoLite(from github.Repository) *model.RepoLite {
return &model.RepoLite{ // return &model.RepoLite{
Owner: *from.Owner.Login, // Owner: *from.Owner.Login,
Name: *from.Name, // Name: *from.Name,
FullName: *from.FullName, // FullName: *from.FullName,
Avatar: *from.Owner.AvatarURL, // Avatar: *from.Owner.AvatarURL,
} // }
} // }
// convertTeamList is a helper function used to convert a GitHub team list to // convertTeamList is a helper function used to convert a GitHub team list to
// the common Drone repository structure. // the common Drone repository structure.

View file

@ -50,36 +50,27 @@ func Test_helper(t *testing.T) {
g.Assert(convertDesc(model.StatusError)).Equal(descError) g.Assert(convertDesc(model.StatusError)).Equal(descError)
}) })
g.It("should convert repository lite", func() {
from := github.Repository{
FullName: github.String("octocat/hello-world"),
Name: github.String("hello-world"),
Owner: &github.User{
AvatarURL: github.String("http://..."),
Login: github.String("octocat"),
},
}
to := convertRepoLite(from)
g.Assert(to.Avatar).Equal("http://...")
g.Assert(to.FullName).Equal("octocat/hello-world")
g.Assert(to.Owner).Equal("octocat")
g.Assert(to.Name).Equal("hello-world")
})
g.It("should convert repository list", func() { g.It("should convert repository list", func() {
from := []github.Repository{ from := []github.Repository{
{ {
Private: github.Bool(false),
FullName: github.String("octocat/hello-world"), FullName: github.String("octocat/hello-world"),
Name: github.String("hello-world"), Name: github.String("hello-world"),
Owner: &github.User{ Owner: &github.User{
AvatarURL: github.String("http://..."), AvatarURL: github.String("http://..."),
Login: github.String("octocat"), Login: github.String("octocat"),
}, },
HTMLURL: github.String("https://github.com/octocat/hello-world"),
CloneURL: github.String("https://github.com/octocat/hello-world.git"),
Permissions: &map[string]bool{
"push": true,
"pull": true,
"admin": true,
},
}, },
} }
to := convertRepoList(from) to := convertRepoList(from, false)
g.Assert(to[0].Avatar).Equal("http://...") g.Assert(to[0].Avatar).Equal("http://...")
g.Assert(to[0].FullName).Equal("octocat/hello-world") g.Assert(to[0].FullName).Equal("octocat/hello-world")
g.Assert(to[0].Owner).Equal("octocat") g.Assert(to[0].Owner).Equal("octocat")
@ -98,6 +89,11 @@ func Test_helper(t *testing.T) {
AvatarURL: github.String("http://..."), AvatarURL: github.String("http://..."),
Login: github.String("octocat"), Login: github.String("octocat"),
}, },
Permissions: &map[string]bool{
"push": true,
"pull": true,
"admin": true,
},
} }
to := convertRepo(&from, false) to := convertRepo(&from, false)

View file

@ -168,16 +168,6 @@ func (c *client) Teams(u *model.User) ([]*model.Team, error) {
return teams, nil return teams, nil
} }
// TeamPerm returns the user permissions for the named GitHub organization.
func (c *client) TeamPerm(u *model.User, org string) (*model.Perm, error) {
client := c.newClientToken(u.Token)
membership, _, err := client.Organizations.GetOrgMembership(u.Login, org)
if err != nil {
return nil, err
}
return convertTeamPerm(membership), nil
}
// Repo returns the named GitHub repository. // Repo returns the named GitHub repository.
func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) { func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := c.newClientToken(u.Token) client := c.newClientToken(u.Token)
@ -190,20 +180,20 @@ func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
// Repos returns a list of all repositories for GitHub account, including // Repos returns a list of all repositories for GitHub account, including
// organization repositories. // organization repositories.
func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) { func (c *client) Repos(u *model.User) ([]*model.Repo, error) {
client := c.newClientToken(u.Token) client := c.newClientToken(u.Token)
opts := new(github.RepositoryListOptions) opts := new(github.RepositoryListOptions)
opts.PerPage = 100 opts.PerPage = 100
opts.Page = 1 opts.Page = 1
var repos []*model.RepoLite var repos []*model.Repo
for opts.Page > 0 { for opts.Page > 0 {
list, resp, err := client.Repositories.List("", opts) list, resp, err := client.Repositories.List("", opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
repos = append(repos, convertRepoList(list)...) repos = append(repos, convertRepoList(list, c.PrivateMode)...)
opts.Page = resp.NextPage opts.Page = resp.NextPage
} }
return repos, nil return repos, nil

View file

@ -110,23 +110,6 @@ func Test_github(t *testing.T) {
}) })
}) })
g.Describe("Requesting organization permissions", func() {
g.It("Should return the permission details of an admin", func() {
perm, err := c.TeamPerm(fakeUser, "octocat")
g.Assert(err == nil).IsTrue()
g.Assert(perm.Admin).IsTrue()
})
g.It("Should return the permission details of a member", func() {
perm, err := c.TeamPerm(fakeUser, "github")
g.Assert(err == nil).IsTrue()
g.Assert(perm.Admin).IsFalse()
})
g.It("Should handle a not found error", func() {
_, err := c.TeamPerm(fakeUser, "org_not_found")
g.Assert(err != nil).IsTrue()
})
})
g.It("Should return a user repository list") g.It("Should return a user repository list")
g.It("Should return a user team list") g.It("Should return a user team list")

View file

@ -203,11 +203,6 @@ func (g *Gitlab) Teams(u *model.User) ([]*model.Team, error) {
return teams, nil return teams, nil
} }
// TeamPerm is not supported by the Gitlab driver.
func (g *Gitlab) TeamPerm(u *model.User, org string) (*model.Perm, error) {
return nil, nil
}
// Repo fetches the named repository from the remote system. // Repo fetches the named repository from the remote system.
func (g *Gitlab) Repo(u *model.User, owner, name string) (*model.Repo, error) { func (g *Gitlab) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := NewClient(g.URL, u.Token, g.SkipVerify) client := NewClient(g.URL, u.Token, g.SkipVerify)
@ -248,32 +243,40 @@ func (g *Gitlab) Repo(u *model.User, owner, name string) (*model.Repo, error) {
} }
// Repos fetches a list of repos from the remote system. // Repos fetches a list of repos from the remote system.
func (g *Gitlab) Repos(u *model.User) ([]*model.RepoLite, error) { func (g *Gitlab) Repos(u *model.User) ([]*model.Repo, error) {
client := NewClient(g.URL, u.Token, g.SkipVerify) client := NewClient(g.URL, u.Token, g.SkipVerify)
var repos = []*model.RepoLite{} var repos = []*model.Repo{}
all, err := client.AllProjects(g.HideArchives) all, err := client.AllProjects(g.HideArchives)
if err != nil { if err != nil {
return repos, err return repos, err
} }
for _, repo := range all { for _, repo_ := range all {
var parts = strings.Split(repo.PathWithNamespace, "/") var parts = strings.Split(repo_.PathWithNamespace, "/")
var owner = parts[0] var owner = parts[0]
var name = parts[1] var name = parts[1]
var avatar = repo.AvatarUrl
if len(avatar) != 0 && !strings.HasPrefix(avatar, "http") { repo := &model.Repo{}
avatar = fmt.Sprintf("%s/%s", g.URL, avatar) repo.Owner = owner
repo.Name = name
repo.FullName = repo_.PathWithNamespace
repo.Link = repo_.Url
repo.Clone = repo_.HttpRepoUrl
repo.Branch = "master"
if repo_.DefaultBranch != "" {
repo.Branch = repo_.DefaultBranch
} }
repos = append(repos, &model.RepoLite{ if g.PrivateMode {
Owner: owner, repo.IsPrivate = true
Name: name, } else {
FullName: repo.PathWithNamespace, repo.IsPrivate = !repo_.Public
Avatar: avatar, }
})
repos = append(repos, repo)
} }
return repos, err return repos, err
@ -295,7 +298,7 @@ func (g *Gitlab) Perm(u *model.User, owner, name string) (*model.Perm, error) {
// repo owner is granted full access // repo owner is granted full access
if repo.Owner != nil && repo.Owner.Username == u.Login { if repo.Owner != nil && repo.Owner.Username == u.Login {
return &model.Perm{true, true, true}, nil return &model.Perm{Push: true, Pull: true, Admin: true}, nil
} }
// check permission for current user // check permission for current user

View file

@ -127,11 +127,6 @@ func (c *client) Teams(u *model.User) ([]*model.Team, error) {
return teams, nil return teams, nil
} }
// TeamPerm is not supported by the Gogs driver.
func (c *client) TeamPerm(u *model.User, org string) (*model.Perm, error) {
return nil, nil
}
// Repo returns the named Gogs repository. // Repo returns the named Gogs repository.
func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) { func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
client := c.newClientToken(u.Token) client := c.newClientToken(u.Token)
@ -147,8 +142,8 @@ func (c *client) Repo(u *model.User, owner, name string) (*model.Repo, error) {
// Repos returns a list of all repositories for the Gogs account, including // Repos returns a list of all repositories for the Gogs account, including
// organization repositories. // organization repositories.
func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) { func (c *client) Repos(u *model.User) ([]*model.Repo, error) {
repos := []*model.RepoLite{} repos := []*model.Repo{}
client := c.newClientToken(u.Token) client := c.newClientToken(u.Token)
all, err := client.ListMyRepos() all, err := client.ListMyRepos()
@ -157,7 +152,7 @@ func (c *client) Repos(u *model.User) ([]*model.RepoLite, error) {
} }
for _, repo := range all { for _, repo := range all {
repos = append(repos, toRepoLite(repo)) repos = append(repos, toRepo(repo))
} }
return repos, err return repos, err
} }

View file

@ -12,21 +12,6 @@ import (
"github.com/gogits/go-gogs-client" "github.com/gogits/go-gogs-client"
) )
// helper function that converts a Gogs repository to a Drone repository.
func toRepoLite(from *gogs.Repository) *model.RepoLite {
name := strings.Split(from.FullName, "/")[1]
avatar := expandAvatar(
from.HtmlUrl,
from.Owner.AvatarUrl,
)
return &model.RepoLite{
Name: name,
Owner: from.Owner.UserName,
FullName: from.FullName,
Avatar: avatar,
}
}
// helper function that converts a Gogs repository to a Drone repository. // helper function that converts a Gogs repository to a Drone repository.
func toRepo(from *gogs.Repository) *model.Repo { func toRepo(from *gogs.Repository) *model.Repo {
name := strings.Split(from.FullName, "/")[1] name := strings.Split(from.FullName, "/")[1]

View file

@ -176,21 +176,6 @@ func Test_parse(t *testing.T) {
g.Assert(repo.IsPrivate).Equal(from.Private) g.Assert(repo.IsPrivate).Equal(from.Private)
}) })
g.It("Should return a RepoLite struct from a Gogs Repo", func() {
from := gogs.Repository{
FullName: "gophers/hello-world",
Owner: gogs.User{
UserName: "gordon",
AvatarUrl: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87",
},
}
repo := toRepoLite(&from)
g.Assert(repo.FullName).Equal(from.FullName)
g.Assert(repo.Owner).Equal(from.Owner.UserName)
g.Assert(repo.Name).Equal("hello-world")
g.Assert(repo.Avatar).Equal(from.Owner.AvatarUrl)
})
g.It("Should correct a malformed avatar url", func() { g.It("Should correct a malformed avatar url", func() {
var urls = []struct { var urls = []struct {

View file

@ -23,15 +23,11 @@ type Remote interface {
// Teams fetches a list of team memberships from the remote system. // Teams fetches a list of team memberships from the remote system.
Teams(u *model.User) ([]*model.Team, error) Teams(u *model.User) ([]*model.Team, error)
// TeamPerm fetches the named organization permissions from
// the remote system for the specified user.
TeamPerm(u *model.User, org string) (*model.Perm, error)
// Repo fetches the named repository from the remote system. // Repo fetches the named repository from the remote system.
Repo(u *model.User, owner, repo string) (*model.Repo, error) Repo(u *model.User, owner, repo string) (*model.Repo, error)
// Repos fetches a list of repos from the remote system. // Repos fetches a list of repos from the remote system.
Repos(u *model.User) ([]*model.RepoLite, error) Repos(u *model.User) ([]*model.Repo, error)
// Perm fetches the named repository permissions from // Perm fetches the named repository permissions from
// the remote system for the specified user. // the remote system for the specified user.
@ -89,19 +85,13 @@ func Teams(c context.Context, u *model.User) ([]*model.Team, error) {
return FromContext(c).Teams(u) return FromContext(c).Teams(u)
} }
// TeamPerm fetches the named organization permissions from
// the remote system for the specified user.
func TeamPerm(c context.Context, u *model.User, org string) (*model.Perm, error) {
return FromContext(c).TeamPerm(u, org)
}
// Repo fetches the named repository from the remote system. // Repo fetches the named repository from the remote system.
func Repo(c context.Context, u *model.User, owner, repo string) (*model.Repo, error) { func Repo(c context.Context, u *model.User, owner, repo string) (*model.Repo, error) {
return FromContext(c).Repo(u, owner, repo) return FromContext(c).Repo(u, owner, repo)
} }
// Repos fetches a list of repos from the remote system. // Repos fetches a list of repos from the remote system.
func Repos(c context.Context, u *model.User) ([]*model.RepoLite, error) { func Repos(c context.Context, u *model.User) ([]*model.Repo, error) {
return FromContext(c).Repos(u) return FromContext(c).Repos(u)
} }

View file

@ -1,24 +0,0 @@
package middleware
import (
"github.com/drone/drone/cache"
"github.com/gin-gonic/gin"
"github.com/urfave/cli"
)
// Cache is a middleware function that initializes the Cache and attaches to
// the context of every http.Request.
func Cache(cli *cli.Context) gin.HandlerFunc {
v := setupCache(cli)
return func(c *gin.Context) {
cache.ToContext(c, v)
}
}
// helper function to create the cache from the CLI context.
func setupCache(c *cli.Context) cache.Cache {
return cache.NewTTL(
c.Duration("cache-ttl"),
)
}

View file

@ -2,9 +2,10 @@ package session
import ( import (
"net/http" "net/http"
"time"
"github.com/drone/drone/cache"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/store" "github.com/drone/drone/store"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
@ -92,10 +93,20 @@ func SetPerm() gin.HandlerFunc {
case user != nil: case user != nil:
var err error var err error
perm, err = cache.GetPerms(c, user, repo.Owner, repo.Name) perm, err = store.FromContext(c).PermFind(user, repo)
if err != nil { if err != nil {
log.Errorf("Error fetching permission for %s %s", log.Errorf("Error fetching permission for %s %s. %s",
user.Login, repo.FullName) user.Login, repo.FullName, err)
}
if time.Unix(perm.Synced, 0).Add(time.Hour).Before(time.Now()) {
perm, err = remote.FromContext(c).Perm(user, repo.Owner, repo.Name)
if err == nil {
log.Debugf("Synced user permission for %s %s", user.Login, repo.FullName)
perm.Repo = repo.FullName
perm.UserID = user.ID
perm.Synced = time.Now().Unix()
store.FromContext(c).PermUpsert(perm)
}
} }
} }

View file

@ -1,73 +0,0 @@
package session
import (
"github.com/drone/drone/cache"
"github.com/drone/drone/model"
log "github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin"
)
func TeamPerm(c *gin.Context) *model.Perm {
user := User(c)
team := c.Param("team")
perm := &model.Perm{}
switch {
// if the user is not authenticated
case user == nil:
perm.Admin = false
perm.Pull = false
perm.Push = false
// if the user is a DRONE_ADMIN
case user.Admin:
perm.Admin = true
perm.Pull = true
perm.Push = true
// otherwise if the user is authenticated we should
// check the remote system to get the users permissiosn.
default:
log.Debugf("Fetching team permission for %s %s",
user.Login, team)
var err error
perm, err = cache.GetTeamPerms(c, user, team)
if err != nil {
// debug
log.Errorf("Error fetching team permission for %s %s",
user.Login, team)
perm.Admin = false
perm.Pull = false
perm.Push = false
}
}
if user != nil {
log.Debugf("%s granted %+v team permission to %s",
user.Login, perm, team)
} else {
log.Debugf("Guest granted %+v to %s", perm, team)
perm.Admin = false
perm.Pull = false
perm.Push = false
}
return perm
}
func MustTeamAdmin() gin.HandlerFunc {
return func(c *gin.Context) {
perm := TeamPerm(c)
if perm.Admin {
c.Next()
} else {
c.String(401, "User not authorized")
c.Abort()
}
}
}

View file

@ -1,95 +0,0 @@
package session
import (
"testing"
"github.com/drone/drone/cache"
"github.com/drone/drone/model"
"github.com/franela/goblin"
"github.com/gin-gonic/gin"
)
func TestTeamPerm(t *testing.T) {
g := goblin.Goblin(t)
g.Describe("TeamPerm", func() {
var c *gin.Context
g.BeforeEach(func() {
c = new(gin.Context)
cache.ToContext(c, cache.Default())
})
g.It("Should set admin to false (user not logged in)", func() {
p := TeamPerm(c)
g.Assert(p.Admin).IsFalse("admin should be false")
})
g.It("Should set admin to true (user is DRONE_ADMIN)", func() {
// Set DRONE_ADMIN user
c.Set("user", fakeUserAdmin)
p := TeamPerm(c)
g.Assert(p.Admin).IsTrue("admin should be false")
})
g.It("Should set admin to false (user logged in, not owner of org)", func() {
// Set fake org
params := gin.Params{
gin.Param{
Key: "team",
Value: "test_org",
},
}
c.Params = params
// Set cache to show user does not Owner/Admin
cache.Set(c, "perms:octocat:test_org", fakeTeamPerm)
// Set User
c.Set("user", fakeUser)
p := TeamPerm(c)
g.Assert(p.Admin).IsFalse("admin should be false")
})
g.It("Should set admin to true (user logged in, owner of org)", func() {
// Set fake org
params := gin.Params{
gin.Param{
Key: "team",
Value: "test_org",
},
}
c.Params = params
// Set cache to show user is Owner/Admin
cache.Set(c, "perms:octocat:test_org", fakeTeamPermAdmin)
// Set User
c.Set("user", fakeUser)
p := TeamPerm(c)
g.Assert(p.Admin).IsTrue("admin should be true")
})
})
}
var (
fakeUserAdmin = &model.User{
Login: "octocatAdmin",
Token: "cfcd2084",
Admin: true,
}
fakeUser = &model.User{
Login: "octocat",
Token: "cfcd2084",
Admin: false,
}
fakeTeamPermAdmin = &model.Perm{
Admin: true,
}
fakeTeamPerm = &model.Perm{
Admin: false,
}
)

View file

@ -47,7 +47,6 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
user.GET("", server.GetSelf) user.GET("", server.GetSelf)
user.GET("/feed", server.GetFeed) user.GET("/feed", server.GetFeed)
user.GET("/repos", server.GetRepos) user.GET("/repos", server.GetRepos)
user.GET("/repos/remote", server.GetRemoteRepos)
user.POST("/token", server.PostToken) user.POST("/token", server.PostToken)
user.DELETE("/token", server.DeleteToken) user.DELETE("/token", server.DeleteToken)
} }
@ -62,46 +61,42 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
users.DELETE("/:login", server.DeleteUser) users.DELETE("/:login", server.DeleteUser)
} }
repos := e.Group("/api/repos/:owner/:name") repo := e.Group("/api/repos/:owner/:name")
{ {
repos.POST("", server.PostRepo) repo.Use(session.SetRepo())
repo.Use(session.SetPerm())
repo.Use(session.MustPull)
repo := repos.Group("") repo.POST("", session.MustRepoAdmin(), server.PostRepo)
{ repo.GET("", server.GetRepo)
repo.Use(session.SetRepo()) repo.GET("/builds", server.GetBuilds)
repo.Use(session.SetPerm()) repo.GET("/builds/:number", server.GetBuild)
repo.Use(session.MustPull) repo.GET("/logs/:number/:ppid/:proc", server.GetBuildLogs)
repo.GET("", server.GetRepo) // requires push permissions
repo.GET("/builds", server.GetBuilds) repo.GET("/secrets", session.MustPush, server.GetSecretList)
repo.GET("/builds/:number", server.GetBuild) repo.POST("/secrets", session.MustPush, server.PostSecret)
repo.GET("/logs/:number/:ppid/:proc", server.GetBuildLogs) repo.GET("/secrets/:secret", session.MustPush, server.GetSecret)
repo.PATCH("/secrets/:secret", session.MustPush, server.PatchSecret)
repo.DELETE("/secrets/:secret", session.MustPush, server.DeleteSecret)
// requires push permissions // requires push permissions
repo.GET("/secrets", session.MustPush, server.GetSecretList) repo.GET("/registry", session.MustPush, server.GetRegistryList)
repo.POST("/secrets", session.MustPush, server.PostSecret) repo.POST("/registry", session.MustPush, server.PostRegistry)
repo.GET("/secrets/:secret", session.MustPush, server.GetSecret) repo.GET("/registry/:registry", session.MustPush, server.GetRegistry)
repo.PATCH("/secrets/:secret", session.MustPush, server.PatchSecret) repo.PATCH("/registry/:registry", session.MustPush, server.PatchRegistry)
repo.DELETE("/secrets/:secret", session.MustPush, server.DeleteSecret) repo.DELETE("/registry/:registry", session.MustPush, server.DeleteRegistry)
// requires push permissions // requires admin permissions
repo.GET("/registry", session.MustPush, server.GetRegistryList) repo.PATCH("", session.MustRepoAdmin(), server.PatchRepo)
repo.POST("/registry", session.MustPush, server.PostRegistry) repo.DELETE("", session.MustRepoAdmin(), server.DeleteRepo)
repo.GET("/registry/:registry", session.MustPush, server.GetRegistry) repo.POST("/chown", session.MustRepoAdmin(), server.ChownRepo)
repo.PATCH("/registry/:registry", session.MustPush, server.PatchRegistry) repo.POST("/repair", session.MustRepoAdmin(), server.RepairRepo)
repo.DELETE("/registry/:registry", session.MustPush, server.DeleteRegistry)
// requires push permissions repo.POST("/builds/:number", session.MustPush, server.PostBuild)
repo.PATCH("", session.MustPush, server.PatchRepo) repo.POST("/builds/:number/approve", session.MustPush, server.PostApproval)
repo.DELETE("", session.MustRepoAdmin(), server.DeleteRepo) repo.POST("/builds/:number/decline", session.MustPush, server.PostDecline)
repo.POST("/chown", session.MustRepoAdmin(), server.ChownRepo) repo.DELETE("/builds/:number/:job", session.MustPush, server.DeleteBuild)
repo.POST("/repair", session.MustRepoAdmin(), server.RepairRepo)
repo.POST("/builds/:number", session.MustPush, server.PostBuild)
repo.POST("/builds/:number/approve", session.MustPush, server.PostApproval)
repo.POST("/builds/:number/decline", session.MustPush, server.PostDecline)
repo.DELETE("/builds/:number/:job", session.MustPush, server.DeleteBuild)
}
} }
badges := e.Group("/api/badges/:owner/:name") badges := e.Group("/api/badges/:owner/:name")

View file

@ -84,6 +84,11 @@ func PostHook(c *gin.Context) {
c.AbortWithError(404, err) c.AbortWithError(404, err)
return return
} }
if !repo.IsActive {
logrus.Errorf("ignoring hook. %s/%s is inactive.", tmprepo.Owner, tmprepo.Name)
c.AbortWithError(204, err)
return
}
// get the token and verify the hook is authorized // get the token and verify the hook is authorized
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) { parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {

View file

@ -4,11 +4,11 @@ import (
"encoding/base32" "encoding/base32"
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
"github.com/drone/drone/cache"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote" "github.com/drone/drone/remote"
"github.com/drone/drone/router/middleware/session" "github.com/drone/drone/router/middleware/session"
@ -20,54 +20,40 @@ import (
func PostRepo(c *gin.Context) { func PostRepo(c *gin.Context) {
remote := remote.FromContext(c) remote := remote.FromContext(c)
user := session.User(c) user := session.User(c)
owner := c.Param("owner") repo := session.Repo(c)
name := c.Param("name")
if user == nil { if repo.IsActive {
c.AbortWithStatus(403) c.String(409, "Repository is already active.")
return return
} }
r, err := remote.Repo(user, owner, name) repo.IsActive = true
if err != nil { repo.UserID = user.ID
c.String(404, err.Error()) if !repo.AllowPush && !repo.AllowPull && !repo.AllowDeploy && !repo.AllowTag {
return repo.AllowPush = true
repo.AllowPull = true
} }
m, err := cache.GetPerms(c, user, owner, name) if repo.Visibility == "" {
if err != nil { repo.Visibility = model.VisibilityPublic
c.String(404, err.Error()) if repo.IsPrivate {
return repo.Visibility = model.VisibilityPrivate
}
} }
if !m.Admin { if repo.Config == "" {
c.String(403, "Administrative access is required.") repo.Config = ".drone.yml"
return }
if repo.Timeout == 0 {
repo.Timeout = 60 // 1 hour default build time
}
if repo.Hash == "" {
repo.Hash = base32.StdEncoding.EncodeToString(
securecookie.GenerateRandomKey(32),
)
} }
// error if the repository already exists // creates the jwt token used to verify the repository
_, err = store.GetRepoOwnerName(c, owner, name) t := token.New(token.HookToken, repo.FullName)
if err == nil { sig, err := t.Sign(repo.Hash)
c.String(409, "Repository already exists.")
return
}
// set the repository owner to the
// currently authenticated user.
r.UserID = user.ID
r.AllowPush = true
r.AllowPull = true
r.Visibility = model.VisibilityPublic
r.Config = ".drone.yml"
r.Timeout = 60 // 1 hour default build time
r.Hash = base32.StdEncoding.EncodeToString(
securecookie.GenerateRandomKey(32),
)
if r.IsPrivate {
r.Visibility = model.VisibilityPrivate
}
// crates the jwt token used to verify the repository
t := token.New(token.HookToken, r.FullName)
sig, err := t.Sign(r.Hash)
if err != nil { if err != nil {
c.String(500, err.Error()) c.String(500, err.Error())
return return
@ -79,22 +65,19 @@ func PostRepo(c *gin.Context) {
sig, sig,
) )
// activate the repository before we make any err = remote.Activate(user, repo, link)
// local changes to the database.
err = remote.Activate(user, r, link)
if err != nil { if err != nil {
c.String(500, err.Error()) c.String(500, err.Error())
return return
} }
// persist the repository err = store.UpdateRepo(c, repo)
err = store.CreateRepo(c, r)
if err != nil { if err != nil {
c.String(500, err.Error()) c.String(500, err.Error())
return return
} }
c.JSON(200, r) c.JSON(200, repo)
} }
func PatchRepo(c *gin.Context) { func PatchRepo(c *gin.Context) {
@ -173,16 +156,28 @@ func GetRepo(c *gin.Context) {
} }
func DeleteRepo(c *gin.Context) { func DeleteRepo(c *gin.Context) {
remove, _ := strconv.ParseBool(c.Query("remove"))
remote := remote.FromContext(c) remote := remote.FromContext(c)
repo := session.Repo(c) repo := session.Repo(c)
user := session.User(c) user := session.User(c)
err := store.DeleteRepo(c, repo) repo.IsActive = false
repo.UserID = 0
err := store.UpdateRepo(c, repo)
if err != nil { if err != nil {
c.AbortWithError(http.StatusInternalServerError, err) c.AbortWithError(http.StatusInternalServerError, err)
return return
} }
if remove {
err := store.DeleteRepo(c, repo)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
return
}
}
remote.Deactivate(user, repo, httputil.GetURL(c.Request)) remote.Deactivate(user, repo, httputil.GetURL(c.Request))
c.Writer.WriteHeader(http.StatusOK) c.Writer.WriteHeader(http.StatusOK)
} }

View file

@ -9,7 +9,6 @@ import (
"github.com/cncd/logging" "github.com/cncd/logging"
"github.com/cncd/pubsub" "github.com/cncd/pubsub"
"github.com/drone/drone/cache"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/router/middleware/session" "github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/store" "github.com/drone/drone/store"
@ -149,7 +148,10 @@ func EventStream(c *gin.Context) {
user := session.User(c) user := session.User(c)
repo := map[string]bool{} repo := map[string]bool{}
if user != nil { if user != nil {
repo, _ = cache.GetRepoMap(c, user) repos, _ := store.FromContext(c).RepoList(user)
for _, r := range repos {
repo[r.FullName] = true
}
} }
ticker := time.NewTicker(pingPeriod) ticker := time.NewTicker(pingPeriod)

55
server/sync.go Normal file
View file

@ -0,0 +1,55 @@
package server
import (
"time"
"github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/store"
)
// Syncer synces the user repository and permissions.
type Syncer interface {
Sync(user *model.User) error
}
type syncer struct {
remote remote.Remote
store store.Store
perms model.PermStore
}
func (s *syncer) Sync(user *model.User) error {
unix := time.Now().Unix()
repos, err := s.remote.Repos(user)
if err != nil {
return err
}
var perms []*model.Perm
for _, repo := range repos {
perm := model.Perm{
UserID: user.ID,
Repo: repo.FullName,
Pull: true,
Synced: unix,
}
if repo.Perm != nil {
perm.Push = repo.Perm.Push
perm.Admin = repo.Perm.Admin
}
perms = append(perms, &perm)
}
err = s.store.RepoBatch(repos)
if err != nil {
return err
}
err = s.store.PermBatch(perms)
if err != nil {
return err
}
return nil
}

View file

@ -4,13 +4,14 @@ import (
"encoding/base32" "encoding/base32"
"net/http" "net/http"
"strconv" "strconv"
"time"
log "github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
"github.com/drone/drone/cache"
"github.com/drone/drone/model" "github.com/drone/drone/model"
"github.com/drone/drone/remote"
"github.com/drone/drone/router/middleware/session" "github.com/drone/drone/router/middleware/session"
"github.com/drone/drone/shared/token" "github.com/drone/drone/shared/token"
"github.com/drone/drone/store" "github.com/drone/drone/store"
@ -21,17 +22,38 @@ func GetSelf(c *gin.Context) {
} }
func GetFeed(c *gin.Context) { func GetFeed(c *gin.Context) {
user := session.User(c)
latest, _ := strconv.ParseBool(c.Query("latest")) latest, _ := strconv.ParseBool(c.Query("latest"))
repos, err := cache.GetRepos(c, session.User(c)) if time.Unix(user.Synced, 0).Add(time.Hour * 72).Before(time.Now()) {
if err != nil { logrus.Debugf("sync begin: %s", user.Login)
c.String(500, "Error fetching repository list. %s", err) sync := syncer{
remote: remote.FromContext(c),
store: store.FromContext(c),
perms: store.FromContext(c),
}
if err := sync.Sync(user); err != nil {
logrus.Debugf("sync error: %s: %s", user.Login, err)
} else {
logrus.Debugf("sync complete: %s", user.Login)
user.Synced = time.Now().Unix()
store.FromContext(c).UpdateUser(user)
}
}
if latest {
feed, err := store.FromContext(c).RepoListLatest(user)
if err != nil {
c.String(500, "Error fetching feed. %s", err)
} else {
c.JSON(200, feed)
}
return return
} }
feed, err := store.GetUserFeed(c, repos, latest) feed, err := store.FromContext(c).UserFeed(user)
if err != nil { if err != nil {
c.String(500, "Error fetching feed. %s", err) c.String(500, "Error fetching user feed. %s", err)
return return
} }
c.JSON(200, feed) c.JSON(200, feed)
@ -44,58 +66,40 @@ func GetRepos(c *gin.Context) {
flush, _ = strconv.ParseBool(c.Query("flush")) flush, _ = strconv.ParseBool(c.Query("flush"))
) )
if flush { if flush || time.Unix(user.Synced, 0).Add(time.Hour*72).Before(time.Now()) {
log.Debugf("Evicting repository cache for user %s.", user.Login) logrus.Debugf("sync begin: %s", user.Login)
cache.DeleteRepos(c, user) sync := syncer{
remote: remote.FromContext(c),
store: store.FromContext(c),
perms: store.FromContext(c),
}
if err := sync.Sync(user); err != nil {
logrus.Debugf("sync error: %s: %s", user.Login, err)
} else {
logrus.Debugf("sync complete: %s", user.Login)
user.Synced = time.Now().Unix()
store.FromContext(c).UpdateUser(user)
}
} }
remote, err := cache.GetRepos(c, user) repos, err := store.FromContext(c).RepoList(user)
if err != nil { if err != nil {
c.String(500, "Error fetching repository list. %s", err) c.String(500, "Error fetching repository list. %s", err)
return return
} }
repos, err := store.GetRepoListOf(c, remote) if all {
if err != nil {
c.String(500, "Error fetching repository list. %s", err)
return
}
if !all {
c.JSON(http.StatusOK, repos) c.JSON(http.StatusOK, repos)
return return
} }
// below we combine the two lists to include both active and inactive active := []*model.Repo{}
// repositories. This is displayed on the settings screen to enable
// toggling on / off repository settings.
repom := map[string]bool{}
for _, repo := range repos { for _, repo := range repos {
repom[repo.FullName] = true if repo.IsActive {
} active = append(active, repo)
for _, repo := range remote {
if repom[repo.FullName] {
continue
} }
repos = append(repos, &model.Repo{
Avatar: repo.Avatar,
FullName: repo.FullName,
Owner: repo.Owner,
Name: repo.Name,
})
} }
c.JSON(http.StatusOK, repos) c.JSON(http.StatusOK, active)
}
func GetRemoteRepos(c *gin.Context) {
repos, err := cache.GetRepos(c, session.User(c))
if err != nil {
c.String(500, "Error fetching repository list. %s", err)
return
}
c.JSON(http.StatusOK, repos)
} }
func PostToken(c *gin.Context) { func PostToken(c *gin.Context) {

View file

@ -112,6 +112,34 @@ var migrations = []struct {
name: "update-table-set-repo-seq-default", name: "update-table-set-repo-seq-default",
stmt: updateTableSetRepoSeqDefault, stmt: updateTableSetRepoSeqDefault,
}, },
{
name: "alter-table-add-repo-active",
stmt: alterTableAddRepoActive,
},
{
name: "update-table-set-repo-active",
stmt: updateTableSetRepoActive,
},
{
name: "alter-table-add-user-synced",
stmt: alterTableAddUserSynced,
},
{
name: "update-table-set-user-synced",
stmt: updateTableSetUserSynced,
},
{
name: "create-table-perms",
stmt: createTablePerms,
},
{
name: "create-index-perms-repo",
stmt: createIndexPermsRepo,
},
{
name: "create-index-perms-user",
stmt: createIndexPermsUser,
},
} }
// Migrate performs the database migration. If the migration fails // Migrate performs the database migration. If the migration fails
@ -500,3 +528,51 @@ var updateTableSetRepoSeqDefault = `
UPDATE repos SET repo_counter = 0 UPDATE repos SET repo_counter = 0
WHERE repo_counter IS NULL WHERE repo_counter IS NULL
` `
//
// 015_add_column_repo_active.sql
//
var alterTableAddRepoActive = `
ALTER TABLE repos ADD COLUMN repo_active BOOLEAN
`
var updateTableSetRepoActive = `
UPDATE repos SET repo_active = 1
`
//
// 016_add_column_user_synced.sql
//
var alterTableAddUserSynced = `
ALTER TABLE users ADD COLUMN user_synced INTEGER;
`
var updateTableSetUserSynced = `
UPDATE users SET user_synced = 0
`
//
// 017_create_table_perms.sql
//
var createTablePerms = `
CREATE TABLE IF NOT EXISTS perms (
perm_user_id INTEGER NOT NULL
,perm_repo_id INTEGER NOT NULL
,perm_pull BOOLEAN
,perm_push BOOLEAN
,perm_admin BOOLEAN
,perm_synced INTEGER
,UNIQUE(perm_user_id, perm_repo_id)
);
`
var createIndexPermsRepo = `
CREATE INDEX IF NOT EXISTS ix_perms_repo ON perms (perm_repo_id);
`
var createIndexPermsUser = `
CREATE INDEX IF NOT EXISTS ix_perms_user ON perms (perm_user_id);
`

View file

@ -0,0 +1,7 @@
-- name: alter-table-add-repo-active
ALTER TABLE repos ADD COLUMN repo_active BOOLEAN
-- name: update-table-set-repo-active
UPDATE repos SET repo_active = 1

View file

@ -0,0 +1,7 @@
-- name: alter-table-add-user-synced
ALTER TABLE users ADD COLUMN user_synced INTEGER;
-- name: update-table-set-user-synced
UPDATE users SET user_synced = 0

View file

@ -0,0 +1,19 @@
-- name: create-table-perms
CREATE TABLE IF NOT EXISTS perms (
perm_user_id INTEGER NOT NULL
,perm_repo_id INTEGER NOT NULL
,perm_pull BOOLEAN
,perm_push BOOLEAN
,perm_admin BOOLEAN
,perm_synced INTEGER
,UNIQUE(perm_user_id, perm_repo_id)
);
-- name: create-index-perms-repo
CREATE INDEX IF NOT EXISTS ix_perms_repo ON perms (perm_repo_id);
-- name: create-index-perms-user
CREATE INDEX IF NOT EXISTS ix_perms_user ON perms (perm_user_id);

View file

@ -1,242 +0,0 @@
-- name: create-table-users
CREATE TABLE users (
user_id INTEGER PRIMARY KEY AUTOINCREMENT
,user_login TEXT
,user_token TEXT
,user_secret TEXT
,user_expiry INTEGER
,user_email TEXT
,user_avatar TEXT
,user_active BOOLEAN
,user_admin BOOLEAN
,user_hash TEXT
,UNIQUE(user_login)
);
--------------------------------------------------------------------------------
-- name: create-table-repos
CREATE TABLE repos (
repo_id INTEGER PRIMARY KEY AUTOINCREMENT
,repo_user_id INTEGER
,repo_owner TEXT
,repo_name TEXT
,repo_full_name TEXT
,repo_avatar TEXT
,repo_link TEXT
,repo_clone TEXT
,repo_branch TEXT
,repo_timeout INTEGER
,repo_private BOOLEAN
,repo_trusted BOOLEAN
,repo_allow_pr BOOLEAN
,repo_allow_push BOOLEAN
,repo_allow_deploys BOOLEAN
,repo_allow_tags BOOLEAN
,repo_hash TEXT
,repo_scm TEXT
,repo_config_path TEXT
,repo_gated BOOLEAN
,UNIQUE(repo_full_name)
);
--------------------------------------------------------------------------------
-- name: create-table-secrets
CREATE TABLE secrets (
secret_id INTEGER PRIMARY KEY AUTOINCREMENT
,secret_repo_id INTEGER
,secret_name TEXT
,secret_value TEXT
,secret_images TEXT
,secret_events TEXT
,secret_skip_verify BOOLEAN
,secret_conceal BOOLEAN
,UNIQUE(secret_name, secret_repo_id)
);
-- name: create-index-secrets-repo
CREATE INDEX ix_secrets_repo ON secrets (secret_repo_id);
--------------------------------------------------------------------------------
-- name: create-table-registry
CREATE TABLE registry (
registry_id INTEGER PRIMARY KEY AUTOINCREMENT
,registry_repo_id INTEGER
,registry_addr TEXT
,registry_username TEXT
,registry_password TEXT
,registry_email TEXT
,registry_token TEXT
,UNIQUE(registry_addr, registry_repo_id)
);
-- name: create-index-registry-repo
CREATE INDEX ix_registry_repo ON registry (registry_repo_id);
--------------------------------------------------------------------------------
-- name: create-table-builds
CREATE TABLE builds (
build_id INTEGER PRIMARY KEY AUTOINCREMENT
,build_repo_id INTEGER
,build_number INTEGER
,build_event TEXT
,build_status TEXT
,build_enqueued INTEGER
,build_created INTEGER
,build_started INTEGER
,build_finished INTEGER
,build_commit TEXT
,build_branch TEXT
,build_ref TEXT
,build_refspec TEXT
,build_remote TEXT
,build_title TEXT
,build_message TEXT
,build_timestamp INTEGER
,build_author TEXT
,build_avatar TEXT
,build_email TEXT
,build_link TEXT
,build_deploy TEXT
,build_signed BOOLEAN
,build_verified BOOLEAN
,build_parent INTEGER
,build_error TEXT
,build_reviewer TEXT
,build_reviewed INTEGER
,build_sender TEXT
,build_config_id INTEGER
,UNIQUE(build_number, build_repo_id)
);
-- name: create-index-builds-repo
CREATE INDEX ix_build_repo ON builds (build_repo_id);
-- name: create-index-builds-author
CREATE INDEX ix_build_author ON builds (build_author);
-- name: create-index-builds-status
CREATE INDEX ix_build_status_running ON builds (build_status)
WHERE build_status IN ('pending', 'running');
--------------------------------------------------------------------------------
-- name: create-table-procs
CREATE TABLE procs (
proc_id INTEGER PRIMARY KEY AUTOINCREMENT
,proc_build_id INTEGER
,proc_pid INTEGER
,proc_ppid INTEGER
,proc_pgid INTEGER
,proc_name TEXT
,proc_state TEXT
,proc_error TEXT
,proc_exit_code INTEGER
,proc_started INTEGER
,proc_stopped INTEGER
,proc_machine TEXT
,proc_platform TEXT
,proc_environ TEXT
,UNIQUE(proc_build_id, proc_pid)
);
-- name: create-index-procs-build
CREATE INDEX proc_build_ix ON procs (proc_build_id);
--------------------------------------------------------------------------------
-- name: create-table-logs
CREATE TABLE IF NOT EXISTS logs (
log_id INTEGER PRIMARY KEY AUTOINCREMENT
,log_job_id INTEGER
,log_data BLOB
,UNIQUE(log_job_id)
);
--------------------------------------------------------------------------------
-- name: create-table-files
CREATE TABLE IF NOT EXISTS files (
file_id INTEGER PRIMARY KEY AUTOINCREMENT
,file_build_id INTEGER
,file_proc_id INTEGER
,file_name TEXT
,file_mime TEXT
,file_size INTEGER
,file_time INTEGER
,file_data BLOB
,UNIQUE(file_proc_id,file_name)
,FOREIGN KEY(file_proc_id) REFERENCES procs (proc_id) ON DELETE CASCADE
);
-- name: create-index-files-builds
CREATE INDEX file_build_ix ON files (file_build_id);
-- name: create-index-files-procs
CREATE INDEX file_proc_ix ON files (file_proc_id);
--------------------------------------------------------------------------------
-- name: create-table-senders
CREATE TABLE IF NOT EXISTS senders (
sender_id INTEGER PRIMARY KEY AUTOINCREMENT
,sender_repo_id INTEGER
,sender_login BOOLEAN
,sender_allow BOOLEAN
,sender_block BOOLEAN
,UNIQUE(sender_repo_id,sender_login)
);
-- name: create-index-sender-repos
CREATE INDEX sender_repo_ix ON senders (sender_repo_id);
--------------------------------------------------------------------------------
-- name: create-table-config
CREATE TABLE IF NOT EXISTS config (
config_id INTEGER PRIMARY KEY AUTOINCREMENT
,config_repo_id INTEGER
,config_hash TEXT
,config_data BLOB
,UNIQUE(config_hash, config_repo_id)
);
--------------------------------------------------------------------------------
-- name: create-table-tasks
CREATE TABLE IF NOT EXISTS tasks (
task_id TEXT PRIMARY KEY
,task_data BLOB
,task_labels BLOB
);
--------------------------------------------------------------------------------
-- name: create-table-agents
CREATE TABLE IF NOT EXISTS agents (
agent_id INTEGER PRIMARY KEY AUTOINCREMENT
,agent_addr TEXT
,agent_platform TEXT
,agent_capacity INTEGER
,agent_created INTEGER
,agent_updated INTEGER
,UNIQUE(agent_addr)
);

44
store/datastore/perms.go Normal file
View file

@ -0,0 +1,44 @@
package datastore
import (
"github.com/drone/drone/model"
"github.com/drone/drone/store/datastore/sql"
"github.com/russross/meddler"
)
func (db *datastore) PermFind(user *model.User, repo *model.Repo) (*model.Perm, error) {
stmt := sql.Lookup(db.driver, "perms-find-user-repo")
data := new(model.Perm)
err := meddler.QueryRow(db, data, stmt, user.ID, repo.ID)
return data, err
}
func (db *datastore) PermUpsert(perm *model.Perm) error {
stmt := sql.Lookup(db.driver, "perms-insert-replace-lookup")
_, err := db.Exec(stmt,
perm.UserID,
perm.Repo,
perm.Pull,
perm.Push,
perm.Admin,
perm.Synced,
)
return err
}
func (db *datastore) PermBatch(perms []*model.Perm) (err error) {
for _, perm := range perms {
err = db.PermUpsert(perm)
if err != nil {
return err
}
}
return nil
}
func (db *datastore) PermDelete(perm *model.Perm) error {
stmt := sql.Lookup(db.driver, "perms-delete-user-repo")
_, err := db.Exec(stmt, perm.UserID, perm.RepoID)
return err
}

View file

@ -0,0 +1,138 @@
package datastore
import (
"testing"
"github.com/drone/drone/model"
)
func TestPermFind(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from perms")
s.Exec("delete from repos")
s.Close()
}()
user := &model.User{ID: 1}
repo := &model.Repo{
UserID: 1,
FullName: "bradrydzewski/drone",
Owner: "bradrydzewski",
Name: "drone",
}
s.CreateRepo(repo)
err := s.PermUpsert(
&model.Perm{
UserID: user.ID,
RepoID: repo.ID,
Repo: repo.FullName,
Pull: true,
Push: false,
Admin: false,
},
)
if err != nil {
t.Error(err)
return
}
perm, err := s.PermFind(user, repo)
if err != nil {
t.Error(err)
return
}
if got, want := perm.Pull, true; got != want {
t.Errorf("Wanted pull %v, got %v", want, got)
}
if got, want := perm.Push, false; got != want {
t.Errorf("Wanted push %v, got %v", want, got)
}
if got, want := perm.Admin, false; got != want {
t.Errorf("Wanted admin %v, got %v", want, got)
}
}
func TestPermUpsert(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from perms")
s.Exec("delete from repos")
s.Close()
}()
user := &model.User{ID: 1}
repo := &model.Repo{
UserID: 1,
FullName: "bradrydzewski/drone",
Owner: "bradrydzewski",
Name: "drone",
}
s.CreateRepo(repo)
err := s.PermUpsert(
&model.Perm{
UserID: user.ID,
RepoID: repo.ID,
Repo: repo.FullName,
Pull: true,
Push: false,
Admin: false,
},
)
if err != nil {
t.Error(err)
return
}
perm, err := s.PermFind(user, repo)
if err != nil {
t.Error(err)
return
}
if got, want := perm.Pull, true; got != want {
t.Errorf("Wanted pull %v, got %v", want, got)
}
if got, want := perm.Push, false; got != want {
t.Errorf("Wanted push %v, got %v", want, got)
}
if got, want := perm.Admin, false; got != want {
t.Errorf("Wanted admin %v, got %v", want, got)
}
//
// this will attempt to replace the existing permissions
// using the insert or replace logic.
//
err = s.PermUpsert(
&model.Perm{
UserID: user.ID,
RepoID: repo.ID,
Repo: repo.FullName,
Pull: true,
Push: true,
Admin: true,
},
)
if err != nil {
t.Error(err)
return
}
perm, err = s.PermFind(user, repo)
if err != nil {
t.Error(err)
return
}
if got, want := perm.Pull, true; got != want {
t.Errorf("Wanted pull %v, got %v", want, got)
}
if got, want := perm.Push, true; got != want {
t.Errorf("Wanted push %v, got %v", want, got)
}
if got, want := perm.Admin, true; got != want {
t.Errorf("Wanted admin %v, got %v", want, got)
}
}

View file

@ -59,6 +59,58 @@ func (db *datastore) DeleteRepo(repo *model.Repo) error {
return err return err
} }
func (db *datastore) RepoList(user *model.User) ([]*model.Repo, error) {
stmt := sql.Lookup(db.driver, "repo-find-user")
data := []*model.Repo{}
err := meddler.QueryAll(db, &data, stmt, user.ID)
return data, err
}
func (db *datastore) RepoListLatest(user *model.User) ([]*model.Feed, error) {
stmt := sql.Lookup(db.driver, "feed-latest-build")
data := []*model.Feed{}
err := meddler.QueryAll(db, &data, stmt, user.ID)
return data, err
}
func (db *datastore) RepoBatch(repos []*model.Repo) error {
stmt := sql.Lookup(db.driver, "repo-insert-ignore")
for _, repo := range repos {
_, err := db.Exec(stmt,
repo.UserID,
repo.Owner,
repo.Name,
repo.FullName,
repo.Avatar,
repo.Link,
repo.Clone,
repo.Branch,
repo.Timeout,
repo.IsPrivate,
repo.IsTrusted,
repo.IsActive,
repo.AllowPull,
repo.AllowPush,
repo.AllowDeploy,
repo.AllowTag,
repo.Hash,
repo.Kind,
repo.Config,
repo.IsGated,
repo.Visibility,
repo.Counter,
)
if err != nil {
return err
}
// last, _ := res.LastInsertId()
// if last != 0 {
// repo.ID = last
// }
}
return nil
}
const repoTable = "repos" const repoTable = "repos"
const repoNameQuery = ` const repoNameQuery = `

View file

@ -174,3 +174,55 @@ func TestRepos(t *testing.T) {
}) })
}) })
} }
func TestRepoBatch(t *testing.T) {
s := newTest()
defer func() {
s.Exec("delete from repos")
s.Close()
}()
repo := &model.Repo{
UserID: 1,
FullName: "foo/bar",
Owner: "foo",
Name: "bar",
}
err := s.CreateRepo(repo)
if err != nil {
t.Error(err)
return
}
err = s.RepoBatch(
[]*model.Repo{
{
UserID: 1,
FullName: "foo/bar",
Owner: "foo",
Name: "bar",
},
{
UserID: 1,
FullName: "bar/baz",
Owner: "bar",
Name: "baz",
},
{
UserID: 1,
FullName: "baz/qux",
Owner: "baz",
Name: "qux",
},
},
)
if err != nil {
t.Error(err)
return
}
count, _ := s.GetRepoCount()
if got, want := count, 3; got != want {
t.Errorf("Want %d repositories, got %d", want, got)
}
}

View file

@ -0,0 +1,61 @@
-- name: feed-latest-build
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos LEFT OUTER JOIN builds ON build_id = (
SELECT build_id FROM builds
WHERE builds.build_repo_id = repos.repo_id
ORDER BY build_id DESC
LIMIT 1
)
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
AND repos.repo_active = 1
ORDER BY repo_full_name ASC;
-- name: feed
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
INNER JOIN builds ON builds.build_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
ORDER BY build_id DESC
LIMIT 50

View file

@ -0,0 +1,58 @@
-- name: perms-find-user
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_date
FROM perms
WHERE perm_user_id = ?
-- name: perms-find-user-repo
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
FROM perms
WHERE perm_user_id = ?
AND perm_repo_id = ?
-- name: perms-insert-replace
INSERT OR REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES (?,?,?,?,?,?)
-- name: perms-insert-replace-lookup
INSERT OR REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES (?,(SELECT repo_id FROM repos WHERE repo_full_name = ?),?,?,?,?)
-- name: perms-delete-user-repo
DELETE FROM perms
WHERE perm_user_id = ?
AND perm_repo_id = ?
-- name: perms-delete-user-date
DELETE FROM perms
WHERE perm_user_id = ?
AND perm_synced < ?

View file

@ -3,3 +3,61 @@
UPDATE repos SET repo_counter = ? UPDATE repos SET repo_counter = ?
WHERE repo_counter = ? WHERE repo_counter = ?
AND repo_id = ? AND repo_id = ?
-- name: repo-find-user
SELECT
repo_id
,repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
ORDER BY repo_full_name ASC
-- name: repo-insert-ignore
INSERT OR IGNORE INTO repos (
repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)

View file

@ -6,35 +6,45 @@ func Lookup(name string) string {
} }
var index = map[string]string{ var index = map[string]string{
"config-find-id": configFindId, "config-find-id": configFindId,
"config-find-repo-hash": configFindRepoHash, "config-find-repo-hash": configFindRepoHash,
"config-find-approved": configFindApproved, "config-find-approved": configFindApproved,
"count-users": countUsers, "count-users": countUsers,
"count-repos": countRepos, "count-repos": countRepos,
"count-builds": countBuilds, "count-builds": countBuilds,
"files-find-build": filesFindBuild, "feed-latest-build": feedLatestBuild,
"files-find-proc-name": filesFindProcName, "feed": feed,
"files-find-proc-name-data": filesFindProcNameData, "files-find-build": filesFindBuild,
"files-delete-build": filesDeleteBuild, "files-find-proc-name": filesFindProcName,
"procs-find-id": procsFindId, "files-find-proc-name-data": filesFindProcNameData,
"procs-find-build": procsFindBuild, "files-delete-build": filesDeleteBuild,
"procs-find-build-pid": procsFindBuildPid, "perms-find-user": permsFindUser,
"procs-find-build-ppid": procsFindBuildPpid, "perms-find-user-repo": permsFindUserRepo,
"procs-delete-build": procsDeleteBuild, "perms-insert-replace": permsInsertReplace,
"registry-find-repo": registryFindRepo, "perms-insert-replace-lookup": permsInsertReplaceLookup,
"registry-find-repo-addr": registryFindRepoAddr, "perms-delete-user-repo": permsDeleteUserRepo,
"registry-delete-repo": registryDeleteRepo, "perms-delete-user-date": permsDeleteUserDate,
"registry-delete": registryDelete, "procs-find-id": procsFindId,
"repo-update-counter": repoUpdateCounter, "procs-find-build": procsFindBuild,
"secret-find-repo": secretFindRepo, "procs-find-build-pid": procsFindBuildPid,
"secret-find-repo-name": secretFindRepoName, "procs-find-build-ppid": procsFindBuildPpid,
"secret-delete": secretDelete, "procs-delete-build": procsDeleteBuild,
"sender-find-repo": senderFindRepo, "registry-find-repo": registryFindRepo,
"sender-find-repo-login": senderFindRepoLogin, "registry-find-repo-addr": registryFindRepoAddr,
"sender-delete-repo": senderDeleteRepo, "registry-delete-repo": registryDeleteRepo,
"sender-delete": senderDelete, "registry-delete": registryDelete,
"task-list": taskList, "repo-update-counter": repoUpdateCounter,
"task-delete": taskDelete, "repo-find-user": repoFindUser,
"repo-insert-ignore": repoInsertIgnore,
"secret-find-repo": secretFindRepo,
"secret-find-repo-name": secretFindRepoName,
"secret-delete": secretDelete,
"sender-find-repo": senderFindRepo,
"sender-find-repo-login": senderFindRepoLogin,
"sender-delete-repo": senderDeleteRepo,
"sender-delete": senderDelete,
"task-list": taskList,
"task-delete": taskDelete,
} }
var configFindId = ` var configFindId = `
@ -81,6 +91,68 @@ SELECT count(1)
FROM builds FROM builds
` `
var feedLatestBuild = `
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos LEFT OUTER JOIN builds ON build_id = (
SELECT build_id FROM builds
WHERE builds.build_repo_id = repos.repo_id
ORDER BY build_id DESC
LIMIT 1
)
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
AND repos.repo_active = 1
ORDER BY repo_full_name ASC;
`
var feed = `
SELECT
repo_owner
,repo_name
,repo_full_name
,build_number
,build_event
,build_status
,build_created
,build_started
,build_finished
,build_commit
,build_branch
,build_ref
,build_refspec
,build_remote
,build_title
,build_message
,build_author
,build_email
,build_avatar
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
INNER JOIN builds ON builds.build_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
ORDER BY build_id DESC
LIMIT 50
`
var filesFindBuild = ` var filesFindBuild = `
SELECT SELECT
file_id file_id
@ -127,6 +199,65 @@ var filesDeleteBuild = `
DELETE FROM files WHERE file_build_id = ? DELETE FROM files WHERE file_build_id = ?
` `
var permsFindUser = `
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_date
FROM perms
WHERE perm_user_id = ?
`
var permsFindUserRepo = `
SELECT
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
FROM perms
WHERE perm_user_id = ?
AND perm_repo_id = ?
`
var permsInsertReplace = `
INSERT OR REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES (?,?,?,?,?,?)
`
var permsInsertReplaceLookup = `
INSERT OR REPLACE INTO perms (
perm_user_id
,perm_repo_id
,perm_pull
,perm_push
,perm_admin
,perm_synced
) VALUES (?,(SELECT repo_id FROM repos WHERE repo_full_name = ?),?,?,?,?)
`
var permsDeleteUserRepo = `
DELETE FROM perms
WHERE perm_user_id = ?
AND perm_repo_id = ?
`
var permsDeleteUserDate = `
DELETE FROM perms
WHERE perm_user_id = ?
AND perm_synced < ?
`
var procsFindId = ` var procsFindId = `
SELECT SELECT
proc_id proc_id
@ -256,6 +387,64 @@ WHERE repo_counter = ?
AND repo_id = ? AND repo_id = ?
` `
var repoFindUser = `
SELECT
repo_id
,repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
FROM repos
INNER JOIN perms ON perms.perm_repo_id = repos.repo_id
WHERE perms.perm_user_id = ?
ORDER BY repo_full_name ASC
`
var repoInsertIgnore = `
INSERT OR IGNORE INTO repos (
repo_user_id
,repo_owner
,repo_name
,repo_full_name
,repo_avatar
,repo_link
,repo_clone
,repo_branch
,repo_timeout
,repo_private
,repo_trusted
,repo_active
,repo_allow_pr
,repo_allow_push
,repo_allow_deploys
,repo_allow_tags
,repo_hash
,repo_scm
,repo_config_path
,repo_gated
,repo_visibility
,repo_counter
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`
var secretFindRepo = ` var secretFindRepo = `
SELECT SELECT
secret_id secret_id

View file

@ -86,6 +86,13 @@ func (db *datastore) DeleteUser(user *model.User) error {
return err return err
} }
func (db *datastore) UserFeed(user *model.User) ([]*model.Feed, error) {
stmt := sql.Lookup(db.driver, "feed")
data := []*model.Feed{}
err := meddler.QueryAll(db, &data, stmt, user.ID)
return data, err
}
const userTable = "users" const userTable = "users"
const userLoginQuery = ` const userLoginQuery = `

View file

@ -92,6 +92,17 @@ type Store interface {
// new functions // new functions
// //
UserFeed(*model.User) ([]*model.Feed, error)
RepoList(*model.User) ([]*model.Repo, error)
RepoListLatest(*model.User) ([]*model.Feed, error)
RepoBatch([]*model.Repo) error
PermFind(user *model.User, repo *model.Repo) (*model.Perm, error)
PermUpsert(perm *model.Perm) error
PermBatch(perms []*model.Perm) error
PermDelete(perm *model.Perm) error
ConfigLoad(int64) (*model.Config, error) ConfigLoad(int64) (*model.Config, error)
ConfigFind(*model.Repo, string) (*model.Config, error) ConfigFind(*model.Repo, string) (*model.Config, error)
ConfigFindApproved(*model.Config) (bool, error) ConfigFindApproved(*model.Config) (bool, error)

View file

@ -1,27 +0,0 @@
Copyright (c) 2013, Ian Schenck
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
Neither the name of Ian Schenck nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,37 +0,0 @@
envflag
=======
Golang flags, but bolted onto the environment rather than the command-line.
Read the [godocs](http://godoc.org/github.com/ianschenck/envflag).
Motivation
==========
Some like the distinction that command-line flags control behavior
while environment variables configure. Also
[12-factor](http://12factor.net/) recommends the use of environment
variables for configuration. The interface of the golang flag package
is well designed and easy to use, and allows for other lists
(os.Environ() vs os.Args) to be parsed as flags. It makes sense then
to use the same interface, the same types, and the same parsing
(caveat: there is some ugly string hacking to make environment
variables look like flags) to the same ends.
Differences
===========
Calling `flag.Parse()` will not parse environment flags. Calling
`envflag.Parse()` will not parse command-line flags. There is no good
reason to combine these two when the net savings is a single line in a
`func main()`. Furthermore, doing so would require users to accept a
precedence order of my choosing.
The presence of an environment variable named `h` or `help` will
probably cause problems (print Usage and os.Exit(0)). Work around this
by defining those flags somewhere (and ignoring them).
Before calling `Flagset.Parse` on `EnvironmentFlags`, the environment
variables being passed to `Parse` are trimmed down using
`Lookup`. This behavior is different from `flag.Parse` in that extra
environment variables are ignored (and won't crash `envflag.Parse`).

View file

@ -1,192 +0,0 @@
// Copyright 2013 Ian Schenck. Use of this source code is governed by
// a license that can be found in the LICENSE file.
/*
Package envflag adds environment variable flags to the flag package.
Usage:
Define flags using envflag.String(), Bool(), Int(), etc. This package
works nearly the same as the stdlib flag package. Parsing the
Environment flags is done by calling envflag.Parse()
It will *not* attempt to parse any normally-defined command-line
flags. Command-line flags are explicitly left alone and separate.
*/
package envflag
import (
"flag"
"fmt"
"os"
"strings"
"time"
)
// VisitAll visits the environment flags in lexicographical order,
// calling fn for each. It visits all flags, even those not set.
func VisitAll(fn func(*flag.Flag)) {
EnvironmentFlags.VisitAll(fn)
}
// Visit visits the environment flags in lexicographical order,
// calling fn for each. It visits only those flags that have been
// set.
func Visit(fn func(*flag.Flag)) {
EnvironmentFlags.Visit(fn)
}
// Lookup returns the Flag structure of the named environment flag,
// returning nil if none exists.
func Lookup(name string) *flag.Flag {
return EnvironmentFlags.Lookup(name)
}
// Set sets the value of the named environment flag.
func Set(name, value string) error {
return EnvironmentFlags.Set(name, value)
}
// BoolVar defines a bool flag with specified name, default value, and
// usage string. The argument p points to a bool variable in which to
// store the value of the flag.
func BoolVar(p *bool, name string, value bool, usage string) {
EnvironmentFlags.BoolVar(p, name, value, usage)
}
// Bool defines a bool flag with specified name, default value, and
// usage string. The return value is the address of a bool variable
// that stores the value of the flag.
func Bool(name string, value bool, usage string) *bool {
return EnvironmentFlags.Bool(name, value, usage)
}
// IntVar defines an int flag with specified name, default value, and
// usage string. The argument p points to an int variable in which to
// store the value of the flag.
func IntVar(p *int, name string, value int, usage string) {
EnvironmentFlags.IntVar(p, name, value, usage)
}
// Int defines an int flag with specified name, default value, and
// usage string. The return value is the address of an int variable
// that stores the value of the flag.
func Int(name string, value int, usage string) *int {
return EnvironmentFlags.Int(name, value, usage)
}
// Int64Var defines an int64 flag with specified name, default value,
// and usage string. The argument p points to an int64 variable in
// which to store the value of the flag.
func Int64Var(p *int64, name string, value int64, usage string) {
EnvironmentFlags.Int64Var(p, name, value, usage)
}
// Int64 defines an int64 flag with specified name, default value, and
// usage string. The return value is the address of an int64 variable
// that stores the value of the flag.
func Int64(name string, value int64, usage string) *int64 {
return EnvironmentFlags.Int64(name, value, usage)
}
// UintVar defines a uint flag with specified name, default value, and
// usage string. The argument p points to a uint variable in which to
// store the value of the flag.
func UintVar(p *uint, name string, value uint, usage string) {
EnvironmentFlags.UintVar(p, name, value, usage)
}
// Uint defines a uint flag with specified name, default value, and
// usage string. The return value is the address of a uint variable
// that stores the value of the flag.
func Uint(name string, value uint, usage string) *uint {
return EnvironmentFlags.Uint(name, value, usage)
}
// Uint64Var defines a uint64 flag with specified name, default value,
// and usage string. The argument p points to a uint64 variable in
// which to store the value of the flag.
func Uint64Var(p *uint64, name string, value uint64, usage string) {
EnvironmentFlags.Uint64Var(p, name, value, usage)
}
// Uint64 defines a uint64 flag with specified name, default value,
// and usage string. The return value is the address of a uint64
// variable that stores the value of the flag.
func Uint64(name string, value uint64, usage string) *uint64 {
return EnvironmentFlags.Uint64(name, value, usage)
}
// StringVar defines a string flag with specified name, default value,
// and usage string. The argument p points to a string variable in
// which to store the value of the flag.
func StringVar(p *string, name string, value string, usage string) {
EnvironmentFlags.StringVar(p, name, value, usage)
}
// String defines a string flag with specified name, default value,
// and usage string. The return value is the address of a string
// variable that stores the value of the flag.
func String(name string, value string, usage string) *string {
return EnvironmentFlags.String(name, value, usage)
}
// Float64Var defines a float64 flag with specified name, default
// value, and usage string. The argument p points to a float64
// variable in which to store the value of the flag.
func Float64Var(p *float64, name string, value float64, usage string) {
EnvironmentFlags.Float64Var(p, name, value, usage)
}
// Float64 defines a float64 flag with specified name, default value,
// and usage string. The return value is the address of a float64
// variable that stores the value of the flag.
func Float64(name string, value float64, usage string) *float64 {
return EnvironmentFlags.Float64(name, value, usage)
}
// DurationVar defines a time.Duration flag with specified name,
// default value, and usage string. The argument p points to a
// time.Duration variable in which to store the value of the flag.
func DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
EnvironmentFlags.DurationVar(p, name, value, usage)
}
// Duration defines a time.Duration flag with specified name, default
// value, and usage string. The return value is the address of a
// time.Duration variable that stores the value of the flag.
func Duration(name string, value time.Duration, usage string) *time.Duration {
return EnvironmentFlags.Duration(name, value, usage)
}
// PrintDefaults prints to standard error the default values of all
// defined environment flags.
func PrintDefaults() {
EnvironmentFlags.PrintDefaults()
}
// Parse parses the environment flags from os.Environ. Must be called
// after all flags are defined and before flags are accessed by the
// program.
func Parse() {
env := os.Environ()
// Clean up and "fake" some flag k/v pairs.
args := make([]string, 0, len(env))
for _, value := range env {
if Lookup(value[:strings.Index(value, "=")]) == nil {
continue
}
args = append(args, fmt.Sprintf("-%s", value))
}
EnvironmentFlags.Parse(args)
}
// Parsed returns true if the environment flags have been parsed.
func Parsed() bool {
return EnvironmentFlags.Parsed()
}
// EnvironmentFlags is the default set of environment flags, parsed
// from os.Environ(). The top-level functions such as BoolVar, Arg,
// and on are wrappers for the methods of EnvironmentFlags.
var EnvironmentFlags = flag.NewFlagSet("environment", flag.ExitOnError)

View file

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.

View file

@ -1,42 +0,0 @@
# syscerts
Gather local system certificates in Go via a public `SystemRootsPool` method.
#### What does this do?
Provide a way to gather local system certificates
on different OS platforms.
#### How does it do it?
It uses the `crypto/x509` package and provides a single public method called
`SystemRootsPool()` to return a `*x509.CertPool` object.
#### How do you use it?
```Go
// gather CA certs
certpool := syscerts.SystemRootsPool()
// place them in an HTTP client for trusted SSL/TLS connections
tlsConfig := &tls.Config{RootCAs: certpool}
transport := &http.Transport{TLSClientConfig: tlsConfig}
client := &http.Client{Transport: transport}
// make a request
resp, err := client.Do(req)
```
#### Why even do it?
The `crypto/x509` package already has a `systemRootsPool` method.
The `crypto/x509.systemRootsPool` method is almost the same as
`github.com/jackspirou/syscerts.SystemRootsPool`.
The difference? The `crypto/x509.systemRootsPool` method is private so you
cannot access it. :(
There are plans for the `crypto/x509.systemRootsPool` method to become public
in Go 1.7. When this happens you might no longer need `github.com/jackspirou/syscerts.SystemRootsPool`.
The only reason you may still use this package after the Go 1.7 release might
be for the Mac OSX System Keychain certs which are not included in the
`crypto/x509` package. Relevant lines below:
* https://github.com/jackspirou/syscerts/blob/master/root_darwin.go#L24-L32
Find more about this Go issue here: https://github.com/golang/go/issues/13335

View file

@ -1,22 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syscerts
import (
"crypto/x509"
"sync"
)
var (
once sync.Once
systemRoots *x509.CertPool
)
// SystemRootsPool attempts to find and return a pool of all all installed
// system certificates.
func SystemRootsPool() *x509.CertPool {
once.Do(initSystemRoots)
return systemRoots
}

View file

@ -1,14 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build dragonfly freebsd netbsd openbsd
package syscerts
// Possible certificate files; stop after finding one.
var certFiles = []string{
"/usr/local/share/certs/ca-root-nss.crt", // FreeBSD/DragonFly
"/etc/ssl/cert.pem", // OpenBSD
"/etc/openssl/certs/ca-certificates.crt", // NetBSD
}

View file

@ -1,85 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build cgo,darwin,!arm,!arm64,!ios
package syscerts
/*
#cgo CFLAGS: -mmacosx-version-min=10.6 -D__MAC_OS_X_VERSION_MAX_ALLOWED=1060
#cgo LDFLAGS: -framework CoreFoundation -framework Security
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
// FetchPEMRootsC fetches the system's list of trusted X.509 root certificates.
//
// On success it returns 0 and fills pemRoots with a CFDataRef that contains the extracted root
// certificates of the system. On failure, the function returns -1.
//
// Note: The CFDataRef returned in pemRoots must be released (using CFRelease) after
// we've consumed its content.
int FetchPEMRootsC(CFDataRef *pemRoots) {
if (pemRoots == NULL) {
return -1;
}
CFArrayRef certs = NULL;
OSStatus err = SecTrustCopyAnchorCertificates(&certs);
if (err != noErr) {
return -1;
}
CFMutableDataRef combinedData = CFDataCreateMutable(kCFAllocatorDefault, 0);
int i, ncerts = CFArrayGetCount(certs);
for (i = 0; i < ncerts; i++) {
CFDataRef data = NULL;
SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
if (cert == NULL) {
continue;
}
// Note: SecKeychainItemExport is deprecated as of 10.7 in favor of SecItemExport.
// Once we support weak imports via cgo we should prefer that, and fall back to this
// for older systems.
err = SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data);
if (err != noErr) {
continue;
}
if (data != NULL) {
CFDataAppendBytes(combinedData, CFDataGetBytePtr(data), CFDataGetLength(data));
CFRelease(data);
}
}
CFRelease(certs);
*pemRoots = combinedData;
return 0;
}
*/
/*
import "C"
import (
"crypto/x509"
"unsafe"
)
func initSystemRoots() {
roots := x509.NewCertPool()
var data C.CFDataRef = nil
err := C.FetchPEMRootsC(&data)
if err == -1 {
return
}
defer C.CFRelease(C.CFTypeRef(data))
buf := C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(data)), C.int(C.CFDataGetLength(data)))
roots.AppendCertsFromPEM(buf)
systemRoots = roots
}
*/

View file

@ -1,39 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:generate go run root_darwin_arm_gen.go -output root_darwin_armx.go
package syscerts
import (
"crypto/x509"
"os"
"os/exec"
)
func execSecurityRoots() (*x509.CertPool, error) {
roots := x509.NewCertPool()
cmd := exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/System/Library/Keychains/SystemRootCertificates.keychain")
data, err := cmd.Output()
if err != nil {
return nil, err
}
roots.AppendCertsFromPEM(data)
// if available add the Mac OSX System Keychain
if _, err := os.Stat("/Library/Keychains/System.keychain"); err == nil {
cmd = exec.Command("/usr/bin/security", "find-certificate", "-a", "-p", "/Library/Keychains/System.keychain")
data, err = cmd.Output()
if err != nil {
return nil, err
}
roots.AppendCertsFromPEM(data)
}
return roots, nil
}
func initSystemRoots() {
systemRoots, _ = execSecurityRoots()
}

File diff suppressed because it is too large Load diff

View file

@ -1,13 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syscerts
// Possible certificate files; stop after finding one.
var certFiles = []string{
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL
"/etc/ssl/ca-bundle.pem", // OpenSUSE
"/etc/pki/tls/cacert.pem", // OpenELEC
}

View file

@ -1,8 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syscerts
// Possible certificate files; stop after finding one.
var certFiles = []string{}

View file

@ -1,32 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build plan9
package syscerts
import (
"crypto/x509"
"io/ioutil"
)
// Possible certificate files; stop after finding one.
var certFiles = []string{
"/sys/lib/tls/ca.pem",
}
func initSystemRoots() {
roots := x509.NewCertPool()
for _, file := range certFiles {
data, err := ioutil.ReadFile(file)
if err == nil {
roots.AppendCertsFromPEM(data)
systemRoots = roots
return
}
}
// All of the files failed to load. systemRoots will be nil which will
// trigger a specific error at verification time.
}

View file

@ -1,12 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syscerts
// Possible certificate files; stop after finding one.
var certFiles = []string{
"/etc/certs/ca-certificates.crt", // Solaris 11.2+
"/etc/ssl/certs/ca-certificates.crt", // Joyent SmartOS
"/etc/ssl/cacert.pem", // OmniOS
}

View file

@ -1,52 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build dragonfly freebsd linux nacl netbsd openbsd solaris
package syscerts
import (
"crypto/x509"
"io/ioutil"
)
// Possible directories with certificate files; stop after successfully
// reading at least one file from a directory.
var certDirectories = []string{
"/etc/ssl/certs", // SLES10/SLES11, https://golang.org/issue/12139
"/system/etc/security/cacerts", // Android
}
func initSystemRoots() {
roots := x509.NewCertPool()
for _, file := range certFiles {
data, err := ioutil.ReadFile(file)
if err == nil {
roots.AppendCertsFromPEM(data)
systemRoots = roots
return
}
}
for _, directory := range certDirectories {
fis, err := ioutil.ReadDir(directory)
if err != nil {
continue
}
rootsAdded := false
for _, fi := range fis {
data, err := ioutil.ReadFile(directory + "/" + fi.Name())
if err == nil && roots.AppendCertsFromPEM(data) {
rootsAdded = true
}
}
if rootsAdded {
systemRoots = roots
return
}
}
// All of the files failed to load. systemRoots will be nil which will
// trigger a specific error at verification time.
}

View file

@ -1,40 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syscerts
import (
"crypto/x509"
"errors"
"syscall"
"unsafe"
)
// extractSimpleChain extracts the final certificate chain from a CertSimpleChain.
func extractSimpleChain(simpleChain **syscall.CertSimpleChain, count int) (chain []*x509.Certificate, err error) {
if simpleChain == nil || count == 0 {
return nil, errors.New("x509: invalid simple chain")
}
simpleChains := (*[1 << 20]*syscall.CertSimpleChain)(unsafe.Pointer(simpleChain))[:]
lastChain := simpleChains[count-1]
elements := (*[1 << 20]*syscall.CertChainElement)(unsafe.Pointer(lastChain.Elements))[:]
for i := 0; i < int(lastChain.NumElements); i++ {
// Copy the buf, since ParseCertificate does not create its own copy.
cert := elements[i].CertContext
encodedCert := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:]
buf := make([]byte, cert.Length)
copy(buf, encodedCert[:])
parsedCert, err := x509.ParseCertificate(buf)
if err != nil {
return nil, err
}
chain = append(chain, parsedCert)
}
return chain, nil
}
func initSystemRoots() {
}

View file

@ -1,32 +0,0 @@
# Cache [![GoDoc](https://godoc.org/github.com/koding/cache?status.svg)](https://godoc.org/github.com/koding/cache) [![Build Status](https://travis-ci.org/koding/cache.svg?branch=master)](https://travis-ci.org/koding/cache)
Cache is a backend provider for common use cases
## Install and Usage
Install the package with:
```bash
go get github.com/koding/cache
```
Import it with:
```go
import "github.com/koding/cache"
```
Example
```go
// create a cache with 2 second TTL
cache := NewMemoryWithTTL(2 * time.Second)
// start garbage collection for expired keys
cache.StartGC(time.Millisecond * 10)
// set item
err := cache.Set("test_key", "test_data")
// get item
data, err := cache.Get("test_key")
```

View file

@ -1,15 +0,0 @@
package cache
// Cache is the contract for all of the cache backends that are supported by
// this package
type Cache interface {
// Get returns single item from the backend if the requested item is not
// found, returns NotFound err
Get(key string) (interface{}, error)
// Set sets a single item to the backend
Set(key string, value interface{}) error
// Delete deletes single item from backend
Delete(key string) error
}

View file

@ -1,9 +0,0 @@
// Package cache provides basic caching mechanisms for Go(lang) projects.
//
// Currently supported caching algorithms:
// MemoryNoTS: provides a non-thread safe in-memory caching system
// Memory : provides a thread safe in-memory caching system, built on top of MemoryNoTS cache
// LRUNoTS : provides a non-thread safe, fixed size in-memory caching system, built on top of MemoryNoTS cache
// LRU : provides a thread safe, fixed size in-memory caching system, built on top of LRUNoTS cache
// MemoryTTL : provides a thread safe, expiring in-memory caching system, built on top of MemoryNoTS cache
package cache

View file

@ -1,8 +0,0 @@
package cache
import "errors"
var (
// ErrNotFound holds exported `not found error` for not found items
ErrNotFound = errors.New("not found")
)

View file

@ -1,51 +0,0 @@
package cache
import "sync"
// LRU Discards the least recently used items first. This algorithm
// requires keeping track of what was used when.
type LRU struct {
// Mutex is used for handling the concurrent
// read/write requests for cache
sync.Mutex
// cache holds the all cache values
cache Cache
}
// NewLRU creates a thread-safe LRU cache
func NewLRU(size int) Cache {
return &LRU{
cache: NewLRUNoTS(size),
}
}
// Get returns the value of a given key if it exists, every get item will be
// moved to the head of the linked list for keeping track of least recent used
// item
func (l *LRU) Get(key string) (interface{}, error) {
l.Lock()
defer l.Unlock()
return l.cache.Get(key)
}
// Set sets or overrides the given key with the given value, every set item will
// be moved or prepended to the head of the linked list for keeping track of
// least recent used item. When the cache is full, last item of the linked list
// will be evicted from the cache
func (l *LRU) Set(key string, val interface{}) error {
l.Lock()
defer l.Unlock()
return l.cache.Set(key, val)
}
// Delete deletes the given key-value pair from cache, this function doesnt
// return an error if item is not in the cache
func (l *LRU) Delete(key string) error {
l.Lock()
defer l.Unlock()
return l.cache.Delete(key)
}

View file

@ -1,122 +0,0 @@
package cache
import (
"container/list"
)
// LRUNoTS Discards the least recently used items first. This algorithm
// requires keeping track of what was used when.
type LRUNoTS struct {
// list holds all items in a linked list, for finding the `tail` of the list
list *list.List
// cache holds the all cache values
cache Cache
// size holds the limit of the LRU cache
size int
}
// kv is an helper struct for keeping track of the key for the list item. Only
// place where we need the key of a value is while removing the last item from
// linked list, for other cases, all operations alread have the key
type kv struct {
k string
v interface{}
}
// NewLRUNoTS creates a new LRU cache struct for further cache operations. Size
// is used for limiting the upper bound of the cache
func NewLRUNoTS(size int) Cache {
if size < 1 {
panic("invalid cache size")
}
return &LRUNoTS{
list: list.New(),
cache: NewMemoryNoTS(),
size: size,
}
}
// Get returns the value of a given key if it exists, every get item will be
// moved to the head of the linked list for keeping track of least recent used
// item
func (l *LRUNoTS) Get(key string) (interface{}, error) {
res, err := l.cache.Get(key)
if err != nil {
return nil, err
}
elem := res.(*list.Element)
// move found item to the head
l.list.MoveToFront(elem)
return elem.Value.(*kv).v, nil
}
// Set sets or overrides the given key with the given value, every set item will
// be moved or prepended to the head of the linked list for keeping track of
// least recent used item. When the cache is full, last item of the linked list
// will be evicted from the cache
func (l *LRUNoTS) Set(key string, val interface{}) error {
// try to get item
res, err := l.cache.Get(key)
if err != nil && err != ErrNotFound {
return err
}
var elem *list.Element
// if elem is not in the cache, push it to front of the list
if err == ErrNotFound {
elem = l.list.PushFront(&kv{k: key, v: val})
} else {
// if elem is in the cache, update the data and move it the front
elem = res.(*list.Element)
// update the data
elem.Value.(*kv).v = val
// item already exists, so move it to the front of the list
l.list.MoveToFront(elem)
}
// in any case, set the item to the cache
err = l.cache.Set(key, elem)
if err != nil {
return err
}
// if the cache is full, evict last entry
if l.list.Len() > l.size {
// remove last element from cache
return l.removeElem(l.list.Back())
}
return nil
}
// Delete deletes the given key-value pair from cache, this function doesnt
// return an error if item is not in the cache
func (l *LRUNoTS) Delete(key string) error {
res, err := l.cache.Get(key)
if err != nil && err != ErrNotFound {
return err
}
// item already deleted
if err == ErrNotFound {
// surpress not found errors
return nil
}
elem := res.(*list.Element)
return l.removeElem(elem)
}
func (l *LRUNoTS) removeElem(e *list.Element) error {
l.list.Remove(e)
return l.cache.Delete(e.Value.(*kv).k)
}

View file

@ -1,46 +0,0 @@
package cache
import "sync"
// Memory provides an inmemory caching mechanism
type Memory struct {
// Mutex is used for handling the concurrent
// read/write requests for cache
sync.Mutex
// cache holds the cache data
cache Cache
}
// NewMemory creates an inmemory cache system
// Which everytime will return the true value about a cache hit
func NewMemory() Cache {
return &Memory{
cache: NewMemoryNoTS(),
}
}
// Get returns the value of a given key if it exists
func (r *Memory) Get(key string) (interface{}, error) {
r.Lock()
defer r.Unlock()
return r.cache.Get(key)
}
// Set sets a value to the cache or overrides existing one with the given value
func (r *Memory) Set(key string, value interface{}) error {
r.Lock()
defer r.Unlock()
return r.cache.Set(key, value)
}
// Delete deletes the given key-value pair from cache, this function doesnt
// return an error if item is not in the cache
func (r *Memory) Delete(key string) error {
r.Lock()
defer r.Unlock()
return r.cache.Delete(key)
}

View file

@ -1,39 +0,0 @@
package cache
// MemoryNoTS provides a non-thread safe caching mechanism
type MemoryNoTS struct {
// items holds the cache data
items map[string]interface{}
}
// NewMemoryNoTS creates MemoryNoTS struct
func NewMemoryNoTS() *MemoryNoTS {
return &MemoryNoTS{
items: map[string]interface{}{},
}
}
// Get returns a value of a given key if it exists
// and valid for the time being
func (r *MemoryNoTS) Get(key string) (interface{}, error) {
value, ok := r.items[key]
if !ok {
return nil, ErrNotFound
}
return value, nil
}
// Set will persist a value to the cache or
// override existing one with the new one
func (r *MemoryNoTS) Set(key string, value interface{}) error {
r.items[key] = value
return nil
}
// Delete deletes a given key, it doesnt return error if the item is not in the
// system
func (r *MemoryNoTS) Delete(key string) error {
delete(r.items, key)
return nil
}

View file

@ -1,111 +0,0 @@
package cache
import (
"sync"
"time"
)
var zeroTTL = time.Duration(0)
// MemoryTTL holds the required variables to compose an in memory cache system
// which also provides expiring key mechanism
type MemoryTTL struct {
// Mutex is used for handling the concurrent
// read/write requests for cache
sync.Mutex
// cache holds the cache data
cache *MemoryNoTS
// setAts holds the time that related item's set at
setAts map[string]time.Time
// ttl is a duration for a cache key to expire
ttl time.Duration
// gcInterval is a duration for garbage collection
gcInterval time.Duration
}
// NewMemoryWithTTL creates an inmemory cache system
// Which everytime will return the true values about a cache hit
// and never will leak memory
// ttl is used for expiration of a key from cache
func NewMemoryWithTTL(ttl time.Duration) *MemoryTTL {
return &MemoryTTL{
cache: NewMemoryNoTS(),
setAts: map[string]time.Time{},
ttl: ttl,
}
}
// StartGC starts the garbage collection process in a go routine
func (r *MemoryTTL) StartGC(gcInterval time.Duration) {
r.gcInterval = gcInterval
go func() {
for _ = range time.Tick(gcInterval) {
for key := range r.cache.items {
if !r.isValid(key) {
r.Delete(key)
}
}
}
}()
}
// Get returns a value of a given key if it exists
// and valid for the time being
func (r *MemoryTTL) Get(key string) (interface{}, error) {
r.Lock()
defer r.Unlock()
if !r.isValid(key) {
r.delete(key)
return nil, ErrNotFound
}
value, err := r.cache.Get(key)
if err != nil {
return nil, err
}
return value, nil
}
// Set will persist a value to the cache or
// override existing one with the new one
func (r *MemoryTTL) Set(key string, value interface{}) error {
r.Lock()
defer r.Unlock()
r.cache.Set(key, value)
r.setAts[key] = time.Now()
return nil
}
// Delete deletes a given key if exists
func (r *MemoryTTL) Delete(key string) error {
r.Lock()
defer r.Unlock()
r.delete(key)
return nil
}
func (r *MemoryTTL) delete(key string) {
r.cache.Delete(key)
delete(r.setAts, key)
}
func (r *MemoryTTL) isValid(key string) bool {
setAt, ok := r.setAts[key]
if !ok {
return false
}
if r.ttl == zeroTTL {
return true
}
return setAt.Add(r.ttl).After(time.Now())
}

View file

@ -1,10 +0,0 @@
Serious about security
======================
Square recognizes the important contributions the security research community
can make. We therefore encourage reporting security issues with the code
contained in this repository.
If you believe you have discovered a security vulnerability, please follow the
guidelines at <https://hackerone.com/square-open-source>.

View file

@ -1,14 +0,0 @@
# Contributing
If you would like to contribute code to go-jose you can do so through GitHub by
forking the repository and sending a pull request.
When submitting code, please make every effort to follow existing conventions
and style in order to keep the code as readable as possible. Please also make
sure all tests pass by running `go test`, and format your code with `go fmt`.
We also recommend using `golint` and `errcheck`.
Before your code can be accepted into the project you must also sign the
[Individual Contributor License Agreement][1].
[1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1

View file

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View file

@ -1,185 +0,0 @@
# Go JOSE
[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/square/go-jose) [![license](http://img.shields.io/badge/license-apache_2.0-red.svg?style=flat)](https://raw.githubusercontent.com/square/go-jose/master/LICENSE) [![build](https://img.shields.io/travis/square/go-jose.svg?style=flat)](https://travis-ci.org/square/go-jose) [![coverage](https://img.shields.io/coveralls/square/go-jose.svg?style=flat)](https://coveralls.io/r/square/go-jose)
Package jose aims to provide an implementation of the Javascript Object Signing
and Encryption set of standards. For the moment, it mainly focuses on encryption
and signing based on the JSON Web Encryption and JSON Web Signature standards.
**Disclaimer**: This library contains encryption software that is subject to
the U.S. Export Administration Regulations. You may not export, re-export,
transfer or download this code or any part of it in violation of any United
States law, directive or regulation. In particular this software may not be
exported or re-exported in any form or on any media to Iran, North Sudan,
Syria, Cuba, or North Korea, or to denied persons or entities mentioned on any
US maintained blocked list.
## Overview
The implementation follows the
[JSON Web Encryption](http://dx.doi.org/10.17487/RFC7516)
standard (RFC 7516) and
[JSON Web Signature](http://dx.doi.org/10.17487/RFC7515)
standard (RFC 7515). Tables of supported algorithms are shown below.
The library supports both the compact and full serialization formats, and has
optional support for multiple recipients. It also comes with a small
command-line utility (`jose-util`) for encrypting/decrypting JWE messages in a
shell.
### Supported algorithms
See below for a table of supported algorithms. Algorithm identifiers match
the names in the
[JSON Web Algorithms](http://dx.doi.org/10.17487/RFC7518)
standard where possible. The
[Godoc reference](https://godoc.org/github.com/square/go-jose#pkg-constants)
has a list of constants.
Key encryption | Algorithm identifier(s)
:------------------------- | :------------------------------
RSA-PKCS#1v1.5 | RSA1_5
RSA-OAEP | RSA-OAEP, RSA-OAEP-256
AES key wrap | A128KW, A192KW, A256KW
AES-GCM key wrap | A128GCMKW, A192GCMKW, A256GCMKW
ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW
ECDH-ES (direct) | ECDH-ES<sup>1</sup>
Direct encryption | dir<sup>1</sup>
<sup>1. Not supported in multi-recipient mode</sup>
Signing / MAC | Algorithm identifier(s)
:------------------------- | :------------------------------
RSASSA-PKCS#1v1.5 | RS256, RS384, RS512
RSASSA-PSS | PS256, PS384, PS512
HMAC | HS256, HS384, HS512
ECDSA | ES256, ES384, ES512
Content encryption | Algorithm identifier(s)
:------------------------- | :------------------------------
AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512
AES-GCM | A128GCM, A192GCM, A256GCM
Compression | Algorithm identifiers(s)
:------------------------- | -------------------------------
DEFLATE (RFC 1951) | DEF
### Supported key types
See below for a table of supported key types. These are understood by the
library, and can be passed to corresponding functions such as `NewEncrypter` or
`NewSigner`.
Algorithm(s) | Corresponding types
:------------------------- | -------------------------------
RSA | *[rsa.PublicKey](http://golang.org/pkg/crypto/rsa/#PublicKey), *[rsa.PrivateKey](http://golang.org/pkg/crypto/rsa/#PrivateKey)
ECDH, ECDSA | *[ecdsa.PublicKey](http://golang.org/pkg/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](http://golang.org/pkg/crypto/ecdsa/#PrivateKey)
AES, HMAC | []byte
## Examples
Encryption/decryption example using RSA:
```Go
// Generate a public/private key pair to use for this example. The library
// also provides two utility functions (LoadPublicKey and LoadPrivateKey)
// that can be used to load keys from PEM/DER-encoded data.
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
// Instantiate an encrypter using RSA-OAEP with AES128-GCM. An error would
// indicate that the selected algorithm(s) are not currently supported.
publicKey := &privateKey.PublicKey
encrypter, err := NewEncrypter(RSA_OAEP, A128GCM, publicKey)
if err != nil {
panic(err)
}
// Encrypt a sample plaintext. Calling the encrypter returns an encrypted
// JWE object, which can then be serialized for output afterwards. An error
// would indicate a problem in an underlying cryptographic primitive.
var plaintext = []byte("Lorem ipsum dolor sit amet")
object, err := encrypter.Encrypt(plaintext)
if err != nil {
panic(err)
}
// Serialize the encrypted object using the full serialization format.
// Alternatively you can also use the compact format here by calling
// object.CompactSerialize() instead.
serialized := object.FullSerialize()
// Parse the serialized, encrypted JWE object. An error would indicate that
// the given input did not represent a valid message.
object, err = ParseEncrypted(serialized)
if err != nil {
panic(err)
}
// Now we can decrypt and get back our original plaintext. An error here
// would indicate the the message failed to decrypt, e.g. because the auth
// tag was broken or the message was tampered with.
decrypted, err := object.Decrypt(privateKey)
if err != nil {
panic(err)
}
fmt.Printf(string(decrypted))
// output: Lorem ipsum dolor sit amet
```
Signing/verification example using RSA:
```Go
// Generate a public/private key pair to use for this example. The library
// also provides two utility functions (LoadPublicKey and LoadPrivateKey)
// that can be used to load keys from PEM/DER-encoded data.
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
// Instantiate a signer using RSASSA-PSS (SHA512) with the given private key.
signer, err := NewSigner(PS512, privateKey)
if err != nil {
panic(err)
}
// Sign a sample payload. Calling the signer returns a protected JWS object,
// which can then be serialized for output afterwards. An error would
// indicate a problem in an underlying cryptographic primitive.
var payload = []byte("Lorem ipsum dolor sit amet")
object, err := signer.Sign(payload)
if err != nil {
panic(err)
}
// Serialize the encrypted object using the full serialization format.
// Alternatively you can also use the compact format here by calling
// object.CompactSerialize() instead.
serialized := object.FullSerialize()
// Parse the serialized, protected JWS object. An error would indicate that
// the given input did not represent a valid message.
object, err = ParseSigned(serialized)
if err != nil {
panic(err)
}
// Now we can verify the signature on the payload. An error here would
// indicate the the message failed to verify, e.g. because the signature was
// broken or the message was tampered with.
output, err := object.Verify(&privateKey.PublicKey)
if err != nil {
panic(err)
}
fmt.Printf(string(output))
// output: Lorem ipsum dolor sit amet
```
More examples can be found in the [Godoc
reference](https://godoc.org/github.com/square/go-jose) for this package. The
`jose-util` subdirectory also contains a small command-line utility for
encrypting/decrypting JWE messages which might be useful as an example.

View file

@ -1,498 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package jose
import (
"crypto"
"crypto/aes"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"errors"
"fmt"
"math/big"
"github.com/square/go-jose/cipher"
)
// A generic RSA-based encrypter/verifier
type rsaEncrypterVerifier struct {
publicKey *rsa.PublicKey
}
// A generic RSA-based decrypter/signer
type rsaDecrypterSigner struct {
privateKey *rsa.PrivateKey
}
// A generic EC-based encrypter/verifier
type ecEncrypterVerifier struct {
publicKey *ecdsa.PublicKey
}
// A key generator for ECDH-ES
type ecKeyGenerator struct {
size int
algID string
publicKey *ecdsa.PublicKey
}
// A generic EC-based decrypter/signer
type ecDecrypterSigner struct {
privateKey *ecdsa.PrivateKey
}
// newRSARecipient creates recipientKeyInfo based on the given key.
func newRSARecipient(keyAlg KeyAlgorithm, publicKey *rsa.PublicKey) (recipientKeyInfo, error) {
// Verify that key management algorithm is supported by this encrypter
switch keyAlg {
case RSA1_5, RSA_OAEP, RSA_OAEP_256:
default:
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
}
return recipientKeyInfo{
keyAlg: keyAlg,
keyEncrypter: &rsaEncrypterVerifier{
publicKey: publicKey,
},
}, nil
}
// newRSASigner creates a recipientSigInfo based on the given key.
func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipientSigInfo, error) {
// Verify that key management algorithm is supported by this encrypter
switch sigAlg {
case RS256, RS384, RS512, PS256, PS384, PS512:
default:
return recipientSigInfo{}, ErrUnsupportedAlgorithm
}
return recipientSigInfo{
sigAlg: sigAlg,
publicKey: &JsonWebKey{
Key: &privateKey.PublicKey,
},
signer: &rsaDecrypterSigner{
privateKey: privateKey,
},
}, nil
}
// newECDHRecipient creates recipientKeyInfo based on the given key.
func newECDHRecipient(keyAlg KeyAlgorithm, publicKey *ecdsa.PublicKey) (recipientKeyInfo, error) {
// Verify that key management algorithm is supported by this encrypter
switch keyAlg {
case ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW:
default:
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
}
return recipientKeyInfo{
keyAlg: keyAlg,
keyEncrypter: &ecEncrypterVerifier{
publicKey: publicKey,
},
}, nil
}
// newECDSASigner creates a recipientSigInfo based on the given key.
func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (recipientSigInfo, error) {
// Verify that key management algorithm is supported by this encrypter
switch sigAlg {
case ES256, ES384, ES512:
default:
return recipientSigInfo{}, ErrUnsupportedAlgorithm
}
return recipientSigInfo{
sigAlg: sigAlg,
publicKey: &JsonWebKey{
Key: &privateKey.PublicKey,
},
signer: &ecDecrypterSigner{
privateKey: privateKey,
},
}, nil
}
// Encrypt the given payload and update the object.
func (ctx rsaEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
encryptedKey, err := ctx.encrypt(cek, alg)
if err != nil {
return recipientInfo{}, err
}
return recipientInfo{
encryptedKey: encryptedKey,
header: &rawHeader{},
}, nil
}
// Encrypt the given payload. Based on the key encryption algorithm,
// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256).
func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, error) {
switch alg {
case RSA1_5:
return rsa.EncryptPKCS1v15(randReader, ctx.publicKey, cek)
case RSA_OAEP:
return rsa.EncryptOAEP(sha1.New(), randReader, ctx.publicKey, cek, []byte{})
case RSA_OAEP_256:
return rsa.EncryptOAEP(sha256.New(), randReader, ctx.publicKey, cek, []byte{})
}
return nil, ErrUnsupportedAlgorithm
}
// Decrypt the given payload and return the content encryption key.
func (ctx rsaDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
return ctx.decrypt(recipient.encryptedKey, KeyAlgorithm(headers.Alg), generator)
}
// Decrypt the given payload. Based on the key encryption algorithm,
// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256).
func (ctx rsaDecrypterSigner) decrypt(jek []byte, alg KeyAlgorithm, generator keyGenerator) ([]byte, error) {
// Note: The random reader on decrypt operations is only used for blinding,
// so stubbing is meanlingless (hence the direct use of rand.Reader).
switch alg {
case RSA1_5:
defer func() {
// DecryptPKCS1v15SessionKey sometimes panics on an invalid payload
// because of an index out of bounds error, which we want to ignore.
// This has been fixed in Go 1.3.1 (released 2014/08/13), the recover()
// only exists for preventing crashes with unpatched versions.
// See: https://groups.google.com/forum/#!topic/golang-dev/7ihX6Y6kx9k
// See: https://code.google.com/p/go/source/detail?r=58ee390ff31602edb66af41ed10901ec95904d33
_ = recover()
}()
// Perform some input validation.
keyBytes := ctx.privateKey.PublicKey.N.BitLen() / 8
if keyBytes != len(jek) {
// Input size is incorrect, the encrypted payload should always match
// the size of the public modulus (e.g. using a 2048 bit key will
// produce 256 bytes of output). Reject this since it's invalid input.
return nil, ErrCryptoFailure
}
cek, _, err := generator.genKey()
if err != nil {
return nil, ErrCryptoFailure
}
// When decrypting an RSA-PKCS1v1.5 payload, we must take precautions to
// prevent chosen-ciphertext attacks as described in RFC 3218, "Preventing
// the Million Message Attack on Cryptographic Message Syntax". We are
// therefore deliberatly ignoring errors here.
_ = rsa.DecryptPKCS1v15SessionKey(rand.Reader, ctx.privateKey, jek, cek)
return cek, nil
case RSA_OAEP:
// Use rand.Reader for RSA blinding
return rsa.DecryptOAEP(sha1.New(), rand.Reader, ctx.privateKey, jek, []byte{})
case RSA_OAEP_256:
// Use rand.Reader for RSA blinding
return rsa.DecryptOAEP(sha256.New(), rand.Reader, ctx.privateKey, jek, []byte{})
}
return nil, ErrUnsupportedAlgorithm
}
// Sign the given payload
func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
var hash crypto.Hash
switch alg {
case RS256, PS256:
hash = crypto.SHA256
case RS384, PS384:
hash = crypto.SHA384
case RS512, PS512:
hash = crypto.SHA512
default:
return Signature{}, ErrUnsupportedAlgorithm
}
hasher := hash.New()
// According to documentation, Write() on hash never fails
_, _ = hasher.Write(payload)
hashed := hasher.Sum(nil)
var out []byte
var err error
switch alg {
case RS256, RS384, RS512:
out, err = rsa.SignPKCS1v15(randReader, ctx.privateKey, hash, hashed)
case PS256, PS384, PS512:
out, err = rsa.SignPSS(randReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{
SaltLength: rsa.PSSSaltLengthAuto,
})
}
if err != nil {
return Signature{}, err
}
return Signature{
signature: out,
protected: &rawHeader{},
}, nil
}
// Verify the given payload
func (ctx rsaEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
var hash crypto.Hash
switch alg {
case RS256, PS256:
hash = crypto.SHA256
case RS384, PS384:
hash = crypto.SHA384
case RS512, PS512:
hash = crypto.SHA512
default:
return ErrUnsupportedAlgorithm
}
hasher := hash.New()
// According to documentation, Write() on hash never fails
_, _ = hasher.Write(payload)
hashed := hasher.Sum(nil)
switch alg {
case RS256, RS384, RS512:
return rsa.VerifyPKCS1v15(ctx.publicKey, hash, hashed, signature)
case PS256, PS384, PS512:
return rsa.VerifyPSS(ctx.publicKey, hash, hashed, signature, nil)
}
return ErrUnsupportedAlgorithm
}
// Encrypt the given payload and update the object.
func (ctx ecEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
switch alg {
case ECDH_ES:
// ECDH-ES mode doesn't wrap a key, the shared secret is used directly as the key.
return recipientInfo{
header: &rawHeader{},
}, nil
case ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW:
default:
return recipientInfo{}, ErrUnsupportedAlgorithm
}
generator := ecKeyGenerator{
algID: string(alg),
publicKey: ctx.publicKey,
}
switch alg {
case ECDH_ES_A128KW:
generator.size = 16
case ECDH_ES_A192KW:
generator.size = 24
case ECDH_ES_A256KW:
generator.size = 32
}
kek, header, err := generator.genKey()
if err != nil {
return recipientInfo{}, err
}
block, err := aes.NewCipher(kek)
if err != nil {
return recipientInfo{}, err
}
jek, err := josecipher.KeyWrap(block, cek)
if err != nil {
return recipientInfo{}, err
}
return recipientInfo{
encryptedKey: jek,
header: &header,
}, nil
}
// Get key size for EC key generator
func (ctx ecKeyGenerator) keySize() int {
return ctx.size
}
// Get a content encryption key for ECDH-ES
func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) {
priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, randReader)
if err != nil {
return nil, rawHeader{}, err
}
out := josecipher.DeriveECDHES(ctx.algID, []byte{}, []byte{}, priv, ctx.publicKey, ctx.size)
headers := rawHeader{
Epk: &JsonWebKey{
Key: &priv.PublicKey,
},
}
return out, headers, nil
}
// Decrypt the given payload and return the content encryption key.
func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
if headers.Epk == nil {
return nil, errors.New("square/go-jose: missing epk header")
}
publicKey, ok := headers.Epk.Key.(*ecdsa.PublicKey)
if publicKey == nil || !ok {
return nil, errors.New("square/go-jose: invalid epk header")
}
apuData := headers.Apu.bytes()
apvData := headers.Apv.bytes()
deriveKey := func(algID string, size int) []byte {
return josecipher.DeriveECDHES(algID, apuData, apvData, ctx.privateKey, publicKey, size)
}
var keySize int
switch KeyAlgorithm(headers.Alg) {
case ECDH_ES:
// ECDH-ES uses direct key agreement, no key unwrapping necessary.
return deriveKey(string(headers.Enc), generator.keySize()), nil
case ECDH_ES_A128KW:
keySize = 16
case ECDH_ES_A192KW:
keySize = 24
case ECDH_ES_A256KW:
keySize = 32
default:
return nil, ErrUnsupportedAlgorithm
}
key := deriveKey(headers.Alg, keySize)
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return josecipher.KeyUnwrap(block, recipient.encryptedKey)
}
// Sign the given payload
func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
var expectedBitSize int
var hash crypto.Hash
switch alg {
case ES256:
expectedBitSize = 256
hash = crypto.SHA256
case ES384:
expectedBitSize = 384
hash = crypto.SHA384
case ES512:
expectedBitSize = 521
hash = crypto.SHA512
}
curveBits := ctx.privateKey.Curve.Params().BitSize
if expectedBitSize != curveBits {
return Signature{}, fmt.Errorf("square/go-jose: expected %d bit key, got %d bits instead", expectedBitSize, curveBits)
}
hasher := hash.New()
// According to documentation, Write() on hash never fails
_, _ = hasher.Write(payload)
hashed := hasher.Sum(nil)
r, s, err := ecdsa.Sign(randReader, ctx.privateKey, hashed)
if err != nil {
return Signature{}, err
}
keyBytes := curveBits / 8
if curveBits%8 > 0 {
keyBytes += 1
}
// We serialize the outpus (r and s) into big-endian byte arrays and pad
// them with zeros on the left to make sure the sizes work out. Both arrays
// must be keyBytes long, and the output must be 2*keyBytes long.
rBytes := r.Bytes()
rBytesPadded := make([]byte, keyBytes)
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)
sBytes := s.Bytes()
sBytesPadded := make([]byte, keyBytes)
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)
out := append(rBytesPadded, sBytesPadded...)
return Signature{
signature: out,
protected: &rawHeader{},
}, nil
}
// Verify the given payload
func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error {
var keySize int
var hash crypto.Hash
switch alg {
case ES256:
keySize = 32
hash = crypto.SHA256
case ES384:
keySize = 48
hash = crypto.SHA384
case ES512:
keySize = 66
hash = crypto.SHA512
}
if len(signature) != 2*keySize {
return fmt.Errorf("square/go-jose: invalid signature size, have %d bytes, wanted %d", len(signature), 2*keySize)
}
hasher := hash.New()
// According to documentation, Write() on hash never fails
_, _ = hasher.Write(payload)
hashed := hasher.Sum(nil)
r := big.NewInt(0).SetBytes(signature[:keySize])
s := big.NewInt(0).SetBytes(signature[keySize:])
match := ecdsa.Verify(ctx.publicKey, hashed, r, s)
if !match {
return errors.New("square/go-jose: ecdsa signature failed to verify")
}
return nil
}

View file

@ -1,196 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package josecipher
import (
"bytes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"encoding/binary"
"errors"
"hash"
)
const (
nonceBytes = 16
)
// NewCBCHMAC instantiates a new AEAD based on CBC+HMAC.
func NewCBCHMAC(key []byte, newBlockCipher func([]byte) (cipher.Block, error)) (cipher.AEAD, error) {
keySize := len(key) / 2
integrityKey := key[:keySize]
encryptionKey := key[keySize:]
blockCipher, err := newBlockCipher(encryptionKey)
if err != nil {
return nil, err
}
var hash func() hash.Hash
switch keySize {
case 16:
hash = sha256.New
case 24:
hash = sha512.New384
case 32:
hash = sha512.New
}
return &cbcAEAD{
hash: hash,
blockCipher: blockCipher,
authtagBytes: keySize,
integrityKey: integrityKey,
}, nil
}
// An AEAD based on CBC+HMAC
type cbcAEAD struct {
hash func() hash.Hash
authtagBytes int
integrityKey []byte
blockCipher cipher.Block
}
func (ctx *cbcAEAD) NonceSize() int {
return nonceBytes
}
func (ctx *cbcAEAD) Overhead() int {
// Maximum overhead is block size (for padding) plus auth tag length, where
// the length of the auth tag is equivalent to the key size.
return ctx.blockCipher.BlockSize() + ctx.authtagBytes
}
// Seal encrypts and authenticates the plaintext.
func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte {
// Output buffer -- must take care not to mangle plaintext input.
ciphertext := make([]byte, len(plaintext)+ctx.Overhead())[:len(plaintext)]
copy(ciphertext, plaintext)
ciphertext = padBuffer(ciphertext, ctx.blockCipher.BlockSize())
cbc := cipher.NewCBCEncrypter(ctx.blockCipher, nonce)
cbc.CryptBlocks(ciphertext, ciphertext)
authtag := ctx.computeAuthTag(data, nonce, ciphertext)
ret, out := resize(dst, len(dst)+len(ciphertext)+len(authtag))
copy(out, ciphertext)
copy(out[len(ciphertext):], authtag)
return ret
}
// Open decrypts and authenticates the ciphertext.
func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
if len(ciphertext) < ctx.authtagBytes {
return nil, errors.New("square/go-jose: invalid ciphertext (too short)")
}
offset := len(ciphertext) - ctx.authtagBytes
expectedTag := ctx.computeAuthTag(data, nonce, ciphertext[:offset])
match := subtle.ConstantTimeCompare(expectedTag, ciphertext[offset:])
if match != 1 {
return nil, errors.New("square/go-jose: invalid ciphertext (auth tag mismatch)")
}
cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce)
// Make copy of ciphertext buffer, don't want to modify in place
buffer := append([]byte{}, []byte(ciphertext[:offset])...)
if len(buffer)%ctx.blockCipher.BlockSize() > 0 {
return nil, errors.New("square/go-jose: invalid ciphertext (invalid length)")
}
cbc.CryptBlocks(buffer, buffer)
// Remove padding
plaintext, err := unpadBuffer(buffer, ctx.blockCipher.BlockSize())
if err != nil {
return nil, err
}
ret, out := resize(dst, len(dst)+len(plaintext))
copy(out, plaintext)
return ret, nil
}
// Compute an authentication tag
func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte {
buffer := make([]byte, len(aad)+len(nonce)+len(ciphertext)+8)
n := 0
n += copy(buffer, aad)
n += copy(buffer[n:], nonce)
n += copy(buffer[n:], ciphertext)
binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad)*8))
// According to documentation, Write() on hash.Hash never fails.
hmac := hmac.New(ctx.hash, ctx.integrityKey)
_, _ = hmac.Write(buffer)
return hmac.Sum(nil)[:ctx.authtagBytes]
}
// resize ensures the the given slice has a capacity of at least n bytes.
// If the capacity of the slice is less than n, a new slice is allocated
// and the existing data will be copied.
func resize(in []byte, n int) (head, tail []byte) {
if cap(in) >= n {
head = in[:n]
} else {
head = make([]byte, n)
copy(head, in)
}
tail = head[len(in):]
return
}
// Apply padding
func padBuffer(buffer []byte, blockSize int) []byte {
missing := blockSize - (len(buffer) % blockSize)
ret, out := resize(buffer, len(buffer)+missing)
padding := bytes.Repeat([]byte{byte(missing)}, missing)
copy(out, padding)
return ret
}
// Remove padding
func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) {
if len(buffer)%blockSize != 0 {
return nil, errors.New("square/go-jose: invalid padding")
}
last := buffer[len(buffer)-1]
count := int(last)
if count == 0 || count > blockSize || count > len(buffer) {
return nil, errors.New("square/go-jose: invalid padding")
}
padding := bytes.Repeat([]byte{last}, count)
if !bytes.HasSuffix(buffer, padding) {
return nil, errors.New("square/go-jose: invalid padding")
}
return buffer[:len(buffer)-count], nil
}

View file

@ -1,75 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package josecipher
import (
"crypto"
"encoding/binary"
"hash"
"io"
)
type concatKDF struct {
z, info []byte
i uint32
cache []byte
hasher hash.Hash
}
// NewConcatKDF builds a KDF reader based on the given inputs.
func NewConcatKDF(hash crypto.Hash, z, algID, ptyUInfo, ptyVInfo, supPubInfo, supPrivInfo []byte) io.Reader {
buffer := make([]byte, len(algID)+len(ptyUInfo)+len(ptyVInfo)+len(supPubInfo)+len(supPrivInfo))
n := 0
n += copy(buffer, algID)
n += copy(buffer[n:], ptyUInfo)
n += copy(buffer[n:], ptyVInfo)
n += copy(buffer[n:], supPubInfo)
copy(buffer[n:], supPrivInfo)
hasher := hash.New()
return &concatKDF{
z: z,
info: buffer,
hasher: hasher,
cache: []byte{},
i: 1,
}
}
func (ctx *concatKDF) Read(out []byte) (int, error) {
copied := copy(out, ctx.cache)
ctx.cache = ctx.cache[copied:]
for copied < len(out) {
ctx.hasher.Reset()
// Write on a hash.Hash never fails
_ = binary.Write(ctx.hasher, binary.BigEndian, ctx.i)
_, _ = ctx.hasher.Write(ctx.z)
_, _ = ctx.hasher.Write(ctx.info)
hash := ctx.hasher.Sum(nil)
chunkCopied := copy(out[copied:], hash)
copied += chunkCopied
ctx.cache = hash[chunkCopied:]
ctx.i++
}
return copied, nil
}

View file

@ -1,51 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package josecipher
import (
"crypto"
"crypto/ecdsa"
"encoding/binary"
)
// DeriveECDHES derives a shared encryption key using ECDH/ConcatKDF as described in JWE/JWA.
func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte {
// algId, partyUInfo, partyVInfo inputs must be prefixed with the length
algID := lengthPrefixed([]byte(alg))
ptyUInfo := lengthPrefixed(apuData)
ptyVInfo := lengthPrefixed(apvData)
// suppPubInfo is the encoded length of the output size in bits
supPubInfo := make([]byte, 4)
binary.BigEndian.PutUint32(supPubInfo, uint32(size)*8)
z, _ := priv.PublicKey.Curve.ScalarMult(pub.X, pub.Y, priv.D.Bytes())
reader := NewConcatKDF(crypto.SHA256, z.Bytes(), algID, ptyUInfo, ptyVInfo, supPubInfo, []byte{})
key := make([]byte, size)
// Read on the KDF will never fail
_, _ = reader.Read(key)
return key
}
func lengthPrefixed(data []byte) []byte {
out := make([]byte, len(data)+4)
binary.BigEndian.PutUint32(out, uint32(len(data)))
copy(out[4:], data)
return out
}

View file

@ -1,109 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package josecipher
import (
"crypto/cipher"
"crypto/subtle"
"encoding/binary"
"errors"
)
var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6}
// KeyWrap implements NIST key wrapping; it wraps a content encryption key (cek) with the given block cipher.
func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) {
if len(cek)%8 != 0 {
return nil, errors.New("square/go-jose: key wrap input must be 8 byte blocks")
}
n := len(cek) / 8
r := make([][]byte, n)
for i := range r {
r[i] = make([]byte, 8)
copy(r[i], cek[i*8:])
}
buffer := make([]byte, 16)
tBytes := make([]byte, 8)
copy(buffer, defaultIV)
for t := 0; t < 6*n; t++ {
copy(buffer[8:], r[t%n])
block.Encrypt(buffer, buffer)
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
for i := 0; i < 8; i++ {
buffer[i] = buffer[i] ^ tBytes[i]
}
copy(r[t%n], buffer[8:])
}
out := make([]byte, (n+1)*8)
copy(out, buffer[:8])
for i := range r {
copy(out[(i+1)*8:], r[i])
}
return out, nil
}
// KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher.
func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) {
if len(ciphertext)%8 != 0 {
return nil, errors.New("square/go-jose: key wrap input must be 8 byte blocks")
}
n := (len(ciphertext) / 8) - 1
r := make([][]byte, n)
for i := range r {
r[i] = make([]byte, 8)
copy(r[i], ciphertext[(i+1)*8:])
}
buffer := make([]byte, 16)
tBytes := make([]byte, 8)
copy(buffer[:8], ciphertext[:8])
for t := 6*n - 1; t >= 0; t-- {
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
for i := 0; i < 8; i++ {
buffer[i] = buffer[i] ^ tBytes[i]
}
copy(buffer[8:], r[t%n])
block.Decrypt(buffer, buffer)
copy(r[t%n], buffer[8:])
}
if subtle.ConstantTimeCompare(buffer[:8], defaultIV) == 0 {
return nil, errors.New("square/go-jose: failed to unwrap key")
}
out := make([]byte, n*8)
for i := range r {
copy(out[i*8:], r[i])
}
return out, nil
}

View file

@ -1,317 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package jose
import (
"crypto/ecdsa"
"crypto/rsa"
"fmt"
"reflect"
)
// Encrypter represents an encrypter which produces an encrypted JWE object.
type Encrypter interface {
Encrypt(plaintext []byte) (*JsonWebEncryption, error)
EncryptWithAuthData(plaintext []byte, aad []byte) (*JsonWebEncryption, error)
SetCompression(alg CompressionAlgorithm)
}
// MultiEncrypter represents an encrypter which supports multiple recipients.
type MultiEncrypter interface {
Encrypt(plaintext []byte) (*JsonWebEncryption, error)
EncryptWithAuthData(plaintext []byte, aad []byte) (*JsonWebEncryption, error)
SetCompression(alg CompressionAlgorithm)
AddRecipient(alg KeyAlgorithm, encryptionKey interface{}) error
}
// A generic content cipher
type contentCipher interface {
keySize() int
encrypt(cek []byte, aad, plaintext []byte) (*aeadParts, error)
decrypt(cek []byte, aad []byte, parts *aeadParts) ([]byte, error)
}
// A key generator (for generating/getting a CEK)
type keyGenerator interface {
keySize() int
genKey() ([]byte, rawHeader, error)
}
// A generic key encrypter
type keyEncrypter interface {
encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) // Encrypt a key
}
// A generic key decrypter
type keyDecrypter interface {
decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) // Decrypt a key
}
// A generic encrypter based on the given key encrypter and content cipher.
type genericEncrypter struct {
contentAlg ContentEncryption
compressionAlg CompressionAlgorithm
cipher contentCipher
recipients []recipientKeyInfo
keyGenerator keyGenerator
}
type recipientKeyInfo struct {
keyAlg KeyAlgorithm
keyEncrypter keyEncrypter
}
// SetCompression sets a compression algorithm to be applied before encryption.
func (ctx *genericEncrypter) SetCompression(compressionAlg CompressionAlgorithm) {
ctx.compressionAlg = compressionAlg
}
// NewEncrypter creates an appropriate encrypter based on the key type
func NewEncrypter(alg KeyAlgorithm, enc ContentEncryption, encryptionKey interface{}) (Encrypter, error) {
encrypter := &genericEncrypter{
contentAlg: enc,
compressionAlg: NONE,
recipients: []recipientKeyInfo{},
cipher: getContentCipher(enc),
}
if encrypter.cipher == nil {
return nil, ErrUnsupportedAlgorithm
}
switch alg {
case DIRECT:
// Direct encryption mode must be treated differently
if reflect.TypeOf(encryptionKey) != reflect.TypeOf([]byte{}) {
return nil, ErrUnsupportedKeyType
}
encrypter.keyGenerator = staticKeyGenerator{
key: encryptionKey.([]byte),
}
recipient, _ := newSymmetricRecipient(alg, encryptionKey.([]byte))
encrypter.recipients = []recipientKeyInfo{recipient}
return encrypter, nil
case ECDH_ES:
// ECDH-ES (w/o key wrapping) is similar to DIRECT mode
typeOf := reflect.TypeOf(encryptionKey)
if typeOf != reflect.TypeOf(&ecdsa.PublicKey{}) {
return nil, ErrUnsupportedKeyType
}
encrypter.keyGenerator = ecKeyGenerator{
size: encrypter.cipher.keySize(),
algID: string(enc),
publicKey: encryptionKey.(*ecdsa.PublicKey),
}
recipient, _ := newECDHRecipient(alg, encryptionKey.(*ecdsa.PublicKey))
encrypter.recipients = []recipientKeyInfo{recipient}
return encrypter, nil
default:
// Can just add a standard recipient
encrypter.keyGenerator = randomKeyGenerator{
size: encrypter.cipher.keySize(),
}
err := encrypter.AddRecipient(alg, encryptionKey)
return encrypter, err
}
}
// NewMultiEncrypter creates a multi-encrypter based on the given parameters
func NewMultiEncrypter(enc ContentEncryption) (MultiEncrypter, error) {
cipher := getContentCipher(enc)
if cipher == nil {
return nil, ErrUnsupportedAlgorithm
}
encrypter := &genericEncrypter{
contentAlg: enc,
compressionAlg: NONE,
recipients: []recipientKeyInfo{},
cipher: cipher,
keyGenerator: randomKeyGenerator{
size: cipher.keySize(),
},
}
return encrypter, nil
}
func (ctx *genericEncrypter) AddRecipient(alg KeyAlgorithm, encryptionKey interface{}) (err error) {
var recipient recipientKeyInfo
switch alg {
case DIRECT, ECDH_ES:
return fmt.Errorf("square/go-jose: key algorithm '%s' not supported in multi-recipient mode", alg)
}
switch encryptionKey := encryptionKey.(type) {
case *rsa.PublicKey:
recipient, err = newRSARecipient(alg, encryptionKey)
case []byte:
recipient, err = newSymmetricRecipient(alg, encryptionKey)
case *ecdsa.PublicKey:
recipient, err = newECDHRecipient(alg, encryptionKey)
default:
return ErrUnsupportedKeyType
}
if err == nil {
ctx.recipients = append(ctx.recipients, recipient)
}
return err
}
// newDecrypter creates an appropriate decrypter based on the key type
func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) {
switch decryptionKey := decryptionKey.(type) {
case *rsa.PrivateKey:
return &rsaDecrypterSigner{
privateKey: decryptionKey,
}, nil
case *ecdsa.PrivateKey:
return &ecDecrypterSigner{
privateKey: decryptionKey,
}, nil
case []byte:
return &symmetricKeyCipher{
key: decryptionKey,
}, nil
default:
return nil, ErrUnsupportedKeyType
}
}
// Implementation of encrypt method producing a JWE object.
func (ctx *genericEncrypter) Encrypt(plaintext []byte) (*JsonWebEncryption, error) {
return ctx.EncryptWithAuthData(plaintext, nil)
}
// Implementation of encrypt method producing a JWE object.
func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JsonWebEncryption, error) {
obj := &JsonWebEncryption{}
obj.aad = aad
obj.protected = &rawHeader{
Enc: ctx.contentAlg,
}
obj.recipients = make([]recipientInfo, len(ctx.recipients))
if len(ctx.recipients) == 0 {
return nil, fmt.Errorf("square/go-jose: no recipients to encrypt to")
}
cek, headers, err := ctx.keyGenerator.genKey()
if err != nil {
return nil, err
}
obj.protected.merge(&headers)
for i, info := range ctx.recipients {
recipient, err := info.keyEncrypter.encryptKey(cek, info.keyAlg)
if err != nil {
return nil, err
}
recipient.header.Alg = string(info.keyAlg)
obj.recipients[i] = recipient
}
if len(ctx.recipients) == 1 {
// Move per-recipient headers into main protected header if there's
// only a single recipient.
obj.protected.merge(obj.recipients[0].header)
obj.recipients[0].header = nil
}
if ctx.compressionAlg != NONE {
plaintext, err = compress(ctx.compressionAlg, plaintext)
if err != nil {
return nil, err
}
obj.protected.Zip = ctx.compressionAlg
}
authData := obj.computeAuthData()
parts, err := ctx.cipher.encrypt(cek, authData, plaintext)
if err != nil {
return nil, err
}
obj.iv = parts.iv
obj.ciphertext = parts.ciphertext
obj.tag = parts.tag
return obj, nil
}
// Decrypt and validate the object and return the plaintext.
func (obj JsonWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) {
headers := obj.mergedHeaders(nil)
if len(headers.Crit) > 0 {
return nil, fmt.Errorf("square/go-jose: unsupported crit header")
}
decrypter, err := newDecrypter(decryptionKey)
if err != nil {
return nil, err
}
cipher := getContentCipher(headers.Enc)
if cipher == nil {
return nil, fmt.Errorf("square/go-jose: unsupported enc value '%s'", string(headers.Enc))
}
generator := randomKeyGenerator{
size: cipher.keySize(),
}
parts := &aeadParts{
iv: obj.iv,
ciphertext: obj.ciphertext,
tag: obj.tag,
}
authData := obj.computeAuthData()
var plaintext []byte
for _, recipient := range obj.recipients {
recipientHeaders := obj.mergedHeaders(&recipient)
cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator)
if err == nil {
// Found a valid CEK -- let's try to decrypt.
plaintext, err = cipher.decrypt(cek, authData, parts)
if err == nil {
break
}
}
}
if plaintext == nil {
return nil, ErrCryptoFailure
}
// The "zip" header paramter may only be present in the protected header.
if obj.protected.Zip != "" {
plaintext, err = decompress(obj.protected.Zip, plaintext)
}
return plaintext, err
}

View file

@ -1,26 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
/*
Package jose aims to provide an implementation of the Javascript Object Signing
and Encryption set of standards. For the moment, it mainly focuses on
encryption and signing based on the JSON Web Encryption and JSON Web Signature
standards. The library supports both the compact and full serialization
formats, and has optional support for multiple recipients.
*/
package jose

View file

@ -1,192 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package jose
import (
"bytes"
"compress/flate"
"encoding/base64"
"encoding/binary"
"encoding/json"
"io"
"math/big"
"regexp"
"strings"
)
var stripWhitespaceRegex = regexp.MustCompile("\\s")
// Url-safe base64 encode that strips padding
func base64URLEncode(data []byte) string {
var result = base64.URLEncoding.EncodeToString(data)
return strings.TrimRight(result, "=")
}
// Url-safe base64 decoder that adds padding
func base64URLDecode(data string) ([]byte, error) {
var missing = (4 - len(data)%4) % 4
data += strings.Repeat("=", missing)
return base64.URLEncoding.DecodeString(data)
}
// Helper function to serialize known-good objects.
// Precondition: value is not a nil pointer.
func mustSerializeJSON(value interface{}) []byte {
out, err := json.Marshal(value)
if err != nil {
panic(err)
}
// We never want to serialize the top-level value "null," since it's not a
// valid JOSE message. But if a caller passes in a nil pointer to this method,
// json.Marshal will happily serialize it as the top-level value "null". If
// that value is then embedded in another operation, for instance by being
// base64-encoded and fed as input to a signing algorithm
// (https://github.com/square/go-jose/issues/22), the result will be
// incorrect. Because this method is intended for known-good objects, and a nil
// pointer is not a known-good object, we are free to panic in this case.
// Note: It's not possible to directly check whether the data pointed at by an
// interface is a nil pointer, so we do this hacky workaround.
// https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I
if string(out) == "null" {
panic("Tried to serialize a nil pointer.")
}
return out
}
// Strip all newlines and whitespace
func stripWhitespace(data string) string {
return stripWhitespaceRegex.ReplaceAllString(data, "")
}
// Perform compression based on algorithm
func compress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) {
switch algorithm {
case DEFLATE:
return deflate(input)
default:
return nil, ErrUnsupportedAlgorithm
}
}
// Perform decompression based on algorithm
func decompress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) {
switch algorithm {
case DEFLATE:
return inflate(input)
default:
return nil, ErrUnsupportedAlgorithm
}
}
// Compress with DEFLATE
func deflate(input []byte) ([]byte, error) {
output := new(bytes.Buffer)
// Writing to byte buffer, err is always nil
writer, _ := flate.NewWriter(output, 1)
_, _ = io.Copy(writer, bytes.NewBuffer(input))
err := writer.Close()
return output.Bytes(), err
}
// Decompress with DEFLATE
func inflate(input []byte) ([]byte, error) {
output := new(bytes.Buffer)
reader := flate.NewReader(bytes.NewBuffer(input))
_, err := io.Copy(output, reader)
if err != nil {
return nil, err
}
err = reader.Close()
return output.Bytes(), err
}
// byteBuffer represents a slice of bytes that can be serialized to url-safe base64.
type byteBuffer struct {
data []byte
}
func newBuffer(data []byte) *byteBuffer {
if data == nil {
return nil
}
return &byteBuffer{
data: data,
}
}
func newFixedSizeBuffer(data []byte, length int) *byteBuffer {
if len(data) > length {
panic("square/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)")
}
pad := make([]byte, length-len(data))
return newBuffer(append(pad, data...))
}
func newBufferFromInt(num uint64) *byteBuffer {
data := make([]byte, 8)
binary.BigEndian.PutUint64(data, num)
return newBuffer(bytes.TrimLeft(data, "\x00"))
}
func (b *byteBuffer) MarshalJSON() ([]byte, error) {
return json.Marshal(b.base64())
}
func (b *byteBuffer) UnmarshalJSON(data []byte) error {
var encoded string
err := json.Unmarshal(data, &encoded)
if err != nil {
return err
}
if encoded == "" {
return nil
}
decoded, err := base64URLDecode(encoded)
if err != nil {
return err
}
*b = *newBuffer(decoded)
return nil
}
func (b *byteBuffer) base64() string {
return base64URLEncode(b.data)
}
func (b *byteBuffer) bytes() []byte {
// Handling nil here allows us to transparently handle nil slices when serializing.
if b == nil {
return nil
}
return b.data
}
func (b byteBuffer) bigInt() *big.Int {
return new(big.Int).SetBytes(b.data)
}
func (b byteBuffer) toInt() int {
return int(b.bigInt().Int64())
}

View file

@ -1,263 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package jose
import (
"encoding/json"
"fmt"
"strings"
)
// rawJsonWebEncryption represents a raw JWE JSON object. Used for parsing/serializing.
type rawJsonWebEncryption struct {
Protected *byteBuffer `json:"protected,omitempty"`
Unprotected *rawHeader `json:"unprotected,omitempty"`
Header *rawHeader `json:"header,omitempty"`
Recipients []rawRecipientInfo `json:"recipients,omitempty"`
Aad *byteBuffer `json:"aad,omitempty"`
EncryptedKey *byteBuffer `json:"encrypted_key,omitempty"`
Iv *byteBuffer `json:"iv,omitempty"`
Ciphertext *byteBuffer `json:"ciphertext,omitempty"`
Tag *byteBuffer `json:"tag,omitempty"`
}
// rawRecipientInfo represents a raw JWE Per-Recipient header JSON object. Used for parsing/serializing.
type rawRecipientInfo struct {
Header *rawHeader `json:"header,omitempty"`
EncryptedKey string `json:"encrypted_key,omitempty"`
}
// JsonWebEncryption represents an encrypted JWE object after parsing.
type JsonWebEncryption struct {
Header JoseHeader
protected, unprotected *rawHeader
recipients []recipientInfo
aad, iv, ciphertext, tag []byte
original *rawJsonWebEncryption
}
// recipientInfo represents a raw JWE Per-Recipient header JSON object after parsing.
type recipientInfo struct {
header *rawHeader
encryptedKey []byte
}
// GetAuthData retrieves the (optional) authenticated data attached to the object.
func (obj JsonWebEncryption) GetAuthData() []byte {
if obj.aad != nil {
out := make([]byte, len(obj.aad))
copy(out, obj.aad)
return out
}
return nil
}
// Get the merged header values
func (obj JsonWebEncryption) mergedHeaders(recipient *recipientInfo) rawHeader {
out := rawHeader{}
out.merge(obj.protected)
out.merge(obj.unprotected)
if recipient != nil {
out.merge(recipient.header)
}
return out
}
// Get the additional authenticated data from a JWE object.
func (obj JsonWebEncryption) computeAuthData() []byte {
var protected string
if obj.original != nil {
protected = obj.original.Protected.base64()
} else {
protected = base64URLEncode(mustSerializeJSON((obj.protected)))
}
output := []byte(protected)
if obj.aad != nil {
output = append(output, '.')
output = append(output, []byte(base64URLEncode(obj.aad))...)
}
return output
}
// ParseEncrypted parses an encrypted message in compact or full serialization format.
func ParseEncrypted(input string) (*JsonWebEncryption, error) {
input = stripWhitespace(input)
if strings.HasPrefix(input, "{") {
return parseEncryptedFull(input)
}
return parseEncryptedCompact(input)
}
// parseEncryptedFull parses a message in compact format.
func parseEncryptedFull(input string) (*JsonWebEncryption, error) {
var parsed rawJsonWebEncryption
err := json.Unmarshal([]byte(input), &parsed)
if err != nil {
return nil, err
}
return parsed.sanitized()
}
// sanitized produces a cleaned-up JWE object from the raw JSON.
func (parsed *rawJsonWebEncryption) sanitized() (*JsonWebEncryption, error) {
obj := &JsonWebEncryption{
original: parsed,
unprotected: parsed.Unprotected,
}
obj.Header = obj.mergedHeaders(nil).sanitized()
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
err := json.Unmarshal(parsed.Protected.bytes(), &obj.protected)
if err != nil {
return nil, fmt.Errorf("square/go-jose: invalid protected header: %s, %s", err, parsed.Protected.base64())
}
}
if len(parsed.Recipients) == 0 {
obj.recipients = []recipientInfo{
recipientInfo{
header: parsed.Header,
encryptedKey: parsed.EncryptedKey.bytes(),
},
}
} else {
obj.recipients = make([]recipientInfo, len(parsed.Recipients))
for r := range parsed.Recipients {
encryptedKey, err := base64URLDecode(parsed.Recipients[r].EncryptedKey)
if err != nil {
return nil, err
}
obj.recipients[r].header = parsed.Recipients[r].Header
obj.recipients[r].encryptedKey = encryptedKey
}
}
for _, recipient := range obj.recipients {
headers := obj.mergedHeaders(&recipient)
if headers.Alg == "" || headers.Enc == "" {
return nil, fmt.Errorf("square/go-jose: message is missing alg/enc headers")
}
}
obj.iv = parsed.Iv.bytes()
obj.ciphertext = parsed.Ciphertext.bytes()
obj.tag = parsed.Tag.bytes()
obj.aad = parsed.Aad.bytes()
return obj, nil
}
// parseEncryptedCompact parses a message in compact format.
func parseEncryptedCompact(input string) (*JsonWebEncryption, error) {
parts := strings.Split(input, ".")
if len(parts) != 5 {
return nil, fmt.Errorf("square/go-jose: compact JWE format must have five parts")
}
rawProtected, err := base64URLDecode(parts[0])
if err != nil {
return nil, err
}
encryptedKey, err := base64URLDecode(parts[1])
if err != nil {
return nil, err
}
iv, err := base64URLDecode(parts[2])
if err != nil {
return nil, err
}
ciphertext, err := base64URLDecode(parts[3])
if err != nil {
return nil, err
}
tag, err := base64URLDecode(parts[4])
if err != nil {
return nil, err
}
raw := &rawJsonWebEncryption{
Protected: newBuffer(rawProtected),
EncryptedKey: newBuffer(encryptedKey),
Iv: newBuffer(iv),
Ciphertext: newBuffer(ciphertext),
Tag: newBuffer(tag),
}
return raw.sanitized()
}
// CompactSerialize serializes an object using the compact serialization format.
func (obj JsonWebEncryption) CompactSerialize() (string, error) {
if len(obj.recipients) > 1 || obj.unprotected != nil || obj.recipients[0].header != nil {
return "", ErrNotSupported
}
serializedProtected := mustSerializeJSON(obj.protected)
return fmt.Sprintf(
"%s.%s.%s.%s.%s",
base64URLEncode(serializedProtected),
base64URLEncode(obj.recipients[0].encryptedKey),
base64URLEncode(obj.iv),
base64URLEncode(obj.ciphertext),
base64URLEncode(obj.tag)), nil
}
// FullSerialize serializes an object using the full JSON serialization format.
func (obj JsonWebEncryption) FullSerialize() string {
raw := rawJsonWebEncryption{
Unprotected: obj.unprotected,
Iv: newBuffer(obj.iv),
Ciphertext: newBuffer(obj.ciphertext),
EncryptedKey: newBuffer(obj.recipients[0].encryptedKey),
Tag: newBuffer(obj.tag),
Aad: newBuffer(obj.aad),
Recipients: []rawRecipientInfo{},
}
if len(obj.recipients) > 1 {
for _, recipient := range obj.recipients {
info := rawRecipientInfo{
Header: recipient.header,
EncryptedKey: base64URLEncode(recipient.encryptedKey),
}
raw.Recipients = append(raw.Recipients, info)
}
} else {
// Use flattened serialization
raw.Header = obj.recipients[0].header
raw.EncryptedKey = newBuffer(obj.recipients[0].encryptedKey)
}
raw.Protected = newBuffer(mustSerializeJSON(obj.protected))
return string(mustSerializeJSON(raw))
}

View file

@ -1,275 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package jose
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"encoding/json"
"fmt"
"math/big"
"reflect"
)
// rawJsonWebKey represents a public or private key in JWK format, used for parsing/serializing.
type rawJsonWebKey struct {
Kty string `json:"kty,omitempty"`
Kid string `json:"kid,omitempty"`
Crv string `json:"crv,omitempty"`
Alg string `json:"alg,omitempty"`
X *byteBuffer `json:"x,omitempty"`
Y *byteBuffer `json:"y,omitempty"`
N *byteBuffer `json:"n,omitempty"`
E *byteBuffer `json:"e,omitempty"`
// -- Following fields are only used for private keys --
// RSA uses D, P and Q, while ECDSA uses only D. Fields Dp, Dq, and Qi are
// completely optional. Therefore for RSA/ECDSA, D != nil is a contract that
// we have a private key whereas D == nil means we have only a public key.
D *byteBuffer `json:"d,omitempty"`
P *byteBuffer `json:"p,omitempty"`
Q *byteBuffer `json:"q,omitempty"`
Dp *byteBuffer `json:"dp,omitempty"`
Dq *byteBuffer `json:"dq,omitempty"`
Qi *byteBuffer `json:"qi,omitempty"`
}
// JsonWebKey represents a public or private key in JWK format.
type JsonWebKey struct {
Key interface{}
KeyID string
Algorithm string
}
// MarshalJSON serializes the given key to its JSON representation.
func (k JsonWebKey) MarshalJSON() ([]byte, error) {
var raw *rawJsonWebKey
var err error
switch key := k.Key.(type) {
case *ecdsa.PublicKey:
raw, err = fromEcPublicKey(key)
case *rsa.PublicKey:
raw = fromRsaPublicKey(key)
case *ecdsa.PrivateKey:
raw, err = fromEcPrivateKey(key)
case *rsa.PrivateKey:
raw, err = fromRsaPrivateKey(key)
default:
return nil, fmt.Errorf("square/go-jose: unkown key type '%s'", reflect.TypeOf(key))
}
if err != nil {
return nil, err
}
raw.Kid = k.KeyID
raw.Alg = k.Algorithm
return json.Marshal(raw)
}
// UnmarshalJSON reads a key from its JSON representation.
func (k *JsonWebKey) UnmarshalJSON(data []byte) (err error) {
var raw rawJsonWebKey
err = json.Unmarshal(data, &raw)
if err != nil {
return err
}
var key interface{}
switch raw.Kty {
case "EC":
if raw.D != nil {
key, err = raw.ecPrivateKey()
} else {
key, err = raw.ecPublicKey()
}
case "RSA":
if raw.D != nil {
key, err = raw.rsaPrivateKey()
} else {
key, err = raw.rsaPublicKey()
}
default:
err = fmt.Errorf("square/go-jose: unkown json web key type '%s'", raw.Kty)
}
if err == nil {
*k = JsonWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg}
}
return
}
func (key rawJsonWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
if key.N == nil || key.E == nil {
return nil, fmt.Errorf("square/go-jose: invalid RSA key, missing n/e values")
}
return &rsa.PublicKey{
N: key.N.bigInt(),
E: key.E.toInt(),
}, nil
}
func fromRsaPublicKey(pub *rsa.PublicKey) *rawJsonWebKey {
return &rawJsonWebKey{
Kty: "RSA",
N: newBuffer(pub.N.Bytes()),
E: newBufferFromInt(uint64(pub.E)),
}
}
func (key rawJsonWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
var curve elliptic.Curve
switch key.Crv {
case "P-256":
curve = elliptic.P256()
case "P-384":
curve = elliptic.P384()
case "P-521":
curve = elliptic.P521()
default:
return nil, fmt.Errorf("square/go-jose: unsupported elliptic curve '%s'", key.Crv)
}
if key.X == nil || key.Y == nil {
return nil, fmt.Errorf("square/go-jose: invalid EC key, missing x/y values")
}
return &ecdsa.PublicKey{
Curve: curve,
X: key.X.bigInt(),
Y: key.Y.bigInt(),
}, nil
}
func fromEcPublicKey(pub *ecdsa.PublicKey) (*rawJsonWebKey, error) {
if pub == nil || pub.X == nil || pub.Y == nil {
return nil, fmt.Errorf("square/go-jose: invalid EC key (nil, or X/Y missing)")
}
name, err := curveName(pub.Curve)
if err != nil {
return nil, err
}
size := curveSize(pub.Curve)
xBytes := pub.X.Bytes()
yBytes := pub.Y.Bytes()
if len(xBytes) > size || len(yBytes) > size {
return nil, fmt.Errorf("square/go-jose: invalid EC key (X/Y too large)")
}
key := &rawJsonWebKey{
Kty: "EC",
Crv: name,
X: newFixedSizeBuffer(xBytes, size),
Y: newFixedSizeBuffer(yBytes, size),
}
return key, nil
}
func (key rawJsonWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
if key.N == nil || key.E == nil || key.D == nil || key.P == nil || key.Q == nil {
return nil, fmt.Errorf("square/go-jose: invalid RSA private key, missing values")
}
rv := &rsa.PrivateKey{
PublicKey: rsa.PublicKey{
N: key.N.bigInt(),
E: key.E.toInt(),
},
D: key.D.bigInt(),
Primes: []*big.Int{
key.P.bigInt(),
key.Q.bigInt(),
},
}
if key.Dp != nil {
rv.Precomputed.Dp = key.Dp.bigInt()
}
if key.Dq != nil {
rv.Precomputed.Dq = key.Dq.bigInt()
}
if key.Qi != nil {
rv.Precomputed.Qinv = key.Qi.bigInt()
}
err := rv.Validate()
return rv, err
}
func fromRsaPrivateKey(rsa *rsa.PrivateKey) (*rawJsonWebKey, error) {
if len(rsa.Primes) != 2 {
return nil, ErrUnsupportedKeyType
}
raw := fromRsaPublicKey(&rsa.PublicKey)
raw.D = newBuffer(rsa.D.Bytes())
raw.P = newBuffer(rsa.Primes[0].Bytes())
raw.Q = newBuffer(rsa.Primes[1].Bytes())
return raw, nil
}
func (key rawJsonWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
var curve elliptic.Curve
switch key.Crv {
case "P-256":
curve = elliptic.P256()
case "P-384":
curve = elliptic.P384()
case "P-521":
curve = elliptic.P521()
default:
return nil, fmt.Errorf("square/go-jose: unsupported elliptic curve '%s'", key.Crv)
}
if key.X == nil || key.Y == nil || key.D == nil {
return nil, fmt.Errorf("square/go-jose: invalid EC private key, missing x/y/d values")
}
return &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: curve,
X: key.X.bigInt(),
Y: key.Y.bigInt(),
},
D: key.D.bigInt(),
}, nil
}
func fromEcPrivateKey(ec *ecdsa.PrivateKey) (*rawJsonWebKey, error) {
raw, err := fromEcPublicKey(&ec.PublicKey)
if err != nil {
return nil, err
}
if ec.D == nil {
return nil, fmt.Errorf("square/go-jose: invalid EC private key")
}
raw.D = newBuffer(ec.D.Bytes())
return raw, nil
}

View file

@ -1,237 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package jose
import (
"encoding/json"
"fmt"
"strings"
)
// rawJsonWebSignature represents a raw JWS JSON object. Used for parsing/serializing.
type rawJsonWebSignature struct {
Payload *byteBuffer `json:"payload,omitempty"`
Signatures []rawSignatureInfo `json:"signatures,omitempty"`
Protected *byteBuffer `json:"protected,omitempty"`
Header *rawHeader `json:"header,omitempty"`
Signature *byteBuffer `json:"signature,omitempty"`
}
// rawSignatureInfo represents a single JWS signature over the JWS payload and protected header.
type rawSignatureInfo struct {
Protected *byteBuffer `json:"protected,omitempty"`
Header *rawHeader `json:"header,omitempty"`
Signature *byteBuffer `json:"signature,omitempty"`
}
// JsonWebSignature represents a signed JWS object after parsing.
type JsonWebSignature struct {
payload []byte
Signatures []Signature
}
// Signature represents a single signature over the JWS payload and protected header.
type Signature struct {
Header JoseHeader
protected *rawHeader
header *rawHeader
signature []byte
original *rawSignatureInfo
}
// ParseSigned parses an encrypted message in compact or full serialization format.
func ParseSigned(input string) (*JsonWebSignature, error) {
input = stripWhitespace(input)
if strings.HasPrefix(input, "{") {
return parseSignedFull(input)
}
return parseSignedCompact(input)
}
// Get a header value
func (sig Signature) mergedHeaders() rawHeader {
out := rawHeader{}
out.merge(sig.protected)
out.merge(sig.header)
return out
}
// Compute data to be signed
func (obj JsonWebSignature) computeAuthData(signature *Signature) []byte {
var serializedProtected string
if signature.original != nil && signature.original.Protected != nil {
serializedProtected = signature.original.Protected.base64()
} else if signature.protected != nil {
serializedProtected = base64URLEncode(mustSerializeJSON(signature.protected))
} else {
serializedProtected = ""
}
return []byte(fmt.Sprintf("%s.%s",
serializedProtected,
base64URLEncode(obj.payload)))
}
// parseSignedFull parses a message in full format.
func parseSignedFull(input string) (*JsonWebSignature, error) {
var parsed rawJsonWebSignature
err := json.Unmarshal([]byte(input), &parsed)
if err != nil {
return nil, err
}
return parsed.sanitized()
}
// sanitized produces a cleaned-up JWS object from the raw JSON.
func (parsed *rawJsonWebSignature) sanitized() (*JsonWebSignature, error) {
if parsed.Payload == nil {
return nil, fmt.Errorf("square/go-jose: missing payload in JWS message")
}
obj := &JsonWebSignature{
payload: parsed.Payload.bytes(),
Signatures: make([]Signature, len(parsed.Signatures)),
}
if len(parsed.Signatures) == 0 {
// No signatures array, must be flattened serialization
signature := Signature{}
if parsed.Protected != nil && len(parsed.Protected.bytes()) > 0 {
signature.protected = &rawHeader{}
err := json.Unmarshal(parsed.Protected.bytes(), signature.protected)
if err != nil {
return nil, err
}
}
signature.header = parsed.Header
signature.signature = parsed.Signature.bytes()
// Make a fake "original" rawSignatureInfo to store the unprocessed
// Protected header. This is necessary because the Protected header can
// contain arbitrary fields not registered as part of the spec. See
// https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-4
// If we unmarshal Protected into a rawHeader with its explicit list of fields,
// we cannot marshal losslessly. So we have to keep around the original bytes.
// This is used in computeAuthData, which will first attempt to use
// the original bytes of a protected header, and fall back on marshaling the
// header struct only if those bytes are not available.
signature.original = &rawSignatureInfo{
Protected: parsed.Protected,
Header: parsed.Header,
Signature: parsed.Signature,
}
signature.Header = signature.mergedHeaders().sanitized()
obj.Signatures = append(obj.Signatures, signature)
}
for i, sig := range parsed.Signatures {
if sig.Protected != nil && len(sig.Protected.bytes()) > 0 {
obj.Signatures[i].protected = &rawHeader{}
err := json.Unmarshal(sig.Protected.bytes(), obj.Signatures[i].protected)
if err != nil {
return nil, err
}
}
obj.Signatures[i].signature = sig.Signature.bytes()
// Copy value of sig
original := sig
obj.Signatures[i].header = sig.Header
obj.Signatures[i].original = &original
obj.Signatures[i].Header = obj.Signatures[i].mergedHeaders().sanitized()
}
return obj, nil
}
// parseSignedCompact parses a message in compact format.
func parseSignedCompact(input string) (*JsonWebSignature, error) {
parts := strings.Split(input, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("square/go-jose: compact JWS format must have three parts")
}
rawProtected, err := base64URLDecode(parts[0])
if err != nil {
return nil, err
}
payload, err := base64URLDecode(parts[1])
if err != nil {
return nil, err
}
signature, err := base64URLDecode(parts[2])
if err != nil {
return nil, err
}
raw := &rawJsonWebSignature{
Payload: newBuffer(payload),
Protected: newBuffer(rawProtected),
Signature: newBuffer(signature),
}
return raw.sanitized()
}
// CompactSerialize serializes an object using the compact serialization format.
func (obj JsonWebSignature) CompactSerialize() (string, error) {
if len(obj.Signatures) > 1 || obj.Signatures[0].header != nil {
return "", ErrNotSupported
}
serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
return fmt.Sprintf(
"%s.%s.%s",
base64URLEncode(serializedProtected),
base64URLEncode(obj.payload),
base64URLEncode(obj.Signatures[0].signature)), nil
}
// FullSerialize serializes an object using the full JSON serialization format.
func (obj JsonWebSignature) FullSerialize() string {
raw := rawJsonWebSignature{
Payload: newBuffer(obj.payload),
}
if len(obj.Signatures) == 1 {
serializedProtected := mustSerializeJSON(obj.Signatures[0].protected)
raw.Protected = newBuffer(serializedProtected)
raw.Header = obj.Signatures[0].header
raw.Signature = newBuffer(obj.Signatures[0].signature)
} else {
raw.Signatures = make([]rawSignatureInfo, len(obj.Signatures))
for i, signature := range obj.Signatures {
serializedProtected := mustSerializeJSON(signature.protected)
raw.Signatures[i] = rawSignatureInfo{
Protected: newBuffer(serializedProtected),
Header: signature.header,
Signature: newBuffer(signature.signature),
}
}
}
return string(mustSerializeJSON(raw))
}

View file

@ -1,214 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package jose
import (
"crypto/elliptic"
"errors"
"fmt"
)
// KeyAlgorithm represents a key management algorithm.
type KeyAlgorithm string
// SignatureAlgorithm represents a signature (or MAC) algorithm.
type SignatureAlgorithm string
// ContentEncryption represents a content encryption algorithm.
type ContentEncryption string
// CompressionAlgorithm represents an algorithm used for plaintext compression.
type CompressionAlgorithm string
var (
// ErrCryptoFailure represents an error in cryptographic primitive. This
// occurs when, for example, a message had an invalid authentication tag or
// could not be decrypted.
ErrCryptoFailure = errors.New("square/go-jose: error in cryptographic primitive")
// ErrUnsupportedAlgorithm indicates that a selected algorithm is not
// supported. This occurs when trying to instantiate an encrypter for an
// algorithm that is not yet implemented.
ErrUnsupportedAlgorithm = errors.New("square/go-jose: unknown/unsupported algorithm")
// ErrUnsupportedKeyType indicates that the given key type/format is not
// supported. This occurs when trying to instantiate an encrypter and passing
// it a key of an unrecognized type or with unsupported parameters, such as
// an RSA private key with more than two primes.
ErrUnsupportedKeyType = errors.New("square/go-jose: unsupported key type/format")
// ErrNotSupported serialization of object is not supported. This occurs when
// trying to compact-serialize an object which can't be represented in
// compact form.
ErrNotSupported = errors.New("square/go-jose: compact serialization not supported for object")
)
// Key management algorithms
const (
RSA1_5 = KeyAlgorithm("RSA1_5") // RSA-PKCS1v1.5
RSA_OAEP = KeyAlgorithm("RSA-OAEP") // RSA-OAEP-SHA1
RSA_OAEP_256 = KeyAlgorithm("RSA-OAEP-256") // RSA-OAEP-SHA256
A128KW = KeyAlgorithm("A128KW") // AES key wrap (128)
A192KW = KeyAlgorithm("A192KW") // AES key wrap (192)
A256KW = KeyAlgorithm("A256KW") // AES key wrap (256)
DIRECT = KeyAlgorithm("dir") // Direct encryption
ECDH_ES = KeyAlgorithm("ECDH-ES") // ECDH-ES
ECDH_ES_A128KW = KeyAlgorithm("ECDH-ES+A128KW") // ECDH-ES + AES key wrap (128)
ECDH_ES_A192KW = KeyAlgorithm("ECDH-ES+A192KW") // ECDH-ES + AES key wrap (192)
ECDH_ES_A256KW = KeyAlgorithm("ECDH-ES+A256KW") // ECDH-ES + AES key wrap (256)
A128GCMKW = KeyAlgorithm("A128GCMKW") // AES-GCM key wrap (128)
A192GCMKW = KeyAlgorithm("A192GCMKW") // AES-GCM key wrap (192)
A256GCMKW = KeyAlgorithm("A256GCMKW") // AES-GCM key wrap (256)
PBES2_HS256_A128KW = KeyAlgorithm("PBES2-HS256+A128KW") // PBES2 + HMAC-SHA256 + AES key wrap (128)
PBES2_HS384_A192KW = KeyAlgorithm("PBES2-HS384+A192KW") // PBES2 + HMAC-SHA384 + AES key wrap (192)
PBES2_HS512_A256KW = KeyAlgorithm("PBES2-HS512+A256KW") // PBES2 + HMAC-SHA512 + AES key wrap (256)
)
// Signature algorithms
const (
HS256 = SignatureAlgorithm("HS256") // HMAC using SHA-256
HS384 = SignatureAlgorithm("HS384") // HMAC using SHA-384
HS512 = SignatureAlgorithm("HS512") // HMAC using SHA-512
RS256 = SignatureAlgorithm("RS256") // RSASSA-PKCS-v1.5 using SHA-256
RS384 = SignatureAlgorithm("RS384") // RSASSA-PKCS-v1.5 using SHA-384
RS512 = SignatureAlgorithm("RS512") // RSASSA-PKCS-v1.5 using SHA-512
ES256 = SignatureAlgorithm("ES256") // RCDSA using P-256 and SHA-256
ES384 = SignatureAlgorithm("ES384") // RCDSA using P-384 and SHA-384
ES512 = SignatureAlgorithm("ES512") // RCDSA using P-521 and SHA-512
PS256 = SignatureAlgorithm("PS256") // RSASSA-PSS using SHA256 and MGF1-SHA256
PS384 = SignatureAlgorithm("PS384") // RSASSA-PSS using SHA384 and MGF1-SHA384
PS512 = SignatureAlgorithm("PS512") // RSASSA-PSS using SHA512 and MGF1-SHA512
)
// Content encryption algorithms
const (
A128CBC_HS256 = ContentEncryption("A128CBC-HS256") // AES-CBC + HMAC-SHA256 (128)
A192CBC_HS384 = ContentEncryption("A192CBC-HS384") // AES-CBC + HMAC-SHA384 (192)
A256CBC_HS512 = ContentEncryption("A256CBC-HS512") // AES-CBC + HMAC-SHA512 (256)
A128GCM = ContentEncryption("A128GCM") // AES-GCM (128)
A192GCM = ContentEncryption("A192GCM") // AES-GCM (192)
A256GCM = ContentEncryption("A256GCM") // AES-GCM (256)
)
// Compression algorithms
const (
NONE = CompressionAlgorithm("") // No compression
DEFLATE = CompressionAlgorithm("DEF") // DEFLATE (RFC 1951)
)
// rawHeader represents the JOSE header for JWE/JWS objects (used for parsing).
type rawHeader struct {
Alg string `json:"alg,omitempty"`
Enc ContentEncryption `json:"enc,omitempty"`
Zip CompressionAlgorithm `json:"zip,omitempty"`
Crit []string `json:"crit,omitempty"`
Apu *byteBuffer `json:"apu,omitempty"`
Apv *byteBuffer `json:"apv,omitempty"`
Epk *JsonWebKey `json:"epk,omitempty"`
Iv *byteBuffer `json:"iv,omitempty"`
Tag *byteBuffer `json:"tag,omitempty"`
Jwk *JsonWebKey `json:"jwk,omitempty"`
Kid string `json:"kid,omitempty"`
}
// JoseHeader represents the read-only JOSE header for JWE/JWS objects.
type JoseHeader struct {
KeyID string
JsonWebKey *JsonWebKey
Algorithm string
}
// sanitized produces a cleaned-up header object from the raw JSON.
func (parsed rawHeader) sanitized() JoseHeader {
return JoseHeader{
KeyID: parsed.Kid,
JsonWebKey: parsed.Jwk,
Algorithm: parsed.Alg,
}
}
// Merge headers from src into dst, giving precedence to headers from l.
func (dst *rawHeader) merge(src *rawHeader) {
if src == nil {
return
}
if dst.Alg == "" {
dst.Alg = src.Alg
}
if dst.Enc == "" {
dst.Enc = src.Enc
}
if dst.Zip == "" {
dst.Zip = src.Zip
}
if dst.Crit == nil {
dst.Crit = src.Crit
}
if dst.Crit == nil {
dst.Crit = src.Crit
}
if dst.Apu == nil {
dst.Apu = src.Apu
}
if dst.Apv == nil {
dst.Apv = src.Apv
}
if dst.Epk == nil {
dst.Epk = src.Epk
}
if dst.Iv == nil {
dst.Iv = src.Iv
}
if dst.Tag == nil {
dst.Tag = src.Tag
}
if dst.Kid == "" {
dst.Kid = src.Kid
}
if dst.Jwk == nil {
dst.Jwk = src.Jwk
}
}
// Get JOSE name of curve
func curveName(crv elliptic.Curve) (string, error) {
switch crv {
case elliptic.P256():
return "P-256", nil
case elliptic.P384():
return "P-384", nil
case elliptic.P521():
return "P-521", nil
default:
return "", fmt.Errorf("square/go-jose: unsupported/unknown elliptic curve")
}
}
// Get size of curve in bytes
func curveSize(crv elliptic.Curve) int {
bits := crv.Params().BitSize
div := bits / 8
mod := bits % 8
if mod == 0 {
return div
}
return div + 1
}

View file

@ -1,182 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package jose
import (
"crypto/ecdsa"
"crypto/rsa"
"fmt"
)
// Signer represents a signer which takes a payload and produces a signed JWS object.
type Signer interface {
Sign(payload []byte) (*JsonWebSignature, error)
}
// MultiSigner represents a signer which supports multiple recipients.
type MultiSigner interface {
Sign(payload []byte) (*JsonWebSignature, error)
AddRecipient(alg SignatureAlgorithm, signingKey interface{}) error
}
type payloadSigner interface {
signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error)
}
type payloadVerifier interface {
verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error
}
type genericSigner struct {
recipients []recipientSigInfo
}
type recipientSigInfo struct {
sigAlg SignatureAlgorithm
publicKey *JsonWebKey
signer payloadSigner
}
// NewSigner creates an appropriate signer based on the key type
func NewSigner(alg SignatureAlgorithm, signingKey interface{}) (Signer, error) {
// NewMultiSigner never fails (currently)
signer := NewMultiSigner()
err := signer.AddRecipient(alg, signingKey)
if err != nil {
return nil, err
}
return signer, nil
}
// NewMultiSigner creates a signer for multiple recipients
func NewMultiSigner() MultiSigner {
return &genericSigner{
recipients: []recipientSigInfo{},
}
}
// newVerifier creates a verifier based on the key type
func newVerifier(verificationKey interface{}) (payloadVerifier, error) {
switch verificationKey := verificationKey.(type) {
case *rsa.PublicKey:
return &rsaEncrypterVerifier{
publicKey: verificationKey,
}, nil
case *ecdsa.PublicKey:
return &ecEncrypterVerifier{
publicKey: verificationKey,
}, nil
case []byte:
return &symmetricMac{
key: verificationKey,
}, nil
case *JsonWebKey:
return newVerifier(verificationKey.Key)
default:
return nil, ErrUnsupportedKeyType
}
}
func (ctx *genericSigner) AddRecipient(alg SignatureAlgorithm, signingKey interface{}) error {
recipient, err := makeRecipient(alg, signingKey)
if err != nil {
return err
}
ctx.recipients = append(ctx.recipients, recipient)
return nil
}
func makeRecipient(alg SignatureAlgorithm, signingKey interface{}) (recipientSigInfo, error) {
switch signingKey := signingKey.(type) {
case *rsa.PrivateKey:
return newRSASigner(alg, signingKey)
case *ecdsa.PrivateKey:
return newECDSASigner(alg, signingKey)
case []byte:
return newSymmetricSigner(alg, signingKey)
case *JsonWebKey:
recipient, err := makeRecipient(alg, signingKey.Key)
if err != nil {
return recipientSigInfo{}, err
}
recipient.publicKey.KeyID = signingKey.KeyID
return recipient, nil
default:
return recipientSigInfo{}, ErrUnsupportedKeyType
}
}
func (ctx *genericSigner) Sign(payload []byte) (*JsonWebSignature, error) {
obj := &JsonWebSignature{}
obj.payload = payload
obj.Signatures = make([]Signature, len(ctx.recipients))
for i, recipient := range ctx.recipients {
protected := &rawHeader{
Alg: string(recipient.sigAlg),
}
if recipient.publicKey != nil {
protected.Jwk = recipient.publicKey
protected.Kid = recipient.publicKey.KeyID
}
serializedProtected := mustSerializeJSON(protected)
input := []byte(fmt.Sprintf("%s.%s",
base64URLEncode(serializedProtected),
base64URLEncode(payload)))
signatureInfo, err := recipient.signer.signPayload(input, recipient.sigAlg)
if err != nil {
return nil, err
}
signatureInfo.protected = protected
obj.Signatures[i] = signatureInfo
}
return obj, nil
}
// Verify validates the signature on the object and returns the payload.
func (obj JsonWebSignature) Verify(verificationKey interface{}) ([]byte, error) {
verifier, err := newVerifier(verificationKey)
if err != nil {
return nil, err
}
for _, signature := range obj.Signatures {
headers := signature.mergedHeaders()
if len(headers.Crit) > 0 {
// Unsupported crit header
continue
}
input := obj.computeAuthData(&signature)
alg := SignatureAlgorithm(headers.Alg)
err := verifier.verifyPayload(input, signature.signature, alg)
if err == nil {
return obj.payload, nil
}
}
return nil, ErrCryptoFailure
}

View file

@ -1,348 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package jose
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"errors"
"github.com/square/go-jose/cipher"
"hash"
"io"
)
// Random reader (stubbed out in tests)
var randReader = rand.Reader
// Dummy key cipher for shared symmetric key mode
type symmetricKeyCipher struct {
key []byte // Pre-shared content-encryption key
}
// Signer/verifier for MAC modes
type symmetricMac struct {
key []byte
}
// Input/output from an AEAD operation
type aeadParts struct {
iv, ciphertext, tag []byte
}
// A content cipher based on an AEAD construction
type aeadContentCipher struct {
keyBytes int
authtagBytes int
getAead func(key []byte) (cipher.AEAD, error)
}
// Random key generator
type randomKeyGenerator struct {
size int
}
// Static key generator
type staticKeyGenerator struct {
key []byte
}
// Create a new content cipher based on AES-GCM
func newAESGCM(keySize int) contentCipher {
return &aeadContentCipher{
keyBytes: keySize,
authtagBytes: 16,
getAead: func(key []byte) (cipher.AEAD, error) {
aes, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
return cipher.NewGCM(aes)
},
}
}
// Create a new content cipher based on AES-CBC+HMAC
func newAESCBC(keySize int) contentCipher {
return &aeadContentCipher{
keyBytes: keySize * 2,
authtagBytes: 16,
getAead: func(key []byte) (cipher.AEAD, error) {
return josecipher.NewCBCHMAC(key, aes.NewCipher)
},
}
}
// Get an AEAD cipher object for the given content encryption algorithm
func getContentCipher(alg ContentEncryption) contentCipher {
switch alg {
case A128GCM:
return newAESGCM(16)
case A192GCM:
return newAESGCM(24)
case A256GCM:
return newAESGCM(32)
case A128CBC_HS256:
return newAESCBC(16)
case A192CBC_HS384:
return newAESCBC(24)
case A256CBC_HS512:
return newAESCBC(32)
default:
return nil
}
}
// newSymmetricRecipient creates a JWE encrypter based on AES-GCM key wrap.
func newSymmetricRecipient(keyAlg KeyAlgorithm, key []byte) (recipientKeyInfo, error) {
switch keyAlg {
case DIRECT, A128GCMKW, A192GCMKW, A256GCMKW, A128KW, A192KW, A256KW:
default:
return recipientKeyInfo{}, ErrUnsupportedAlgorithm
}
return recipientKeyInfo{
keyAlg: keyAlg,
keyEncrypter: &symmetricKeyCipher{
key: key,
},
}, nil
}
// newSymmetricSigner creates a recipientSigInfo based on the given key.
func newSymmetricSigner(sigAlg SignatureAlgorithm, key []byte) (recipientSigInfo, error) {
// Verify that key management algorithm is supported by this encrypter
switch sigAlg {
case HS256, HS384, HS512:
default:
return recipientSigInfo{}, ErrUnsupportedAlgorithm
}
return recipientSigInfo{
sigAlg: sigAlg,
signer: &symmetricMac{
key: key,
},
}, nil
}
// Generate a random key for the given content cipher
func (ctx randomKeyGenerator) genKey() ([]byte, rawHeader, error) {
key := make([]byte, ctx.size)
_, err := io.ReadFull(randReader, key)
if err != nil {
return nil, rawHeader{}, err
}
return key, rawHeader{}, nil
}
// Key size for random generator
func (ctx randomKeyGenerator) keySize() int {
return ctx.size
}
// Generate a static key (for direct mode)
func (ctx staticKeyGenerator) genKey() ([]byte, rawHeader, error) {
cek := make([]byte, len(ctx.key))
copy(cek, ctx.key)
return cek, rawHeader{}, nil
}
// Key size for static generator
func (ctx staticKeyGenerator) keySize() int {
return len(ctx.key)
}
// Get key size for this cipher
func (ctx aeadContentCipher) keySize() int {
return ctx.keyBytes
}
// Encrypt some data
func (ctx aeadContentCipher) encrypt(key, aad, pt []byte) (*aeadParts, error) {
// Get a new AEAD instance
aead, err := ctx.getAead(key)
if err != nil {
return nil, err
}
// Initialize a new nonce
iv := make([]byte, aead.NonceSize())
_, err = io.ReadFull(randReader, iv)
if err != nil {
return nil, err
}
ciphertextAndTag := aead.Seal(nil, iv, pt, aad)
offset := len(ciphertextAndTag) - ctx.authtagBytes
return &aeadParts{
iv: iv,
ciphertext: ciphertextAndTag[:offset],
tag: ciphertextAndTag[offset:],
}, nil
}
// Decrypt some data
func (ctx aeadContentCipher) decrypt(key, aad []byte, parts *aeadParts) ([]byte, error) {
aead, err := ctx.getAead(key)
if err != nil {
return nil, err
}
return aead.Open(nil, parts.iv, append(parts.ciphertext, parts.tag...), aad)
}
// Encrypt the content encryption key.
func (ctx *symmetricKeyCipher) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) {
switch alg {
case DIRECT:
return recipientInfo{
header: &rawHeader{},
}, nil
case A128GCMKW, A192GCMKW, A256GCMKW:
aead := newAESGCM(len(ctx.key))
parts, err := aead.encrypt(ctx.key, []byte{}, cek)
if err != nil {
return recipientInfo{}, err
}
return recipientInfo{
header: &rawHeader{
Iv: newBuffer(parts.iv),
Tag: newBuffer(parts.tag),
},
encryptedKey: parts.ciphertext,
}, nil
case A128KW, A192KW, A256KW:
block, err := aes.NewCipher(ctx.key)
if err != nil {
return recipientInfo{}, err
}
jek, err := josecipher.KeyWrap(block, cek)
if err != nil {
return recipientInfo{}, err
}
return recipientInfo{
encryptedKey: jek,
header: &rawHeader{},
}, nil
}
return recipientInfo{}, ErrUnsupportedAlgorithm
}
// Decrypt the content encryption key.
func (ctx *symmetricKeyCipher) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) {
switch KeyAlgorithm(headers.Alg) {
case DIRECT:
cek := make([]byte, len(ctx.key))
copy(cek, ctx.key)
return cek, nil
case A128GCMKW, A192GCMKW, A256GCMKW:
aead := newAESGCM(len(ctx.key))
parts := &aeadParts{
iv: headers.Iv.bytes(),
ciphertext: recipient.encryptedKey,
tag: headers.Tag.bytes(),
}
cek, err := aead.decrypt(ctx.key, []byte{}, parts)
if err != nil {
return nil, err
}
return cek, nil
case A128KW, A192KW, A256KW:
block, err := aes.NewCipher(ctx.key)
if err != nil {
return nil, err
}
cek, err := josecipher.KeyUnwrap(block, recipient.encryptedKey)
if err != nil {
return nil, err
}
return cek, nil
}
return nil, ErrUnsupportedAlgorithm
}
// Sign the given payload
func (ctx symmetricMac) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) {
mac, err := ctx.hmac(payload, alg)
if err != nil {
return Signature{}, errors.New("square/go-jose: failed to compute hmac")
}
return Signature{
signature: mac,
protected: &rawHeader{},
}, nil
}
// Verify the given payload
func (ctx symmetricMac) verifyPayload(payload []byte, mac []byte, alg SignatureAlgorithm) error {
expected, err := ctx.hmac(payload, alg)
if err != nil {
return errors.New("square/go-jose: failed to compute hmac")
}
if len(mac) != len(expected) {
return errors.New("square/go-jose: invalid hmac")
}
match := subtle.ConstantTimeCompare(mac, expected)
if match != 1 {
return errors.New("square/go-jose: invalid hmac")
}
return nil
}
// Compute the HMAC based on the given alg value
func (ctx symmetricMac) hmac(payload []byte, alg SignatureAlgorithm) ([]byte, error) {
var hash func() hash.Hash
switch alg {
case HS256:
hash = sha256.New
case HS384:
hash = sha512.New384
case HS512:
hash = sha512.New
default:
return nil, ErrUnsupportedAlgorithm
}
hmac := hmac.New(hash, ctx.key)
// According to documentation, Write() on hash never fails
_, _ = hmac.Write(payload)
return hmac.Sum(nil), nil
}

View file

@ -1,74 +0,0 @@
/*-
* Copyright 2014 Square Inc.
*
* 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.
*/
package jose
import (
"crypto/x509"
"encoding/pem"
"fmt"
)
// LoadPublicKey loads a public key from PEM/DER-encoded data.
func LoadPublicKey(data []byte) (interface{}, error) {
input := data
block, _ := pem.Decode(data)
if block != nil {
input = block.Bytes
}
// Try to load SubjectPublicKeyInfo
pub, err0 := x509.ParsePKIXPublicKey(input)
if err0 == nil {
return pub, nil
}
cert, err1 := x509.ParseCertificate(input)
if err1 == nil {
return cert.PublicKey, nil
}
return nil, fmt.Errorf("square/go-jose: parse error, got '%s' and '%s'", err0, err1)
}
// LoadPrivateKey loads a private key from PEM/DER-encoded data.
func LoadPrivateKey(data []byte) (interface{}, error) {
input := data
block, _ := pem.Decode(data)
if block != nil {
input = block.Bytes
}
var priv interface{}
priv, err0 := x509.ParsePKCS1PrivateKey(input)
if err0 == nil {
return priv, nil
}
priv, err1 := x509.ParsePKCS8PrivateKey(input)
if err1 == nil {
return priv, nil
}
priv, err2 := x509.ParseECPrivateKey(input)
if err2 == nil {
return priv, nil
}
return nil, fmt.Errorf("square/go-jose: parse error, got '%s', '%s' and '%s'", err0, err1, err2)
}

27
vendor/golang.org/x/text/LICENSE generated vendored
View file

@ -1,27 +0,0 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

22
vendor/golang.org/x/text/PATENTS generated vendored
View file

@ -1,22 +0,0 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

View file

@ -1,351 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gen
import (
"bytes"
"encoding/gob"
"fmt"
"hash"
"hash/fnv"
"io"
"log"
"os"
"reflect"
"strings"
"unicode"
"unicode/utf8"
)
// This file contains utilities for generating code.
// TODO: other write methods like:
// - slices, maps, types, etc.
// CodeWriter is a utility for writing structured code. It computes the content
// hash and size of written content. It ensures there are newlines between
// written code blocks.
type CodeWriter struct {
buf bytes.Buffer
Size int
Hash hash.Hash32 // content hash
gob *gob.Encoder
// For comments we skip the usual one-line separator if they are followed by
// a code block.
skipSep bool
}
func (w *CodeWriter) Write(p []byte) (n int, err error) {
return w.buf.Write(p)
}
// NewCodeWriter returns a new CodeWriter.
func NewCodeWriter() *CodeWriter {
h := fnv.New32()
return &CodeWriter{Hash: h, gob: gob.NewEncoder(h)}
}
// WriteGoFile appends the buffer with the total size of all created structures
// and writes it as a Go file to the the given file with the given package name.
func (w *CodeWriter) WriteGoFile(filename, pkg string) {
f, err := os.Create(filename)
if err != nil {
log.Fatalf("Could not create file %s: %v", filename, err)
}
defer f.Close()
if _, err = w.WriteGo(f, pkg); err != nil {
log.Fatalf("Error writing file %s: %v", filename, err)
}
}
// WriteGo appends the buffer with the total size of all created structures and
// writes it as a Go file to the the given writer with the given package name.
func (w *CodeWriter) WriteGo(out io.Writer, pkg string) (n int, err error) {
sz := w.Size
w.WriteComment("Total table size %d bytes (%dKiB); checksum: %X\n", sz, sz/1024, w.Hash.Sum32())
defer w.buf.Reset()
return WriteGo(out, pkg, w.buf.Bytes())
}
func (w *CodeWriter) printf(f string, x ...interface{}) {
fmt.Fprintf(w, f, x...)
}
func (w *CodeWriter) insertSep() {
if w.skipSep {
w.skipSep = false
return
}
// Use at least two newlines to ensure a blank space between the previous
// block. WriteGoFile will remove extraneous newlines.
w.printf("\n\n")
}
// WriteComment writes a comment block. All line starts are prefixed with "//".
// Initial empty lines are gobbled. The indentation for the first line is
// stripped from consecutive lines.
func (w *CodeWriter) WriteComment(comment string, args ...interface{}) {
s := fmt.Sprintf(comment, args...)
s = strings.Trim(s, "\n")
// Use at least two newlines to ensure a blank space between the previous
// block. WriteGoFile will remove extraneous newlines.
w.printf("\n\n// ")
w.skipSep = true
// strip first indent level.
sep := "\n"
for ; len(s) > 0 && (s[0] == '\t' || s[0] == ' '); s = s[1:] {
sep += s[:1]
}
strings.NewReplacer(sep, "\n// ", "\n", "\n// ").WriteString(w, s)
w.printf("\n")
}
func (w *CodeWriter) writeSizeInfo(size int) {
w.printf("// Size: %d bytes\n", size)
}
// WriteConst writes a constant of the given name and value.
func (w *CodeWriter) WriteConst(name string, x interface{}) {
w.insertSep()
v := reflect.ValueOf(x)
switch v.Type().Kind() {
case reflect.String:
w.printf("const %s %s = ", name, typeName(x))
w.WriteString(v.String())
w.printf("\n")
default:
w.printf("const %s = %#v\n", name, x)
}
}
// WriteVar writes a variable of the given name and value.
func (w *CodeWriter) WriteVar(name string, x interface{}) {
w.insertSep()
v := reflect.ValueOf(x)
oldSize := w.Size
sz := int(v.Type().Size())
w.Size += sz
switch v.Type().Kind() {
case reflect.String:
w.printf("var %s %s = ", name, typeName(x))
w.WriteString(v.String())
case reflect.Struct:
w.gob.Encode(x)
fallthrough
case reflect.Slice, reflect.Array:
w.printf("var %s = ", name)
w.writeValue(v)
w.writeSizeInfo(w.Size - oldSize)
default:
w.printf("var %s %s = ", name, typeName(x))
w.gob.Encode(x)
w.writeValue(v)
w.writeSizeInfo(w.Size - oldSize)
}
w.printf("\n")
}
func (w *CodeWriter) writeValue(v reflect.Value) {
x := v.Interface()
switch v.Kind() {
case reflect.String:
w.WriteString(v.String())
case reflect.Array:
// Don't double count: callers of WriteArray count on the size being
// added, so we need to discount it here.
w.Size -= int(v.Type().Size())
w.writeSlice(x, true)
case reflect.Slice:
w.writeSlice(x, false)
case reflect.Struct:
w.printf("%s{\n", typeName(v.Interface()))
t := v.Type()
for i := 0; i < v.NumField(); i++ {
w.printf("%s: ", t.Field(i).Name)
w.writeValue(v.Field(i))
w.printf(",\n")
}
w.printf("}")
default:
w.printf("%#v", x)
}
}
// WriteString writes a string literal.
func (w *CodeWriter) WriteString(s string) {
s = strings.Replace(s, `\`, `\\`, -1)
io.WriteString(w.Hash, s) // content hash
w.Size += len(s)
const maxInline = 40
if len(s) <= maxInline {
w.printf("%q", s)
return
}
// We will render the string as a multi-line string.
const maxWidth = 80 - 4 - len(`"`) - len(`" +`)
// When starting on its own line, go fmt indents line 2+ an extra level.
n, max := maxWidth, maxWidth-4
// As per https://golang.org/issue/18078, the compiler has trouble
// compiling the concatenation of many strings, s0 + s1 + s2 + ... + sN,
// for large N. We insert redundant, explicit parentheses to work around
// that, lowering the N at any given step: (s0 + s1 + ... + s63) + (s64 +
// ... + s127) + etc + (etc + ... + sN).
explicitParens, extraComment := len(s) > 128*1024, ""
if explicitParens {
w.printf(`(`)
extraComment = "; the redundant, explicit parens are for https://golang.org/issue/18078"
}
// Print "" +\n, if a string does not start on its own line.
b := w.buf.Bytes()
if p := len(bytes.TrimRight(b, " \t")); p > 0 && b[p-1] != '\n' {
w.printf("\"\" + // Size: %d bytes%s\n", len(s), extraComment)
n, max = maxWidth, maxWidth
}
w.printf(`"`)
for sz, p, nLines := 0, 0, 0; p < len(s); {
var r rune
r, sz = utf8.DecodeRuneInString(s[p:])
out := s[p : p+sz]
chars := 1
if !unicode.IsPrint(r) || r == utf8.RuneError || r == '"' {
switch sz {
case 1:
out = fmt.Sprintf("\\x%02x", s[p])
case 2, 3:
out = fmt.Sprintf("\\u%04x", r)
case 4:
out = fmt.Sprintf("\\U%08x", r)
}
chars = len(out)
}
if n -= chars; n < 0 {
nLines++
if explicitParens && nLines&63 == 63 {
w.printf("\") + (\"")
}
w.printf("\" +\n\"")
n = max - len(out)
}
w.printf("%s", out)
p += sz
}
w.printf(`"`)
if explicitParens {
w.printf(`)`)
}
}
// WriteSlice writes a slice value.
func (w *CodeWriter) WriteSlice(x interface{}) {
w.writeSlice(x, false)
}
// WriteArray writes an array value.
func (w *CodeWriter) WriteArray(x interface{}) {
w.writeSlice(x, true)
}
func (w *CodeWriter) writeSlice(x interface{}, isArray bool) {
v := reflect.ValueOf(x)
w.gob.Encode(v.Len())
w.Size += v.Len() * int(v.Type().Elem().Size())
name := typeName(x)
if isArray {
name = fmt.Sprintf("[%d]%s", v.Len(), name[strings.Index(name, "]")+1:])
}
if isArray {
w.printf("%s{\n", name)
} else {
w.printf("%s{ // %d elements\n", name, v.Len())
}
switch kind := v.Type().Elem().Kind(); kind {
case reflect.String:
for _, s := range x.([]string) {
w.WriteString(s)
w.printf(",\n")
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
// nLine and nBlock are the number of elements per line and block.
nLine, nBlock, format := 8, 64, "%d,"
switch kind {
case reflect.Uint8:
format = "%#02x,"
case reflect.Uint16:
format = "%#04x,"
case reflect.Uint32:
nLine, nBlock, format = 4, 32, "%#08x,"
case reflect.Uint, reflect.Uint64:
nLine, nBlock, format = 4, 32, "%#016x,"
case reflect.Int8:
nLine = 16
}
n := nLine
for i := 0; i < v.Len(); i++ {
if i%nBlock == 0 && v.Len() > nBlock {
w.printf("// Entry %X - %X\n", i, i+nBlock-1)
}
x := v.Index(i).Interface()
w.gob.Encode(x)
w.printf(format, x)
if n--; n == 0 {
n = nLine
w.printf("\n")
}
}
w.printf("\n")
case reflect.Struct:
zero := reflect.Zero(v.Type().Elem()).Interface()
for i := 0; i < v.Len(); i++ {
x := v.Index(i).Interface()
w.gob.EncodeValue(v)
if !reflect.DeepEqual(zero, x) {
line := fmt.Sprintf("%#v,\n", x)
line = line[strings.IndexByte(line, '{'):]
w.printf("%d: ", i)
w.printf(line)
}
}
case reflect.Array:
for i := 0; i < v.Len(); i++ {
w.printf("%d: %#v,\n", i, v.Index(i).Interface())
}
default:
panic("gen: slice elem type not supported")
}
w.printf("}")
}
// WriteType writes a definition of the type of the given value and returns the
// type name.
func (w *CodeWriter) WriteType(x interface{}) string {
t := reflect.TypeOf(x)
w.printf("type %s struct {\n", t.Name())
for i := 0; i < t.NumField(); i++ {
w.printf("\t%s %s\n", t.Field(i).Name, t.Field(i).Type)
}
w.printf("}\n")
return t.Name()
}
// typeName returns the name of the go type of x.
func typeName(x interface{}) string {
t := reflect.ValueOf(x).Type()
return strings.Replace(fmt.Sprint(t), "main.", "", 1)
}

View file

@ -1,281 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package gen contains common code for the various code generation tools in the
// text repository. Its usage ensures consistency between tools.
//
// This package defines command line flags that are common to most generation
// tools. The flags allow for specifying specific Unicode and CLDR versions
// in the public Unicode data repository (http://www.unicode.org/Public).
//
// A local Unicode data mirror can be set through the flag -local or the
// environment variable UNICODE_DIR. The former takes precedence. The local
// directory should follow the same structure as the public repository.
//
// IANA data can also optionally be mirrored by putting it in the iana directory
// rooted at the top of the local mirror. Beware, though, that IANA data is not
// versioned. So it is up to the developer to use the right version.
package gen // import "golang.org/x/text/internal/gen"
import (
"bytes"
"flag"
"fmt"
"go/build"
"go/format"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path"
"path/filepath"
"sync"
"unicode"
"golang.org/x/text/unicode/cldr"
)
var (
url = flag.String("url",
"http://www.unicode.org/Public",
"URL of Unicode database directory")
iana = flag.String("iana",
"http://www.iana.org",
"URL of the IANA repository")
unicodeVersion = flag.String("unicode",
getEnv("UNICODE_VERSION", unicode.Version),
"unicode version to use")
cldrVersion = flag.String("cldr",
getEnv("CLDR_VERSION", cldr.Version),
"cldr version to use")
)
func getEnv(name, def string) string {
if v := os.Getenv(name); v != "" {
return v
}
return def
}
// Init performs common initialization for a gen command. It parses the flags
// and sets up the standard logging parameters.
func Init() {
log.SetPrefix("")
log.SetFlags(log.Lshortfile)
flag.Parse()
}
const header = `// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
package %s
`
// UnicodeVersion reports the requested Unicode version.
func UnicodeVersion() string {
return *unicodeVersion
}
// UnicodeVersion reports the requested CLDR version.
func CLDRVersion() string {
return *cldrVersion
}
// IsLocal reports whether data files are available locally.
func IsLocal() bool {
dir, err := localReadmeFile()
if err != nil {
return false
}
if _, err = os.Stat(dir); err != nil {
return false
}
return true
}
// OpenUCDFile opens the requested UCD file. The file is specified relative to
// the public Unicode root directory. It will call log.Fatal if there are any
// errors.
func OpenUCDFile(file string) io.ReadCloser {
return openUnicode(path.Join(*unicodeVersion, "ucd", file))
}
// OpenCLDRCoreZip opens the CLDR core zip file. It will call log.Fatal if there
// are any errors.
func OpenCLDRCoreZip() io.ReadCloser {
return OpenUnicodeFile("cldr", *cldrVersion, "core.zip")
}
// OpenUnicodeFile opens the requested file of the requested category from the
// root of the Unicode data archive. The file is specified relative to the
// public Unicode root directory. If version is "", it will use the default
// Unicode version. It will call log.Fatal if there are any errors.
func OpenUnicodeFile(category, version, file string) io.ReadCloser {
if version == "" {
version = UnicodeVersion()
}
return openUnicode(path.Join(category, version, file))
}
// OpenIANAFile opens the requested IANA file. The file is specified relative
// to the IANA root, which is typically either http://www.iana.org or the
// iana directory in the local mirror. It will call log.Fatal if there are any
// errors.
func OpenIANAFile(path string) io.ReadCloser {
return Open(*iana, "iana", path)
}
var (
dirMutex sync.Mutex
localDir string
)
const permissions = 0755
func localReadmeFile() (string, error) {
p, err := build.Import("golang.org/x/text", "", build.FindOnly)
if err != nil {
return "", fmt.Errorf("Could not locate package: %v", err)
}
return filepath.Join(p.Dir, "DATA", "README"), nil
}
func getLocalDir() string {
dirMutex.Lock()
defer dirMutex.Unlock()
readme, err := localReadmeFile()
if err != nil {
log.Fatal(err)
}
dir := filepath.Dir(readme)
if _, err := os.Stat(readme); err != nil {
if err := os.MkdirAll(dir, permissions); err != nil {
log.Fatalf("Could not create directory: %v", err)
}
ioutil.WriteFile(readme, []byte(readmeTxt), permissions)
}
return dir
}
const readmeTxt = `Generated by golang.org/x/text/internal/gen. DO NOT EDIT.
This directory contains downloaded files used to generate the various tables
in the golang.org/x/text subrepo.
Note that the language subtag repo (iana/assignments/language-subtag-registry)
and all other times in the iana subdirectory are not versioned and will need
to be periodically manually updated. The easiest way to do this is to remove
the entire iana directory. This is mostly of concern when updating the language
package.
`
// Open opens subdir/path if a local directory is specified and the file exists,
// where subdir is a directory relative to the local root, or fetches it from
// urlRoot/path otherwise. It will call log.Fatal if there are any errors.
func Open(urlRoot, subdir, path string) io.ReadCloser {
file := filepath.Join(getLocalDir(), subdir, filepath.FromSlash(path))
return open(file, urlRoot, path)
}
func openUnicode(path string) io.ReadCloser {
file := filepath.Join(getLocalDir(), filepath.FromSlash(path))
return open(file, *url, path)
}
// TODO: automatically periodically update non-versioned files.
func open(file, urlRoot, path string) io.ReadCloser {
if f, err := os.Open(file); err == nil {
return f
}
r := get(urlRoot, path)
defer r.Close()
b, err := ioutil.ReadAll(r)
if err != nil {
log.Fatalf("Could not download file: %v", err)
}
os.MkdirAll(filepath.Dir(file), permissions)
if err := ioutil.WriteFile(file, b, permissions); err != nil {
log.Fatalf("Could not create file: %v", err)
}
return ioutil.NopCloser(bytes.NewReader(b))
}
func get(root, path string) io.ReadCloser {
url := root + "/" + path
fmt.Printf("Fetching %s...", url)
defer fmt.Println(" done.")
resp, err := http.Get(url)
if err != nil {
log.Fatalf("HTTP GET: %v", err)
}
if resp.StatusCode != 200 {
log.Fatalf("Bad GET status for %q: %q", url, resp.Status)
}
return resp.Body
}
// TODO: use Write*Version in all applicable packages.
// WriteUnicodeVersion writes a constant for the Unicode version from which the
// tables are generated.
func WriteUnicodeVersion(w io.Writer) {
fmt.Fprintf(w, "// UnicodeVersion is the Unicode version from which the tables in this package are derived.\n")
fmt.Fprintf(w, "const UnicodeVersion = %q\n\n", UnicodeVersion())
}
// WriteCLDRVersion writes a constant for the CLDR version from which the
// tables are generated.
func WriteCLDRVersion(w io.Writer) {
fmt.Fprintf(w, "// CLDRVersion is the CLDR version from which the tables in this package are derived.\n")
fmt.Fprintf(w, "const CLDRVersion = %q\n\n", CLDRVersion())
}
// WriteGoFile prepends a standard file comment and package statement to the
// given bytes, applies gofmt, and writes them to a file with the given name.
// It will call log.Fatal if there are any errors.
func WriteGoFile(filename, pkg string, b []byte) {
w, err := os.Create(filename)
if err != nil {
log.Fatalf("Could not create file %s: %v", filename, err)
}
defer w.Close()
if _, err = WriteGo(w, pkg, b); err != nil {
log.Fatalf("Error writing file %s: %v", filename, err)
}
}
// WriteGo prepends a standard file comment and package statement to the given
// bytes, applies gofmt, and writes them to w.
func WriteGo(w io.Writer, pkg string, b []byte) (n int, err error) {
src := []byte(fmt.Sprintf(header, pkg))
src = append(src, b...)
formatted, err := format.Source(src)
if err != nil {
// Print the generated code even in case of an error so that the
// returned error can be meaningfully interpreted.
n, _ = w.Write(src)
return n, err
}
return w.Write(formatted)
}
// Repackage rewrites a Go file from belonging to package main to belonging to
// the given package.
func Repackage(inFile, outFile, pkg string) {
src, err := ioutil.ReadFile(inFile)
if err != nil {
log.Fatalf("reading %s: %v", inFile, err)
}
const toDelete = "package main\n\n"
i := bytes.Index(src, []byte(toDelete))
if i < 0 {
log.Fatalf("Could not find %q in %s.", toDelete, inFile)
}
w := &bytes.Buffer{}
w.Write(src[i+len(toDelete):])
WriteGoFile(outFile, pkg, w.Bytes())
}

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