initial commit

This commit is contained in:
Eric Chiang 2016-07-25 13:00:28 -07:00
commit cab271f304
1438 changed files with 335968 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
bin
dist

55
Makefile Normal file
View file

@ -0,0 +1,55 @@
PROJ="poke"
ORG_PATH="github.com/coreos"
REPO_PATH="$(ORG_PATH)/$(PROJ)"
export GOBIN=$(PWD)/bin
export GO15VENDOREXPERIMENT=1
GOOS=$(shell go env GOOS)
GOARCH=$(shell go env GOARCH)
COMMIT=$(shell git rev-parse HEAD)
# check if the current commit has a matching tag
TAG=$(shell git describe --exact-match --abbrev=0 --tags $(COMMIT) 2> /dev/null || true)
ifeq ($(TAG),)
VERSION=$(TAG)
else
VERSION=$(COMMIT)
endif
build: bin/poke bin/pokectl
bin/poke: FORCE
@go install $(REPO_PATH)/cmd/poke
bin/pokectl: FORCE
@go install $(REPO_PATH)/cmd/pokectl
test:
@go test -v $(shell go list ./... | grep -v '/vendor/')
testrace:
@go test -v --race $(shell go list ./... | grep -v '/vendor/')
vet:
@go vet $(shell go list ./... | grep -v '/vendor/')
fmt:
@go fmt $(shell go list ./... | grep -v '/vendor/')
lint:
@for package in $(shell go list ./... | grep -v '/vendor/'); do \
golint $$package; \
done
clean:
rm bin/poke bin/pokectl
testall: testrace vet fmt lint
FORCE:
.PHONY: test testrace vet fmt lint testall

126
cmd/poke/config.go Normal file
View file

@ -0,0 +1,126 @@
package main
import (
"fmt"
"github.com/coreos/poke/connector"
"github.com/coreos/poke/connector/github"
"github.com/coreos/poke/connector/ldap"
"github.com/coreos/poke/connector/mock"
"github.com/coreos/poke/storage"
"github.com/coreos/poke/storage/kubernetes"
)
// Config is the config format for the main application.
type Config struct {
Issuer string `yaml:"issuer"`
Storage Storage `yaml:"storage"`
Connectors []Connector `yaml:"connectors"`
Web Web `yaml:"web"`
}
// Web is the config format for the HTTP server.
type Web struct {
HTTP string `yaml:"http"`
HTTPS string `yaml:"https"`
TLSCert string `yaml:"tlsCert"`
TLSKey string `yaml:"tlsKey"`
}
// Storage holds app's storage configuration.
type Storage struct {
Type string `yaml:"type"`
Config StorageConfig `yaml:"config"`
}
// UnmarshalYAML allows Storage to unmarshal its config field dynamically
// depending on the type of storage.
func (s *Storage) UnmarshalYAML(unmarshal func(interface{}) error) error {
var storageMeta struct {
Type string `yaml:"type"`
}
if err := unmarshal(&storageMeta); err != nil {
return err
}
s.Type = storageMeta.Type
var c struct {
Config StorageConfig `yaml:"config"`
}
switch storageMeta.Type {
case "kubernetes":
c.Config = &kubernetes.Config{}
default:
return fmt.Errorf("unknown storage type %q", storageMeta.Type)
}
if err := unmarshal(c); err != nil {
return err
}
s.Config = c.Config
return nil
}
// StorageConfig is a configuration that can create a storage.
type StorageConfig interface {
Open() (storage.Storage, error)
}
// Connector is a magical type that can unmarshal YAML dynamically. The
// Type field determines the connector type, which is then customized for Config.
type Connector struct {
Type string `yaml:"type"`
Name string `yaml:"name"`
ID string `yaml:"id"`
Config ConnectorConfig `yaml:"config"`
}
// ConnectorConfig is a configuration that can open a connector.
type ConnectorConfig interface {
Open() (connector.Connector, error)
}
// UnmarshalYAML allows Connector to unmarshal its config field dynamically
// depending on the type of connector.
func (c *Connector) UnmarshalYAML(unmarshal func(interface{}) error) error {
var connectorMetadata struct {
Type string `yaml:"type"`
Name string `yaml:"name"`
ID string `yaml:"id"`
}
if err := unmarshal(&connectorMetadata); err != nil {
return err
}
c.Type = connectorMetadata.Type
c.Name = connectorMetadata.Name
c.ID = connectorMetadata.ID
switch c.Type {
case "mock":
var config struct {
Config mock.Config `yaml:"config"`
}
if err := unmarshal(&config); err != nil {
return err
}
c.Config = &config.Config
case "ldap":
var config struct {
Config ldap.Config `yaml:"config"`
}
if err := unmarshal(&config); err != nil {
return err
}
c.Config = &config.Config
case "github":
var config struct {
Config github.Config `yaml:"config"`
}
if err := unmarshal(&config); err != nil {
return err
}
c.Config = &config.Config
default:
return fmt.Errorf("unknown connector type %q", c.Type)
}
return nil
}

1
cmd/poke/init.go Normal file
View file

@ -0,0 +1 @@
package main

28
cmd/poke/poke.go Normal file
View file

@ -0,0 +1,28 @@
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
func commandRoot() *cobra.Command {
rootCmd := &cobra.Command{
Use: "poke",
Run: func(cmd *cobra.Command, args []string) {
cmd.Help()
os.Exit(2)
},
}
rootCmd.AddCommand(commandServe())
rootCmd.AddCommand(commandVersion())
return rootCmd
}
func main() {
if err := commandRoot().Execute(); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(2)
}
}

111
cmd/poke/serve.go Normal file
View file

@ -0,0 +1,111 @@
package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
yaml "gopkg.in/yaml.v2"
"github.com/coreos/poke/server"
"github.com/spf13/cobra"
)
func commandServe() *cobra.Command {
return &cobra.Command{
Use: "serve [ config file ]",
Short: "Connect to the storage and begin serving requests.",
Long: ``,
Example: "poke serve c.yaml",
RunE: serve,
}
}
func serve(cmd *cobra.Command, args []string) error {
switch len(args) {
default:
return errors.New("surplus arguments")
case 0:
// TODO(ericchiang): Consider having a default config file location.
return errors.New("no config file specified")
case 1:
}
configFile := args[0]
configData, err := ioutil.ReadFile(configFile)
if err != nil {
return fmt.Errorf("read config file %s: %v", configFile, err)
}
var c Config
if err := yaml.Unmarshal(configData, &c); err != nil {
return fmt.Errorf("parse config file %s: %v", configFile, err)
}
// Fast checks. Perform these first for a more responsive CLI.
checks := []struct {
bad bool
errMsg string
}{
{c.Issuer == "", "no issuer specified in config file"},
{len(c.Connectors) == 0, "no connectors supplied in config file"},
{c.Storage.Config == nil, "no storage suppied in config file"},
{c.Web.HTTP == "" && c.Web.HTTPS == "", "must supply a HTTP/HTTPS address to listen on"},
{c.Web.HTTPS != "" && c.Web.TLSCert == "", "no cert specified for HTTPS"},
{c.Web.HTTPS != "" && c.Web.TLSKey == "", "no private key specified for HTTPS"},
}
for _, check := range checks {
if check.bad {
return errors.New(check.errMsg)
}
}
connectors := make([]server.Connector, len(c.Connectors))
for i, conn := range c.Connectors {
if conn.Config == nil {
return fmt.Errorf("no config field for connector %q", conn.ID)
}
c, err := conn.Config.Open()
if err != nil {
return fmt.Errorf("open %s: %v", conn.ID, err)
}
connectors[i] = server.Connector{
ID: conn.ID,
DisplayName: conn.Name,
Connector: c,
}
}
s, err := c.Storage.Config.Open()
if err != nil {
return fmt.Errorf("initializing storage: %v", err)
}
serverConfig := server.Config{
Issuer: c.Issuer,
Connectors: connectors,
Storage: s,
}
serv, err := server.New(serverConfig)
if err != nil {
return fmt.Errorf("initializing server: %v", err)
}
errc := make(chan error, 2)
if c.Web.HTTP != "" {
go func() {
log.Printf("listening on %s", c.Web.HTTP)
errc <- http.ListenAndServe(c.Web.HTTP, serv)
}()
}
if c.Web.HTTPS != "" {
go func() {
log.Printf("listening on %s", c.Web.HTTPS)
errc <- http.ListenAndServeTLS(c.Web.HTTPS, c.Web.TLSCert, c.Web.TLSKey, serv)
}()
}
return <-errc
}

19
cmd/poke/version.go Normal file
View file

@ -0,0 +1,19 @@
package main
import (
"fmt"
"runtime"
"github.com/coreos/poke/version"
"github.com/spf13/cobra"
)
func commandVersion() *cobra.Command {
return &cobra.Command{
Use: "version",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf(`v%s %s %s %s
`, version.Version, runtime.Version(), runtime.GOOS, runtime.GOARCH)
},
}
}

24
cmd/pokectl/pokectl.go Normal file
View file

@ -0,0 +1,24 @@
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "pokectl",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
func init() {}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
}

32
connector/connector.go Normal file
View file

@ -0,0 +1,32 @@
// Package connector defines interfaces for federated identity strategies.
package connector
import (
"net/http"
"github.com/coreos/poke/storage"
)
// Connector is a mechanism for federating login to a remote identity service.
//
// Implementations are expected to implement either the PasswordConnector or
// CallbackConnector interface.
type Connector interface {
Close() error
}
// PasswordConnector is an optional interface for password based connectors.
type PasswordConnector interface {
Login(username, password string) (identity storage.Identity, validPassword bool, err error)
}
// CallbackConnector is an optional interface for callback based connectors.
type CallbackConnector interface {
LoginURL(callbackURL, state string) (string, error)
HandleCallback(r *http.Request) (identity storage.Identity, state string, err error)
}
// GroupsConnector is an optional interface for connectors which can map a user to groups.
type GroupsConnector interface {
Groups(identity storage.Identity) ([]string, error)
}

177
connector/github/github.go Normal file
View file

@ -0,0 +1,177 @@
// Package github provides authentication strategies using GitHub.
package github
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"strconv"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
"github.com/coreos/poke/connector"
"github.com/coreos/poke/storage"
)
const baseURL = "https://api.github.com"
// Config holds configuration options for github logins.
type Config struct {
ClientID string `yaml:"clientID"`
ClientSecret string `yaml:"clientSecret"`
RedirectURI string `yaml:"redirectURI"`
Org string `yaml:"org"`
}
// Open returns a strategy for logging in through GitHub.
func (c *Config) Open() (connector.Connector, error) {
return &githubConnector{
redirectURI: c.RedirectURI,
org: c.Org,
oauth2Config: &oauth2.Config{
ClientID: os.ExpandEnv(c.ClientID),
ClientSecret: os.ExpandEnv(c.ClientSecret),
Endpoint: github.Endpoint,
Scopes: []string{
"user:email", // View user's email
"read:org", // View user's org teams.
},
},
}, nil
}
type connectorData struct {
// GitHub's OAuth2 tokens never expire. We don't need a refresh token.
AccessToken string `json:"accessToken"`
}
var (
_ connector.CallbackConnector = (*githubConnector)(nil)
_ connector.GroupsConnector = (*githubConnector)(nil)
)
type githubConnector struct {
redirectURI string
org string
oauth2Config *oauth2.Config
ctx context.Context
cancel context.CancelFunc
}
func (c *githubConnector) Close() error {
return nil
}
func (c *githubConnector) LoginURL(callbackURL, state string) (string, error) {
if c.redirectURI != callbackURL {
return "", fmt.Errorf("expected callback URL did not match the URL in the config")
}
return c.oauth2Config.AuthCodeURL(state), nil
}
type oauth2Error struct {
error string
errorDescription string
}
func (e *oauth2Error) Error() string {
if e.errorDescription == "" {
return e.error
}
return e.error + ": " + e.errorDescription
}
func (c *githubConnector) HandleCallback(r *http.Request) (identity storage.Identity, state string, err error) {
q := r.URL.Query()
if errType := q.Get("error"); errType != "" {
return identity, "", &oauth2Error{errType, q.Get("error_description")}
}
token, err := c.oauth2Config.Exchange(c.ctx, q.Get("code"))
if err != nil {
return identity, "", fmt.Errorf("github: failed to get token: %v", err)
}
resp, err := c.oauth2Config.Client(c.ctx, token).Get(baseURL + "/user")
if err != nil {
return identity, "", fmt.Errorf("github: get URL %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return identity, "", fmt.Errorf("github: read body: %v", err)
}
return identity, "", fmt.Errorf("%s: %s", resp.Status, body)
}
var user struct {
Name string `json:"name"`
Login string `json:"login"`
ID int `json:"id"`
Email string `json:"email"`
}
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
return identity, "", fmt.Errorf("failed to decode response: %v", err)
}
data := connectorData{AccessToken: token.AccessToken}
connData, err := json.Marshal(data)
if err != nil {
return identity, "", fmt.Errorf("marshal connector data: %v", err)
}
username := user.Name
if username == "" {
username = user.Login
}
identity = storage.Identity{
UserID: strconv.Itoa(user.ID),
Username: username,
Email: user.Email,
EmailVerified: true,
ConnectorData: connData,
}
return identity, q.Get("state"), nil
}
func (c *githubConnector) Groups(identity storage.Identity) ([]string, error) {
var data connectorData
if err := json.Unmarshal(identity.ConnectorData, &data); err != nil {
return nil, fmt.Errorf("decode connector data: %v", err)
}
token := &oauth2.Token{AccessToken: data.AccessToken}
resp, err := c.oauth2Config.Client(c.ctx, token).Get(baseURL + "/user/teams")
if err != nil {
return nil, fmt.Errorf("github: get teams: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("github: read body: %v", err)
}
return nil, fmt.Errorf("%s: %s", resp.Status, body)
}
// https://developer.github.com/v3/orgs/teams/#response-12
var teams []struct {
Name string `json:"name"`
Org struct {
Login string `json:"login"`
} `json:"organization"`
}
if err := json.NewDecoder(resp.Body).Decode(&teams); err != nil {
return nil, fmt.Errorf("github: unmarshal groups: %v", err)
}
groups := []string{}
for _, team := range teams {
if team.Org.Login == c.org {
groups = append(groups, team.Name)
}
}
return groups, nil
}

59
connector/ldap/ldap.go Normal file
View file

@ -0,0 +1,59 @@
// Package ldap implements strategies for authenticating using the LDAP protocol.
package ldap
import (
"errors"
"fmt"
"gopkg.in/ldap.v2"
"github.com/coreos/poke/connector"
"github.com/coreos/poke/storage"
)
// Config holds the configuration parameters for the LDAP connector.
type Config struct {
Host string `yaml:"host"`
BindDN string `yaml:"bindDN"`
}
// Open returns an authentication strategy using LDAP.
func (c *Config) Open() (connector.Connector, error) {
if c.Host == "" {
return nil, errors.New("missing host parameter")
}
if c.BindDN == "" {
return nil, errors.New("missing bindDN paramater")
}
return &ldapConnector{*c}, nil
}
type ldapConnector struct {
Config
}
func (c *ldapConnector) do(f func(c *ldap.Conn) error) error {
// TODO(ericchiang): Connection pooling.
conn, err := ldap.Dial("tcp", c.Host)
if err != nil {
return fmt.Errorf("failed to connect: %v", err)
}
defer conn.Close()
return f(conn)
}
func (c *ldapConnector) Login(username, password string) (storage.Identity, error) {
err := c.do(func(conn *ldap.Conn) error {
return conn.Bind(fmt.Sprintf("uid=%s,%s", username, c.BindDN), password)
})
if err != nil {
return storage.Identity{}, err
}
return storage.Identity{Username: username}, nil
}
func (c *ldapConnector) Close() error {
return nil
}

View file

@ -0,0 +1,53 @@
// Package mock implements a mock connector which requires no user interaction.
package mock
import (
"fmt"
"net/http"
"net/url"
"github.com/coreos/poke/connector"
"github.com/coreos/poke/storage"
)
// New returns a mock connector which requires no user interaction. It always returns
// the same (fake) identity.
func New() connector.Connector {
return mockConnector{}
}
type mockConnector struct{}
func (m mockConnector) Close() error { return nil }
func (m mockConnector) LoginURL(callbackURL, state string) (string, error) {
u, err := url.Parse(callbackURL)
if err != nil {
return "", fmt.Errorf("failed to parse callbackURL %q: %v", callbackURL, err)
}
v := u.Query()
v.Set("state", state)
u.RawQuery = v.Encode()
return u.String(), nil
}
func (m mockConnector) HandleCallback(r *http.Request) (storage.Identity, string, error) {
return storage.Identity{
UserID: "0-385-28089-0",
Username: "Kilgore Trout",
Email: "kilgore@kilgore.trout",
EmailVerified: true,
}, r.URL.Query().Get("state"), nil
}
func (m mockConnector) Groups(identity storage.Identity) ([]string, error) {
return []string{"authors"}, nil
}
// Config holds the configuration parameters for the mock connector.
type Config struct{}
// Open returns an authentication strategy which requires no user interaction.
func (c *Config) Open() (connector.Connector, error) {
return New(), nil
}

2
connector/oidc/oidc.go Normal file
View file

@ -0,0 +1,2 @@
// Package oidc implements logging in through OpenID Connect providers.
package oidc

9
example/client.yaml Normal file
View file

@ -0,0 +1,9 @@
kind: OAuth2Client
apiVersion: oauth2clients.oidc.coreos.com/v1
metadata:
name: example-app
namespace: default
secret: ZXhhbXBsZS1hcHAtc2VjcmV0
redirectURIs:
- http://127.0.0.1:5555/callback
name: Example App

20
example/config.yaml Normal file
View file

@ -0,0 +1,20 @@
issuer: http://127.0.0.1:5556
storage:
type: kubernetes
web:
http: 127.0.0.1:5556
connectors:
- type: mock
id: mock
name: Mock
- type: github
id: github
name: GitHub
config:
clientID: "$GITHUB_CLIENT_ID"
clientSecret: "$GITHUB_CLIENT_SECRET"
redirectURI: http://127.0.0.1:5556/callback/github
org: kubernetes

View file

@ -0,0 +1,48 @@
# NOTE: Because of a bug in third party resources, each resource must be in it's
# own API Group.
#
# See fix at https://github.com/kubernetes/kubernetes/pull/28414
metadata:
name: auth-code.authcodes.oidc.coreos.com
apiVersion: extensions/v1beta1
kind: ThirdPartyResource
description: "A code which can be claimed for an access token."
versions:
- name: v1
---
metadata:
name: auth-request.authrequests.oidc.coreos.com
apiVersion: extensions/v1beta1
kind: ThirdPartyResource
description: "A request for an end user to authorize a client."
versions:
- name: v1
---
metadata:
name: o-auth2-client.oauth2clients.oidc.coreos.com
apiVersion: extensions/v1beta1
kind: ThirdPartyResource
description: "An OpenID Connect client."
versions:
- name: v1
---
metadata:
name: signing-key.signingkeies.oidc.coreos.com
apiVersion: extensions/v1beta1
kind: ThirdPartyResource
description: "Keys used to sign and verify OpenID Connect tokens."
versions:
- name: v1
---
metadata:
name: refresh-token.refreshtokens.oidc.coreos.com
apiVersion: extensions/v1beta1
kind: ThirdPartyResource
description: "Refresh tokens for clients to continuously act on behalf of an end user."
versions:
- name: v1

67
glide.lock generated Normal file
View file

@ -0,0 +1,67 @@
hash: 8b33b8abf5bca183ffa7108b03f4001f4d23f7f2e26f773f5bcb4bfefa51a26f
updated: 2016-07-22T23:25:33.173188655-07:00
imports:
- name: github.com/ericchiang/oidc
version: 69fec81d167d815f4f455c741b2a94ffaf547ed2
- name: github.com/golang/protobuf
version: 874264fbbb43f4d91e999fecb4b40143ed611400
subpackages:
- proto
- name: github.com/gorilla/context
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
- name: github.com/gorilla/mux
version: 9fa818a44c2bf1396a17f9d5a3c0f6dd39d2ff8e
- name: github.com/gtank/cryptopasta
version: e7e23673cac3f529f49e22f94e4af6d12bb49dba
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/mitchellh/go-homedir
version: 756f7b183b7ab78acdbbee5c7f392838ed459dda
- name: github.com/pquerna/cachecontrol
version: c97913dcbd76de40b051a9b4cd827f7eaeb7a868
subpackages:
- cacheobject
- name: github.com/spf13/cobra
version: bc81c21bd0d8be5ba2d6630a505d79d4467566e7
- name: github.com/spf13/pflag
version: 367864438f1b1a3c7db4da06a2f55b144e6784e0
- name: golang.org/x/crypto
version: 2c99acdd1e9b90d779ca23f632aad86af9909c62
subpackages:
- bcrypt
- blowfish
- name: golang.org/x/net
version: d7bf3545bb0dacf009c535b3d3fbf53ac0a339ab
subpackages:
- context
- name: golang.org/x/oauth2
version: 08c8d727d2392d18286f9f88ad775ad98f09ab33
subpackages:
- internal
- name: google.golang.org/appengine
version: 267c27e7492265b84fc6719503b14a1e17975d79
subpackages:
- urlfetch
- internal
- internal/urlfetch
- internal/base
- internal/datastore
- internal/log
- internal/remote_api
- name: gopkg.in/asn1-ber.v1
version: 4e86f4367175e39f69d9358a5f17b4dda270378d
- name: gopkg.in/ldap.v2
version: 0e7db8eb77695b5a952f0e5d78df9ab160050c73
- name: gopkg.in/square/go-jose.v1
version: e3f973b66b91445ec816dd7411ad1b6495a5a2fc
subpackages:
- cipher
- json
- name: gopkg.in/square/go-jose.v2
version: f209f41628247c56938cb20ef51d589ddad6c30b
subpackages:
- cipher
- json
- name: gopkg.in/yaml.v2
version: a83829b6f1293c91addabc89d0571c246397bbf4
testImports: []

63
glide.yaml Normal file
View file

@ -0,0 +1,63 @@
package: github.com/coreos/poke
import:
- package: github.com/spf13/cobra
version: bc81c21bd0d8be5ba2d6630a505d79d4467566e7
- package: github.com/spf13/pflag
version: 367864438f1b1a3c7db4da06a2f55b144e6784e0
- package: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- package: gopkg.in/ldap.v2
version: 0e7db8eb77695b5a952f0e5d78df9ab160050c73
- package: gopkg.in/asn1-ber.v1
version: 4e86f4367175e39f69d9358a5f17b4dda270378d
- package: gopkg.in/square/go-jose.v2
version: f209f41628247c56938cb20ef51d589ddad6c30b
subpackages:
- cipher
- json
- package: gopkg.in/yaml.v2
version: a83829b6f1293c91addabc89d0571c246397bbf4
- package: golang.org/x/net/context
version: d7bf3545bb0dacf009c535b3d3fbf53ac0a339ab
- package: github.com/gorilla/mux
version: 9fa818a44c2bf1396a17f9d5a3c0f6dd39d2ff8e
- package: github.com/gorilla/context
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
- package: github.com/gtank/cryptopasta
version: e7e23673cac3f529f49e22f94e4af6d12bb49dba
- package: golang.org/x/crypto
version: 2c99acdd1e9b90d779ca23f632aad86af9909c62
subpackages:
- bcrypt
- package: github.com/ericchiang/oidc
version: 69fec81d167d815f4f455c741b2a94ffaf547ed2
- package: github.com/pquerna/cachecontrol
version: c97913dcbd76de40b051a9b4cd827f7eaeb7a868
- package: gopkg.in/square/go-jose.v1
version: v1.0.2
- package: golang.org/x/oauth2
version: 08c8d727d2392d18286f9f88ad775ad98f09ab33
- package: google.golang.org/appengine
version: 267c27e7492265b84fc6719503b14a1e17975d79
subpackages:
- urlfetch
- internal
- internal/urlfetch
- internal/base
- internal/datastore
- internal/log
- internal/remote_api
- package: github.com/golang/protobuf
version: 874264fbbb43f4d91e999fecb4b40143ed611400
subpackages:
- proto
- package: github.com/mitchellh/go-homedir
verison: 756f7b183b7ab78acdbbee5c7f392838ed459dda

112
glide_test.go Normal file
View file

@ -0,0 +1,112 @@
package main
import (
"io/ioutil"
"os"
"path"
"path/filepath"
"testing"
"gopkg.in/yaml.v2"
)
type glideLock struct {
Imports []struct {
Name string `yaml:"name"`
Subpackages []string `yaml:"subpackages,omitempty"`
} `yaml:"imports"`
TestImports []struct {
Name string `yaml:"name"`
Subpackages []string `yaml:"subpackages,omitempty"`
} `yaml:"testImports"`
}
type glideYAML struct {
Imports []struct {
Name string `yaml:"package"`
} `yaml:"import"`
}
func loadYAML(t *testing.T, file string, v interface{}) {
data, err := ioutil.ReadFile(file)
if err != nil {
t.Fatalf("read file %s: %v", file, err)
}
if err := yaml.Unmarshal(data, v); err != nil {
t.Fatalf("unmarshal file %s: %v", file, err)
}
return
}
// TestGlideYAMLPinsAllDependencies ensures that all packages listed in glide.lock also
// appear in glide.yaml which can get out of sync if glide.yaml fails to list transitive
// dependencies.
//
// Testing this ensures developers can update individual packages without grabbing the HEAD
// of an unspecified dependency.
func TestGlideYAMLPinsAllDependencies(t *testing.T) {
var (
lockPackages glideLock
yamlPackages glideYAML
)
loadYAML(t, "glide.lock", &lockPackages)
loadYAML(t, "glide.yaml", &yamlPackages)
if len(yamlPackages.Imports) == 0 {
t.Fatalf("no packages found in glide.yaml")
}
pkgs := make(map[string]bool)
for _, pkg := range yamlPackages.Imports {
pkgs[pkg.Name] = true
}
for _, pkg := range lockPackages.Imports {
if pkgs[pkg.Name] {
continue
}
if len(pkg.Subpackages) == 0 {
t.Errorf("package in glide lock but not pinned in glide yaml: %s", pkg.Name)
continue
}
for _, subpkg := range pkg.Subpackages {
pkgName := path.Join(pkg.Name, subpkg)
if !pkgs[pkgName] {
t.Errorf("package in glide lock but not pinned in glide yaml: %s", pkgName)
}
}
}
for _, pkg := range lockPackages.TestImports {
if pkgs[pkg.Name] {
continue
}
if len(pkg.Subpackages) == 0 {
t.Errorf("package in glide lock but not pinned in glide yaml: %s", pkg.Name)
continue
}
for _, subpkg := range pkg.Subpackages {
pkgName := path.Join(pkg.Name, subpkg)
if !pkgs[pkgName] {
t.Errorf("package in glide lock but not pinned in glide yaml: %s", pkgName)
}
}
}
}
func TestRemoveVersionControl(t *testing.T) {
err := filepath.Walk("vendor", func(path string, info os.FileInfo, err error) error {
if err != nil {
t.Fatalf("walk: stat path %s failed: %v", path, err)
}
if info.IsDir() && filepath.Base(path) == ".git" {
t.Fatalf(".git directory detected in vendor: %s. Revendor packages and remove version control data with 'glide update -s -v -u'", path)
}
return nil
})
if err != nil {
t.Fatalf("walk: %v", err)
}
}

2
server/doc.go Normal file
View file

@ -0,0 +1,2 @@
// Package server implements an OpenID Connect server with federated logins.
package server

556
server/handlers.go Normal file
View file

@ -0,0 +1,556 @@
package server
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
jose "gopkg.in/square/go-jose.v2"
"github.com/coreos/poke/connector"
"github.com/coreos/poke/storage"
)
func (s *Server) handlePublicKeys(w http.ResponseWriter, r *http.Request) {
// TODO(ericchiang): Cache this.
keys, err := s.storage.GetKeys()
if err != nil {
log.Printf("failed to get keys: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
if keys.SigningKeyPub == nil {
log.Printf("No public keys found.")
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
jwks := jose.JSONWebKeySet{
Keys: make([]jose.JSONWebKey, len(keys.VerificationKeys)+1),
}
jwks.Keys[0] = *keys.SigningKeyPub
for i, verificationKey := range keys.VerificationKeys {
jwks.Keys[i+1] = *verificationKey.PublicKey
}
data, err := json.MarshalIndent(jwks, "", " ")
if err != nil {
log.Printf("failed to marshal discovery data: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
maxAge := keys.NextRotation.Sub(s.now())
if maxAge < (time.Minute * 2) {
maxAge = time.Minute * 2
}
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d, must-revalidate", maxAge))
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
w.Write(data)
}
type discovery struct {
Issuer string `json:"issuer"`
Auth string `json:"authorization_endpoint"`
Token string `json:"token_endpoint"`
Keys string `json:"jwks_uri"`
ResponseTypes []string `json:"response_types_supported"`
Subjects []string `json:"subject_types_supported"`
IDTokenAlgs []string `json:"id_token_signing_alg_values_supported"`
Scopes []string `json:"scopes_supported"`
AuthMethods []string `json:"token_endpoint_auth_methods_supported"`
Claims []string `json:"claims_supported"`
}
func (s *Server) handleDiscovery(w http.ResponseWriter, r *http.Request) {
// TODO(ericchiang): Cache this
d := discovery{
Issuer: s.issuerURL.String(),
Auth: s.absURL("/auth"),
Token: s.absURL("/token"),
Keys: s.absURL("/keys"),
ResponseTypes: []string{"code"},
Subjects: []string{"public"},
IDTokenAlgs: []string{string(jose.RS256)},
Scopes: []string{"openid", "email", "profile"},
AuthMethods: []string{"client_secret_basic"},
Claims: []string{
"aud", "email", "email_verified", "exp", "family_name", "given_name",
"iat", "iss", "locale", "name", "sub",
},
}
data, err := json.MarshalIndent(d, "", " ")
if err != nil {
log.Printf("failed to marshal discovery data: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
w.Write(data)
}
// handleAuthorization handles the OAuth2 auth endpoint.
func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
authReq, err := parseAuthorizationRequest(s.storage, r)
if err != nil {
s.renderError(w, http.StatusInternalServerError, err.Type, err.Description)
return
}
if err := s.storage.CreateAuthRequest(authReq); err != nil {
log.Printf("Failed to create authorization request: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "")
return
}
state := authReq.ID
if len(s.connectors) == 1 {
for id := range s.connectors {
http.Redirect(w, r, s.absPath("/auth", id)+"?state="+state, http.StatusFound)
return
}
}
connectorInfos := make([]connectorInfo, len(s.connectors))
i := 0
for id := range s.connectors {
connectorInfos[i] = connectorInfo{
DisplayName: id,
URL: s.absPath("/auth", id) + "?state=" + state,
}
i++
}
renderLoginOptions(w, connectorInfos, state)
}
func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
connID := mux.Vars(r)["connector"]
conn, ok := s.connectors[connID]
if !ok {
s.notFound(w, r)
return
}
// TODO(ericchiang): cache user identity.
state := r.FormValue("state")
switch r.Method {
case "GET":
switch conn := conn.Connector.(type) {
case connector.CallbackConnector:
callbackURL, err := conn.LoginURL(s.absURL("/callback", connID), state)
if err != nil {
log.Printf("Connector %q returned error when creating callback: %v", connID, err)
s.renderError(w, http.StatusInternalServerError, errServerError, "")
return
}
http.Redirect(w, r, callbackURL, http.StatusFound)
case connector.PasswordConnector:
renderPasswordTmpl(w, state, r.URL.String(), "")
default:
s.notFound(w, r)
}
case "POST":
passwordConnector, ok := conn.Connector.(connector.PasswordConnector)
if !ok {
s.notFound(w, r)
return
}
username := r.FormValue("username")
password := r.FormValue("password")
identity, ok, err := passwordConnector.Login(username, password)
if err != nil {
log.Printf("Failed to login user: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "")
return
}
if !ok {
renderPasswordTmpl(w, state, r.URL.String(), "Invalid credentials")
return
}
groups, ok, err := s.groups(identity, state, conn.Connector)
if err != nil {
s.renderError(w, http.StatusInternalServerError, errServerError, "")
return
}
if ok {
identity.Groups = groups
}
s.redirectToApproval(w, r, identity, connID, state)
default:
s.notFound(w, r)
}
}
func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) {
connID := mux.Vars(r)["connector"]
conn, ok := s.connectors[connID]
if !ok {
s.notFound(w, r)
return
}
callbackConnector, ok := conn.Connector.(connector.CallbackConnector)
if !ok {
s.notFound(w, r)
return
}
identity, state, err := callbackConnector.HandleCallback(r)
if err != nil {
log.Printf("Failed to authenticate: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "")
return
}
groups, ok, err := s.groups(identity, state, conn.Connector)
if err != nil {
s.renderError(w, http.StatusInternalServerError, errServerError, "")
return
}
if ok {
identity.Groups = groups
}
s.redirectToApproval(w, r, identity, connID, state)
}
func (s *Server) redirectToApproval(w http.ResponseWriter, r *http.Request, identity storage.Identity, connectorID, state string) {
updater := func(a storage.AuthRequest) (storage.AuthRequest, error) {
a.Identity = &identity
a.ConnectorID = connectorID
return a, nil
}
if err := s.storage.UpdateAuthRequest(state, updater); err != nil {
log.Printf("Failed to updated auth request with identity: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "")
return
}
http.Redirect(w, r, path.Join(s.issuerURL.Path, "/approval")+"?state="+state, http.StatusSeeOther)
}
func (s *Server) groups(identity storage.Identity, authReqID string, conn connector.Connector) ([]string, bool, error) {
groupsConn, ok := conn.(connector.GroupsConnector)
if !ok {
return nil, false, nil
}
authReq, err := s.storage.GetAuthRequest(authReqID)
if err != nil {
log.Printf("get auth request: %v", err)
return nil, false, err
}
reqGroups := func() bool {
for _, scope := range authReq.Scopes {
if scope == scopeGroups {
return true
}
}
return false
}()
if !reqGroups {
return nil, false, nil
}
groups, err := groupsConn.Groups(identity)
return groups, true, err
}
func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) {
authReq, err := s.storage.GetAuthRequest(r.FormValue("state"))
if err != nil {
log.Printf("Failed to get auth request: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "")
return
}
if authReq.Identity == nil {
log.Printf("Auth request does not have an identity for approval")
s.renderError(w, http.StatusInternalServerError, errServerError, "")
return
}
switch r.Method {
case "GET":
if s.skipApproval {
s.sendCodeResponse(w, r, authReq, *authReq.Identity)
return
}
client, err := s.storage.GetClient(authReq.ClientID)
if err != nil {
log.Printf("Failed to get client %q: %v", authReq.ClientID, err)
s.renderError(w, http.StatusInternalServerError, errServerError, "")
return
}
renderApprovalTmpl(w, authReq.ID, *authReq.Identity, client, authReq.Scopes)
case "POST":
if r.FormValue("approval") != "approve" {
s.renderError(w, http.StatusInternalServerError, "approval rejected", "")
return
}
s.sendCodeResponse(w, r, authReq, *authReq.Identity)
}
}
func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authReq storage.AuthRequest, identity storage.Identity) {
if authReq.Expiry.After(s.now()) {
s.renderError(w, http.StatusBadRequest, errInvalidRequest, "Authorization request period has expired.")
return
}
if err := s.storage.DeleteAuthRequest(authReq.ID); err != nil {
if err != storage.ErrNotFound {
log.Printf("Failed to delete authorization request: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "")
} else {
s.renderError(w, http.StatusBadRequest, errInvalidRequest, "Authorization request has already been completed.")
}
return
}
code := storage.AuthCode{
ID: storage.NewNonce(),
ClientID: authReq.ClientID,
ConnectorID: authReq.ConnectorID,
Nonce: authReq.Nonce,
Scopes: authReq.Scopes,
Identity: *authReq.Identity,
Expiry: s.now().Add(time.Minute * 5),
RedirectURI: authReq.RedirectURI,
}
if err := s.storage.CreateAuthCode(code); err != nil {
log.Printf("Failed to create auth code: %v", err)
s.renderError(w, http.StatusInternalServerError, errServerError, "")
return
}
if authReq.RedirectURI == "urn:ietf:wg:oauth:2.0:oob" {
// TODO(ericchiang): Add a proper template.
fmt.Fprintf(w, "Code: %s", code.ID)
return
}
u, err := url.Parse(authReq.RedirectURI)
if err != nil {
s.renderError(w, http.StatusInternalServerError, errServerError, "Invalid redirect URI.")
return
}
q := u.Query()
q.Set("code", code.ID)
q.Set("state", authReq.State)
u.RawQuery = q.Encode()
http.Redirect(w, r, u.String(), http.StatusSeeOther)
}
func (s *Server) handleToken(w http.ResponseWriter, r *http.Request) {
clientID, clientSecret, ok := r.BasicAuth()
if ok {
var err error
if clientID, err = url.QueryUnescape(clientID); err != nil {
tokenErr(w, errInvalidRequest, "client_id improperly encoded", http.StatusBadRequest)
return
}
if clientSecret, err = url.QueryUnescape(clientSecret); err != nil {
tokenErr(w, errInvalidRequest, "client_secret improperly encoded", http.StatusBadRequest)
return
}
} else {
clientID = r.PostFormValue("client_id")
clientSecret = r.PostFormValue("client_secret")
}
client, err := s.storage.GetClient(clientID)
if err != nil {
if err != storage.ErrNotFound {
log.Printf("failed to get client: %v", err)
tokenErr(w, errServerError, "", http.StatusInternalServerError)
} else {
tokenErr(w, errInvalidClient, "Invalid client credentials.", http.StatusUnauthorized)
}
return
}
if client.Secret != clientSecret {
tokenErr(w, errInvalidClient, "Invalid client credentials.", http.StatusUnauthorized)
return
}
grantType := r.PostFormValue("grant_type")
switch grantType {
case "authorization_code":
s.handleAuthCode(w, r, client)
case "refresh_token":
s.handleRefreshToken(w, r, client)
default:
tokenErr(w, errInvalidGrant, "", http.StatusBadRequest)
}
}
// handle an access token request https://tools.ietf.org/html/rfc6749#section-4.1.3
func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client storage.Client) {
code := r.PostFormValue("code")
redirectURI := r.PostFormValue("redirect_uri")
authCode, err := s.storage.GetAuthCode(code)
if err != nil || s.now().After(authCode.Expiry) || authCode.ClientID != client.ID {
if err != storage.ErrNotFound {
log.Printf("failed to get auth code: %v", err)
tokenErr(w, errServerError, "", http.StatusInternalServerError)
} else {
tokenErr(w, errInvalidRequest, "Invalid or expired code parameter.", http.StatusBadRequest)
}
return
}
if authCode.RedirectURI != redirectURI {
tokenErr(w, errInvalidRequest, "redirect_uri did not match URI from initial request.", http.StatusBadRequest)
return
}
idToken, expiry, err := s.newIDToken(client.ID, authCode.Identity, authCode.Scopes, authCode.Nonce)
if err != nil {
log.Printf("failed to create ID token: %v", err)
tokenErr(w, errServerError, "", http.StatusInternalServerError)
return
}
if err := s.storage.DeleteAuthCode(code); err != nil {
log.Printf("failed to delete auth code: %v", err)
tokenErr(w, errServerError, "", http.StatusInternalServerError)
return
}
reqRefresh := func() bool {
for _, scope := range authCode.Scopes {
if scope == scopeOfflineAccess {
return true
}
}
return false
}()
var refreshToken string
if reqRefresh {
refresh := storage.Refresh{
RefreshToken: storage.NewNonce(),
ClientID: authCode.ClientID,
ConnectorID: authCode.ConnectorID,
Scopes: authCode.Scopes,
Identity: authCode.Identity,
Nonce: authCode.Nonce,
}
if err := s.storage.CreateRefresh(refresh); err != nil {
log.Printf("failed to create refresh token: %v", err)
tokenErr(w, errServerError, "", http.StatusInternalServerError)
return
}
refreshToken = refresh.RefreshToken
}
s.writeAccessToken(w, idToken, refreshToken, expiry)
}
// handle a refresh token request https://tools.ietf.org/html/rfc6749#section-6
func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, client storage.Client) {
code := r.PostFormValue("refresh_token")
scope := r.PostFormValue("scope")
if code == "" {
tokenErr(w, errInvalidRequest, "No refresh token in request.", http.StatusBadRequest)
return
}
refresh, err := s.storage.GetRefresh(code)
if err != nil || refresh.ClientID != client.ID {
if err != storage.ErrNotFound {
log.Printf("failed to get auth code: %v", err)
tokenErr(w, errServerError, "", http.StatusInternalServerError)
} else {
tokenErr(w, errInvalidRequest, "Refresh token is invalid or has already been claimed by another client.", http.StatusBadRequest)
}
return
}
scopes := refresh.Scopes
if scope != "" {
requestedScopes := strings.Split(scope, " ")
contains := func() bool {
Loop:
for _, s := range requestedScopes {
for _, scope := range refresh.Scopes {
if s == scope {
continue Loop
}
}
return false
}
return true
}()
if !contains {
tokenErr(w, errInvalidRequest, "Requested scopes did not contain authorized scopes.", http.StatusBadRequest)
return
}
scopes = requestedScopes
}
// TODO(ericchiang): re-auth with backends
idToken, expiry, err := s.newIDToken(client.ID, refresh.Identity, scopes, refresh.Nonce)
if err != nil {
log.Printf("failed to create ID token: %v", err)
tokenErr(w, errServerError, "", http.StatusInternalServerError)
return
}
if err := s.storage.DeleteRefresh(code); err != nil {
log.Printf("failed to delete auth code: %v", err)
tokenErr(w, errServerError, "", http.StatusInternalServerError)
return
}
refresh.RefreshToken = storage.NewNonce()
if err := s.storage.CreateRefresh(refresh); err != nil {
log.Printf("failed to create refresh token: %v", err)
tokenErr(w, errServerError, "", http.StatusInternalServerError)
return
}
s.writeAccessToken(w, idToken, refresh.RefreshToken, expiry)
}
func (s *Server) writeAccessToken(w http.ResponseWriter, idToken, refreshToken string, expiry time.Time) {
// TODO(ericchiang): figure out an access token story and support the user info
// endpoint. For now use a random value so no one depends on the access_token
// holding a specific structure.
resp := struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token,omitempty"`
IDToken string `json:"id_token"`
}{
storage.NewNonce(),
"bearer",
int(expiry.Sub(s.now())),
refreshToken,
idToken,
}
data, err := json.Marshal(resp)
if err != nil {
log.Printf("failed to marshal access token response: %v", err)
tokenErr(w, errServerError, "", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
w.Write(data)
}
func (s *Server) renderError(w http.ResponseWriter, status int, err, description string) {
http.Error(w, fmt.Sprintf("%s: %s", err, description), status)
}
func (s *Server) notFound(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
}

1
server/handlers_test.go Normal file
View file

@ -0,0 +1 @@
package server

339
server/oauth2.go Normal file
View file

@ -0,0 +1,339 @@
package server
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/coreos/poke/storage"
)
// TODO(ericchiang): clean this file up and figure out more idiomatic error handling.
// authErr is an error response to an authorization request.
// See: https://tools.ietf.org/html/rfc6749#section-4.1.2.1
type authErr struct {
State string
RedirectURI string
Type string
Description string
}
func (err *authErr) ServeHTTP(w http.ResponseWriter, r *http.Request) {
v := url.Values{}
v.Add("state", err.State)
v.Add("error", err.Type)
if err.Description != "" {
v.Add("error_description", err.Description)
}
var redirectURI string
if strings.Contains(err.RedirectURI, "?") {
redirectURI = err.RedirectURI + "&" + v.Encode()
} else {
redirectURI = err.RedirectURI + "?" + v.Encode()
}
http.Redirect(w, r, redirectURI, http.StatusSeeOther)
}
func tokenErr(w http.ResponseWriter, typ, description string, statusCode int) {
data := struct {
Error string `json:"error"`
Description string `json:"error_description,omitempty"`
}{typ, description}
body, err := json.Marshal(data)
if err != nil {
log.Printf("failed to marshal token error response: %v", err)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
w.Write(body)
}
const (
errInvalidRequest = "invalid_request"
errUnauthorizedClient = "unauthorized_client"
errAccessDenied = "access_denied"
errUnsupportedResponseType = "unsupported_response_type"
errInvalidScope = "invalid_scope"
errServerError = "server_error"
errTemporarilyUnavailable = "temporarily_unavailable"
errUnsupportedGrantType = "unsupported_grant_type"
errInvalidGrant = "invalid_grant"
errInvalidClient = "invalid_client"
)
const (
scopeOfflineAccess = "offline_access" // Request a refresh token.
scopeOpenID = "openid"
scopeGroups = "groups"
scopeEmail = "email"
scopeProfile = "profile"
scopeCrossClientPrefix = "oauth2:server:client_id:"
)
const (
grantTypeAuthorizationCode = "code"
grantTypeRefreshToken = "refresh_token"
)
const (
responseTypeCode = "code" // "Regular" flow
responseTypeToken = "token" // Implicit flow for frontend apps.
responseTypeIDToken = "id_token" // ID Token in url fragment
)
var validResponseTypes = map[string]bool{
"code": true,
"token": true,
"id_token": true,
}
type audience []string
func (a audience) MarshalJSON() ([]byte, error) {
if len(a) == 1 {
return json.Marshal(a[0])
}
return json.Marshal(a)
}
type idTokenClaims struct {
Issuer string `json:"iss"`
Subject string `json:"sub"`
Audience audience `json:"aud"`
Expiry int64 `json:"exp"`
IssuedAt int64 `json:"iat"`
AuthorizingParty string `json:"azp,omitempty"`
Nonce string `json:"nonce,omitempty"`
Email string `json:"email,omitempty"`
EmailVerified *bool `json:"email_verified,omitempty"`
Groups []string `json:"groups,omitempty"`
Name string `json:"name,omitempty"`
}
func (s *Server) newIDToken(clientID string, claims storage.Identity, scopes []string, nonce string) (idToken string, expiry time.Time, err error) {
issuedAt := s.now()
expiry = issuedAt.Add(s.idTokensValidFor)
tok := idTokenClaims{
Issuer: s.issuerURL.String(),
Subject: claims.UserID,
Nonce: nonce,
Expiry: expiry.Unix(),
IssuedAt: issuedAt.Unix(),
}
for _, scope := range scopes {
switch {
case scope == scopeEmail:
tok.Email = claims.Email
tok.EmailVerified = &claims.EmailVerified
case scope == scopeGroups:
tok.Groups = claims.Groups
case scope == scopeProfile:
tok.Name = claims.Username
default:
peerID, ok := parseCrossClientScope(scope)
if !ok {
continue
}
isTrusted, err := validateCrossClientTrust(s.storage, clientID, peerID)
if err != nil {
return "", expiry, err
}
if !isTrusted {
// TODO(ericchiang): propagate this error to the client.
return "", expiry, fmt.Errorf("peer (%s) does not trust client", peerID)
}
tok.Audience = append(tok.Audience, peerID)
}
}
if len(tok.Audience) == 0 {
tok.Audience = audience{clientID}
} else {
tok.AuthorizingParty = clientID
}
payload, err := json.Marshal(tok)
if err != nil {
return "", expiry, fmt.Errorf("could not serialize claims: %v", err)
}
keys, err := s.storage.GetKeys()
if err != nil {
log.Printf("Failed to get keys: %v", err)
return "", expiry, err
}
if idToken, err = keys.Sign(payload); err != nil {
return "", expiry, fmt.Errorf("failed to sign payload: %v", err)
}
return idToken, expiry, nil
}
// parse the initial request from the OAuth2 client.
//
// For correctness the logic is largely copied from https://github.com/RangelReale/osin.
func parseAuthorizationRequest(s storage.Storage, r *http.Request) (req storage.AuthRequest, oauth2Err *authErr) {
if err := r.ParseForm(); err != nil {
return req, &authErr{"", "", errInvalidRequest, "Failed to parse request."}
}
redirectURI, err := url.QueryUnescape(r.Form.Get("redirect_uri"))
if err != nil {
return req, &authErr{"", "", errInvalidRequest, "No redirect_uri provided."}
}
state := r.FormValue("state")
clientID := r.Form.Get("client_id")
client, err := s.GetClient(clientID)
if err != nil {
if err == storage.ErrNotFound {
description := fmt.Sprintf("Invalid client_id (%q).", clientID)
return req, &authErr{"", "", errUnauthorizedClient, description}
}
log.Printf("Failed to get client: %v", err)
return req, &authErr{"", "", errServerError, ""}
}
if !validateRedirectURI(client, redirectURI) {
description := fmt.Sprintf("Unregistered redirect_uri (%q).", redirectURI)
return req, &authErr{"", "", errInvalidRequest, description}
}
newErr := func(typ, format string, a ...interface{}) *authErr {
return &authErr{state, redirectURI, typ, fmt.Sprintf(format, a...)}
}
scopes := strings.Split(r.Form.Get("scope"), " ")
var (
unrecognized []string
invalidScopes []string
)
hasOpenIDScope := false
for _, scope := range scopes {
switch scope {
case scopeOpenID:
hasOpenIDScope = true
case scopeOfflineAccess, scopeEmail, scopeProfile, scopeGroups:
default:
peerID, ok := parseCrossClientScope(scope)
if !ok {
unrecognized = append(unrecognized, scope)
continue
}
isTrusted, err := validateCrossClientTrust(s, clientID, peerID)
if err != nil {
return req, newErr(errServerError, "")
}
if !isTrusted {
invalidScopes = append(invalidScopes, scope)
}
}
}
if !hasOpenIDScope {
return req, newErr("invalid_scope", `Missing required scope(s) ["openid"].`)
}
if len(unrecognized) > 0 {
return req, newErr("invalid_scope", "Unrecognized scope(s) %q", unrecognized)
}
if len(invalidScopes) > 0 {
return req, newErr("invalid_scope", "Client can't request scope(s) %q", invalidScopes)
}
responseTypes := strings.Split(r.Form.Get("response_type"), " ")
for _, responseType := range responseTypes {
if !validResponseTypes[responseType] {
return req, newErr("invalid_request", "Invalid response type %q", responseType)
}
}
return storage.AuthRequest{
ID: storage.NewNonce(),
ClientID: client.ID,
State: r.Form.Get("state"),
Nonce: r.Form.Get("nonce"),
ForceApprovalPrompt: r.Form.Get("approval_prompt") == "force",
Scopes: scopes,
RedirectURI: redirectURI,
ResponseTypes: responseTypes,
}, nil
}
func parseCrossClientScope(scope string) (peerID string, ok bool) {
if ok = strings.HasPrefix(scope, scopeCrossClientPrefix); ok {
peerID = scope[len(scopeCrossClientPrefix):]
}
return
}
func validateCrossClientTrust(s storage.Storage, clientID, peerID string) (trusted bool, err error) {
if peerID == clientID {
return true, nil
}
peer, err := s.GetClient(peerID)
if err != nil {
if err != storage.ErrNotFound {
log.Printf("Failed to get client: %v", err)
return false, err
}
return false, nil
}
for _, id := range peer.TrustedPeers {
if id == clientID {
return true, nil
}
}
return false, nil
}
func validateRedirectURI(client storage.Client, redirectURI string) bool {
if !client.Public {
for _, uri := range client.RedirectURIs {
if redirectURI == uri {
return true
}
}
return false
}
if redirectURI == "urn:ietf:wg:oauth:2.0:oob" {
return true
}
if !strings.HasPrefix(redirectURI, "http://localhost:") {
return false
}
n, err := strconv.Atoi(strings.TrimPrefix(redirectURI, "https://localhost:"))
return err == nil && n <= 0
}
type tokenRequest struct {
Client storage.Client
IsRefresh bool
Token string
RedirectURI string
Scopes []string
}
func handleTokenRequest(s storage.Storage, w http.ResponseWriter, r *http.Request) *authErr {
return nil
}
func handleRefreshRequest(s storage.Storage, w http.ResponseWriter, r *http.Request, client storage.Client) *authErr {
return nil
}
func handleCodeRequest(s storage.Storage, w http.ResponseWriter, r *http.Request, client storage.Client) *authErr {
return nil
}

1
server/oauth2_test.go Normal file
View file

@ -0,0 +1 @@
package server

165
server/rotation.go Normal file
View file

@ -0,0 +1,165 @@
package server
import (
"crypto/rand"
"crypto/rsa"
"encoding/hex"
"errors"
"fmt"
"io"
"log"
"time"
"golang.org/x/net/context"
"gopkg.in/square/go-jose.v2"
"github.com/coreos/poke/storage"
)
// rotationStrategy describes a strategy for generating cryptographic keys, how
// often to rotate them, and how long they can validate signatures after rotation.
type rotationStrategy struct {
// Time between rotations.
period time.Duration
// After being rotated how long can a key validate signatues?
verifyFor time.Duration
// Keys are always RSA keys. Though cryptopasta recommends ECDSA keys, not every
// client may support these (e.g. github.com/coreos/go-oidc/oidc).
key func() (*rsa.PrivateKey, error)
}
// staticRotationStrategy returns a strategy which never rotates keys.
func staticRotationStrategy(key *rsa.PrivateKey) rotationStrategy {
return rotationStrategy{
// Setting these values to 100 years is easier than having a flag indicating no rotation.
period: time.Hour * 8760 * 100,
verifyFor: time.Hour * 8760 * 100,
key: func() (*rsa.PrivateKey, error) { return key, nil },
}
}
// defaultRotationStrategy returns a strategy which rotates keys every provided period,
// holding onto the public parts for some specified amount of time.
func defaultRotationStrategy(rotationPeriod, verifyFor time.Duration) rotationStrategy {
return rotationStrategy{
period: rotationPeriod,
verifyFor: verifyFor,
key: func() (*rsa.PrivateKey, error) {
return rsa.GenerateKey(rand.Reader, 2048)
},
}
}
type keyRotater struct {
storage.Storage
strategy rotationStrategy
cancel context.CancelFunc
now func() time.Time
}
func storageWithKeyRotation(s storage.Storage, strategy rotationStrategy, now func() time.Time) storage.Storage {
if now == nil {
now = time.Now
}
ctx, cancel := context.WithCancel(context.Background())
rotater := keyRotater{s, strategy, cancel, now}
// Try to rotate immediately so properly configured storages will return a
// storage with keys.
if err := rotater.rotate(); err != nil {
log.Printf("failed to rotate keys: %v", err)
}
go func() {
select {
case <-ctx.Done():
return
case <-time.After(time.Second * 30):
if err := rotater.rotate(); err != nil {
log.Printf("failed to rotate keys: %v", err)
}
}
}()
return rotater
}
func (k keyRotater) Close() error {
k.cancel()
return k.Storage.Close()
}
func (k keyRotater) rotate() error {
keys, err := k.GetKeys()
if err != nil && err != storage.ErrNotFound {
return fmt.Errorf("get keys: %v", err)
}
if k.now().Before(keys.NextRotation) {
return nil
}
log.Println("keys expired, rotating")
// Generate the key outside of a storage transaction.
key, err := k.strategy.key()
if err != nil {
return fmt.Errorf("generate key: %v", err)
}
b := make([]byte, 20)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
panic(err)
}
keyID := hex.EncodeToString(b)
priv := &jose.JSONWebKey{
Key: key,
KeyID: keyID,
Algorithm: "RS256",
Use: "sig",
}
pub := &jose.JSONWebKey{
Key: key.Public(),
KeyID: keyID,
Algorithm: "RS256",
Use: "sig",
}
var nextRotation time.Time
err = k.Storage.UpdateKeys(func(keys storage.Keys) (storage.Keys, error) {
tNow := k.now()
if tNow.Before(keys.NextRotation) {
return storage.Keys{}, errors.New("keys already rotated")
}
// Remove expired verification keys.
i := 0
for _, key := range keys.VerificationKeys {
if !key.Expiry.After(tNow) {
keys.VerificationKeys[i] = key
i++
}
}
keys.VerificationKeys = keys.VerificationKeys[:i]
if keys.SigningKeyPub != nil {
// Move current signing key to a verification only key.
verificationKey := storage.VerificationKey{
PublicKey: keys.SigningKeyPub,
Expiry: tNow.Add(k.strategy.verifyFor),
}
keys.VerificationKeys = append(keys.VerificationKeys, verificationKey)
}
nextRotation = k.now().Add(k.strategy.period)
keys.SigningKey = priv
keys.SigningKeyPub = pub
keys.NextRotation = nextRotation
return keys, nil
})
if err != nil {
return err
}
log.Printf("keys rotated, next rotation: %s", nextRotation)
return nil
}

1
server/rotation_test.go Normal file
View file

@ -0,0 +1 @@
package server

141
server/server.go Normal file
View file

@ -0,0 +1,141 @@
package server
import (
"errors"
"fmt"
"net/http"
"net/url"
"path"
"time"
"github.com/gorilla/mux"
"github.com/coreos/poke/connector"
"github.com/coreos/poke/storage"
)
// Connector is a connector with metadata.
type Connector struct {
ID string
DisplayName string
Connector connector.Connector
}
// Config holds the server's configuration options.
type Config struct {
Issuer string
// The backing persistence layer.
Storage storage.Storage
// Strategies for federated identity.
Connectors []Connector
// NOTE: Multiple servers using the same storage are expected to set rotation and
// validity periods to the same values.
RotateKeysAfter time.Duration // Defaults to 6 hours.
IDTokensValidFor time.Duration // Defaults to 24 hours
// If specified, the server will use this function for determining time.
Now func() time.Time
}
func value(val, defaultValue time.Duration) time.Duration {
if val == 0 {
return defaultValue
}
return val
}
// Server is the top level object.
type Server struct {
issuerURL url.URL
// Read-only map of connector IDs to connectors.
connectors map[string]Connector
storage storage.Storage
mux http.Handler
// If enabled, don't prompt user for approval after logging in through connector.
// No package level API to set this, only used in tests.
skipApproval bool
now func() time.Time
idTokensValidFor time.Duration
}
// New constructs a server from the provided config.
func New(c Config) (*Server, error) {
return newServer(c, defaultRotationStrategy(
value(c.RotateKeysAfter, 6*time.Hour),
value(c.IDTokensValidFor, 24*time.Hour),
))
}
func newServer(c Config, rotationStrategy rotationStrategy) (*Server, error) {
issuerURL, err := url.Parse(c.Issuer)
if err != nil {
return nil, fmt.Errorf("server: can't parse issuer URL")
}
if len(c.Connectors) == 0 {
return nil, errors.New("server: no connectors specified")
}
if c.Storage == nil {
return nil, errors.New("server: storage cannot be nil")
}
now := c.Now
if now == nil {
now = time.Now
}
s := &Server{
issuerURL: *issuerURL,
connectors: make(map[string]Connector),
storage: storageWithKeyRotation(c.Storage, rotationStrategy, now),
idTokensValidFor: value(c.IDTokensValidFor, 24*time.Hour),
now: now,
}
for _, conn := range c.Connectors {
s.connectors[conn.ID] = conn
}
r := mux.NewRouter()
handleFunc := func(p string, h http.HandlerFunc) {
r.HandleFunc(path.Join(issuerURL.Path, p), h)
}
r.NotFoundHandler = http.HandlerFunc(s.notFound)
// TODO(ericchiang): rate limit certain paths based on IP.
handleFunc("/.well-known/openid-configuration", s.handleDiscovery)
handleFunc("/token", s.handleToken)
handleFunc("/keys", s.handlePublicKeys)
handleFunc("/auth", s.handleAuthorization)
handleFunc("/auth/{connector}", s.handleConnectorLogin)
handleFunc("/callback/{connector}", s.handleConnectorCallback)
handleFunc("/approval", s.handleApproval)
s.mux = r
return s, nil
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.mux.ServeHTTP(w, r)
}
func (s *Server) absPath(pathItems ...string) string {
paths := make([]string, len(pathItems)+1)
paths[0] = s.issuerURL.Path
copy(paths[1:], pathItems)
return path.Join(paths...)
}
func (s *Server) absURL(pathItems ...string) string {
u := s.issuerURL
u.Path = s.absPath(pathItems...)
return u.String()
}

221
server/server_test.go Normal file
View file

@ -0,0 +1,221 @@
package server
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"net/http"
"net/http/httptest"
"net/http/httputil"
"testing"
"time"
"github.com/ericchiang/oidc"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"github.com/coreos/poke/connector/mock"
"github.com/coreos/poke/storage"
"github.com/coreos/poke/storage/memory"
)
func mustLoad(s string) *rsa.PrivateKey {
block, _ := pem.Decode([]byte(s))
if block == nil {
panic("no pem data found")
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
return key
}
var testKey = mustLoad(`-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEArmoiX5G36MKPiVGS1sicruEaGRrbhPbIKOf97aGGQRjXVngo
Knwd2L4T9CRyABgQm3tLHHcT5crODoy46wX2g9onTZWViWWuhJ5wxXNmUbCAPWHb
j9SunW53WuLYZ/IJLNZt5XYCAFPjAakWp8uMuuDwWo5EyFaw85X3FSMhVmmaYDd0
cn+1H4+NS/52wX7tWmyvGUNJ8lzjFAnnOtBJByvkyIC7HDphkLQV4j//sMNY1mPX
HbsYgFv2J/LIJtkjdYO2UoDhZG3Gvj16fMy2JE2owA8IX4/s+XAmA2PiTfd0J5b4
drAKEcdDl83G6L3depEkTkfvp0ZLsh9xupAvIwIDAQABAoIBABKGgWonPyKA7+AF
AxS/MC0/CZebC6/+ylnV8lm4K1tkuRKdJp8EmeL4pYPsDxPFepYZLWwzlbB1rxdK
iSWld36fwEb0WXLDkxrQ/Wdrj3Wjyqs6ZqjLTVS5dAH6UEQSKDlT+U5DD4lbX6RA
goCGFUeQNtdXfyTMWHU2+4yKM7NKzUpczFky+0d10Mg0ANj3/4IILdr3hqkmMSI9
1TB9ksWBXJxt3nGxAjzSFihQFUlc231cey/HhYbvAX5fN0xhLxOk88adDcdXE7br
3Ser1q6XaaFQSMj4oi1+h3RAT9MUjJ6johEqjw0PbEZtOqXvA1x5vfFdei6SqgKn
Am3BspkCgYEA2lIiKEkT/Je6ZH4Omhv9atbGoBdETAstL3FnNQjkyVau9f6bxQkl
4/sz985JpaiasORQBiTGY8JDT/hXjROkut91agi2Vafhr29L/mto7KZglfDsT4b2
9z/EZH8wHw7eYhvdoBbMbqNDSI8RrGa4mpLpuN+E0wsFTzSZEL+QMQUCgYEAzIQh
xnreQvDAhNradMqLmxRpayn1ORaPReD4/off+mi7hZRLKtP0iNgEVEWHJ6HEqqi1
r38XAc8ap/lfOVMar2MLyCFOhYspdHZ+TGLZfr8gg/Fzeq9IRGKYadmIKVwjMeyH
REPqg1tyrvMOE0HI5oqkko8JTDJ0OyVC0Vc6+AcCgYAqCzkywugLc/jcU35iZVOH
WLdFq1Vmw5w/D7rNdtoAgCYPj6nV5y4Z2o2mgl6ifXbU7BMRK9Hc8lNeOjg6HfdS
WahV9DmRA1SuIWPkKjE5qczd81i+9AHpmakrpWbSBF4FTNKAewOBpwVVGuBPcDTK
59IE3V7J+cxa9YkotYuCNQKBgCwGla7AbHBEm2z+H+DcaUktD7R+B8gOTzFfyLoi
Tdj+CsAquDO0BQQgXG43uWySql+CifoJhc5h4v8d853HggsXa0XdxaWB256yk2Wm
MePTCRDePVm/ufLetqiyp1kf+IOaw1Oyux0j5oA62mDS3Iikd+EE4Z+BjPvefY/L
E2qpAoGAZo5Wwwk7q8b1n9n/ACh4LpE+QgbFdlJxlfFLJCKstl37atzS8UewOSZj
FDWV28nTP9sqbtsmU8Tem2jzMvZ7C/Q0AuDoKELFUpux8shm8wfIhyaPnXUGZoAZ
Np4vUwMSYV5mopESLWOg3loBxKyLGFtgGKVCjGiQvy6zISQ4fQo=
-----END RSA PRIVATE KEY-----`)
func newTestServer() (*httptest.Server, *Server) {
var server *Server
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
server.ServeHTTP(w, r)
}))
config := Config{
Issuer: s.URL,
Storage: memory.New(),
Connectors: []Connector{
{
ID: "mock",
DisplayName: "Mock",
Connector: mock.New(),
},
},
}
var err error
if server, err = newServer(config, staticRotationStrategy(testKey)); err != nil {
panic(err)
}
server.skipApproval = true // Don't prompt for approval, just immediately redirect with code.
return s, server
}
func TestNewTestServer(t *testing.T) {
newTestServer()
}
func TestDiscovery(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
httpServer, _ := newTestServer()
defer httpServer.Close()
p, err := oidc.NewProvider(ctx, httpServer.URL)
if err != nil {
t.Fatalf("failed to get provider: %v", err)
}
required := []struct {
name, val string
}{
{"issuer", p.Issuer},
{"authorization_endpoint", p.AuthURL},
{"token_endpoint", p.TokenURL},
{"jwks_uri", p.JWKSURL},
}
for _, field := range required {
if field.val == "" {
t.Errorf("server discovery is missing required field %q", field.name)
}
}
}
func TestOAuth2Flow(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
httpServer, s := newTestServer()
defer httpServer.Close()
p, err := oidc.NewProvider(ctx, httpServer.URL)
if err != nil {
t.Fatalf("failed to get provider: %v", err)
}
var (
reqDump, respDump []byte
gotCode bool
state = "a_state"
)
defer func() {
if !gotCode {
t.Errorf("never got a code in callback\n%s\n%s", reqDump, respDump)
}
}()
var oauth2Config *oauth2.Config
oauth2Server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/callback" {
q := r.URL.Query()
if errType := q.Get("error"); errType != "" {
if desc := q.Get("error_description"); desc != "" {
t.Errorf("got error from server %s: %s", errType, desc)
} else {
t.Errorf("got error from server %s", errType)
}
w.WriteHeader(http.StatusInternalServerError)
return
}
if code := q.Get("code"); code != "" {
gotCode = true
token, err := oauth2Config.Exchange(ctx, code)
if err != nil {
t.Errorf("failed to exchange code for token: %v", err)
return
}
idToken, ok := token.Extra("id_token").(string)
if !ok {
t.Errorf("no id token found: %v", err)
return
}
// TODO(ericchiang): validate id token
_ = idToken
token.Expiry = time.Now().Add(time.Second * -10)
if token.Valid() {
t.Errorf("token shouldn't be valid")
}
newToken, err := oauth2Config.TokenSource(ctx, token).Token()
if err != nil {
t.Errorf("failed to refresh token: %v", err)
return
}
if token.RefreshToken == newToken.RefreshToken {
t.Errorf("old refresh token was the same as the new token %q", token.RefreshToken)
}
}
if gotState := q.Get("state"); gotState != state {
t.Errorf("state did not match, want=%q got=%q", state, gotState)
}
w.WriteHeader(http.StatusOK)
return
}
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusSeeOther)
}))
defer oauth2Server.Close()
redirectURL := oauth2Server.URL + "/callback"
client := storage.Client{
ID: "testclient",
Secret: "testclientsecret",
RedirectURIs: []string{redirectURL},
}
if err := s.storage.CreateClient(client); err != nil {
t.Fatalf("failed to create client: %v", err)
}
oauth2Config = &oauth2.Config{
ClientID: client.ID,
ClientSecret: client.Secret,
Endpoint: p.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email", "offline_access"},
RedirectURL: redirectURL,
}
resp, err := http.Get(oauth2Server.URL + "/login")
if err != nil {
t.Fatalf("get failed: %v", err)
}
if reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {
t.Fatal(err)
}
if respDump, err = httputil.DumpResponse(resp, true); err != nil {
t.Fatal(err)
}
}

101
server/templates.go Normal file
View file

@ -0,0 +1,101 @@
package server
import (
"log"
"net/http"
"text/template"
"github.com/coreos/poke/storage"
)
type connectorInfo struct {
DisplayName string
URL string
}
var loginTmpl = template.Must(template.New("login-template").Parse(`<html>
<head></head>
<body>
<p>Login options</p>
{{ range $i, $connector := .Connectors }}
<a href="{{ $connector.URL }}?state={{ $.State }}">{{ $connector.DisplayName }}</a>
{{ end }}
</body>
</html>`))
func renderLoginOptions(w http.ResponseWriter, connectors []connectorInfo, state string) {
data := struct {
Connectors []connectorInfo
State string
}{connectors, state}
renderTemplate(w, loginTmpl, data)
}
var passwordTmpl = template.Must(template.New("password-template").Parse(`<html>
<body>
<p>Login</p>
<form action="{{ .Callback }}" method="POST">
Login: <input type="text" name="login"/><br/>
Password: <input type="password" name="password"/><br/>
<input type="hidden" name="state" value="{{ .State }}"/>
<input type="submit"/>
{{ if .Message }}
<p>Error: {{ .Message }}</p>
{{ end }}
</form>
</body>
</html>`))
func renderPasswordTmpl(w http.ResponseWriter, state, callback, message string) {
data := struct {
State string
Callback string
Message string
}{state, callback, message}
renderTemplate(w, passwordTmpl, data)
}
var approvalTmpl = template.Must(template.New("approval-template").Parse(`<html>
<body>
<p>User: {{ .User }}</p>
<p>Client: {{ .ClientName }}</p>
<form method="post">
<input type="hidden" name="state" value="{{ .State }}"/>
<input type="hidden" name="approval" value="approve">
<button type="submit">Approve</button>
</form>
<form method="post">
<input type="hidden" name="state" value="{{ .State }}"/>
<input type="hidden" name="approval" value="reject">
<button type="submit">Reject</button>
</form>
</body>
</html>`))
func renderApprovalTmpl(w http.ResponseWriter, state string, identity storage.Identity, client storage.Client, scopes []string) {
data := struct {
User string
ClientName string
State string
}{identity.Email, client.Name, state}
renderTemplate(w, approvalTmpl, data)
}
func renderTemplate(w http.ResponseWriter, tmpl *template.Template, data interface{}) {
err := tmpl.Execute(w, data)
if err == nil {
return
}
switch err := err.(type) {
case template.ExecError:
// An ExecError guarentees that Execute has not written to the underlying reader.
log.Printf("Error rendering template %s: %s", tmpl.Name(), err)
// TODO(ericchiang): replace with better internal server error.
http.Error(w, "Internal server error", http.StatusInternalServerError)
default:
// An error with the underlying write, such as the connection being
// dropped. Ignore for now.
}
}

1
server/templates_test.go Normal file
View file

@ -0,0 +1 @@
package server

2
storage/doc.go Normal file
View file

@ -0,0 +1,2 @@
// Package storage defines the storage interface and types used by the server.
package storage

View file

@ -0,0 +1,363 @@
package kubernetes
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/gtank/cryptopasta"
yaml "gopkg.in/yaml.v2"
"github.com/coreos/poke/storage"
"github.com/coreos/poke/storage/kubernetes/k8sapi"
)
type client struct {
client *http.Client
baseURL string
namespace string
apiVersion string
now func() time.Time
// BUG: currently each third party API group can only have one resource in it,
// so for each resource this storage uses, it need a unique API group.
//
// Prepend the name of each resource to the API group for a predictable mapping.
//
// See: https://github.com/kubernetes/kubernetes/pull/28414
prependResourceNameToAPIGroup bool
}
func (c *client) apiVersionForResource(resource string) string {
if !c.prependResourceNameToAPIGroup {
return c.apiVersion
}
return resource + "." + c.apiVersion
}
func (c *client) urlFor(apiVersion, namespace, resource, name string) string {
basePath := "apis/"
if apiVersion == "v1" {
basePath = "api/"
}
if c.prependResourceNameToAPIGroup && apiVersion != "" && resource != "" {
apiVersion = resource + "." + apiVersion
}
var p string
if namespace != "" {
p = path.Join(basePath, apiVersion, "namespaces", namespace, resource, name)
} else {
p = path.Join(basePath, apiVersion, resource, name)
}
if strings.HasSuffix(c.baseURL, "/") {
return c.baseURL + p
}
return c.baseURL + "/" + p
}
type httpErr struct {
method string
url string
status string
body []byte
}
func (e *httpErr) Error() string {
return fmt.Sprintf("%s %s %s: response from server \"%s\"", e.method, e.url, e.status, bytes.TrimSpace(e.body))
}
func checkHTTPErr(r *http.Response, validStatusCodes ...int) error {
for _, status := range validStatusCodes {
if r.StatusCode == status {
return nil
}
}
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 2<<15)) // 64 KiB
if err != nil {
return fmt.Errorf("read response body: %v", err)
}
var url, method string
if r.Request != nil {
method = r.Request.Method
url = r.Request.URL.String()
}
err = &httpErr{method, url, r.Status, body}
log.Printf("%s", err)
if r.StatusCode == http.StatusNotFound {
return storage.ErrNotFound
}
return err
}
// Close the response body. The initial request is drained so the connection can
// be reused.
func closeResp(r *http.Response) {
io.Copy(ioutil.Discard, r.Body)
r.Body.Close()
}
func (c *client) get(resource, name string, v interface{}) error {
url := c.urlFor(c.apiVersion, c.namespace, resource, name)
resp, err := c.client.Get(url)
if err != nil {
return err
}
defer closeResp(resp)
if err := checkHTTPErr(resp, http.StatusOK); err != nil {
return err
}
return json.NewDecoder(resp.Body).Decode(v)
}
func (c *client) list(resource string, v interface{}) error {
return c.get(resource, "", v)
}
func (c *client) post(resource string, v interface{}) error {
body, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("marshal object: %v", err)
}
url := c.urlFor(c.apiVersion, c.namespace, resource, "")
resp, err := c.client.Post(url, "application/json", bytes.NewReader(body))
if err != nil {
return err
}
defer closeResp(resp)
return checkHTTPErr(resp, http.StatusCreated)
}
func (c *client) delete(resource, name string) error {
url := c.urlFor(c.apiVersion, c.namespace, resource, name)
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return fmt.Errorf("create delete request: %v", err)
}
resp, err := c.client.Do(req)
if err != nil {
return fmt.Errorf("delete request: %v", err)
}
defer closeResp(resp)
return checkHTTPErr(resp, http.StatusOK)
}
func (c *client) put(resource, name string, v interface{}) error {
body, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("marshal object: %v", err)
}
url := c.urlFor(c.apiVersion, c.namespace, resource, name)
req, err := http.NewRequest("PUT", url, bytes.NewReader(body))
if err != nil {
return fmt.Errorf("create patch request: %v", err)
}
req.Header.Set("Content-Length", strconv.Itoa(len(body)))
resp, err := c.client.Do(req)
if err != nil {
return fmt.Errorf("patch request: %v", err)
}
defer closeResp(resp)
return checkHTTPErr(resp, http.StatusOK)
}
func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string) (*client, error) {
tlsConfig := cryptopasta.DefaultTLSConfig()
data := func(b []byte, file string) ([]byte, error) {
if b != nil {
return b, nil
}
if file == "" {
return nil, nil
}
return ioutil.ReadFile(file)
}
if caData, err := data(cluster.CertificateAuthorityData, cluster.CertificateAuthority); err != nil {
return nil, err
} else if caData != nil {
tlsConfig.RootCAs = x509.NewCertPool()
if !tlsConfig.RootCAs.AppendCertsFromPEM(caData) {
return nil, fmt.Errorf("no certificate data found: %v", err)
}
}
clientCert, err := data(user.ClientCertificateData, user.ClientCertificate)
if err != nil {
return nil, err
}
clientKey, err := data(user.ClientKeyData, user.ClientKey)
if err != nil {
return nil, err
}
if clientCert != nil && clientKey != nil {
cert, err := tls.X509KeyPair(clientCert, clientKey)
if err != nil {
return nil, fmt.Errorf("failed to load client cert: %v", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
var t http.RoundTripper = &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSClientConfig: tlsConfig,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
if user.Token != "" {
t = transport{
updateReq: func(r *http.Request) {
r.Header.Set("Authorization", "Bearer "+user.Token)
},
base: t,
}
}
if user.Username != "" && user.Password != "" {
t = transport{
updateReq: func(r *http.Request) {
r.SetBasicAuth(user.Username, user.Password)
},
base: t,
}
}
// TODO(ericchiang): make API Group and version configurable.
return &client{&http.Client{Transport: t}, cluster.Server, namespace, "oidc.coreos.com/v1", time.Now, true}, nil
}
type transport struct {
updateReq func(r *http.Request)
base http.RoundTripper
}
func (t transport) RoundTrip(r *http.Request) (*http.Response, error) {
// shallow copy of the struct
r2 := new(http.Request)
*r2 = *r
// deep copy of the Header
r2.Header = make(http.Header, len(r.Header))
for k, s := range r.Header {
r2.Header[k] = append([]string(nil), s...)
}
t.updateReq(r2)
return t.base.RoundTrip(r2)
}
func loadKubeConfig(kubeConfigPath string) (cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, err error) {
data, err := ioutil.ReadFile(kubeConfigPath)
if err != nil {
err = fmt.Errorf("read %s: %v", kubeConfigPath, err)
return
}
var c k8sapi.Config
if err = yaml.Unmarshal(data, &c); err != nil {
err = fmt.Errorf("unmarshal %s: %v", kubeConfigPath, err)
return
}
cluster, user, namespace, err = currentContext(&c)
if namespace == "" {
namespace = "default"
}
return
}
func inClusterConfig() (cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, err error) {
host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
if len(host) == 0 || len(port) == 0 {
err = fmt.Errorf("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined")
return
}
cluster = k8sapi.Cluster{
Server: "https://" + host + ":" + port,
CertificateAuthority: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
}
if namespace = os.Getenv("KUBERNETES_POD_NAMESPACE"); namespace == "" {
err = fmt.Errorf("unable to load in-cluster configuration, KUBERNETES_POD_NAMESPACE must be defined")
return
}
token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
if err != nil {
return
}
user = k8sapi.AuthInfo{Token: string(token)}
return
}
func currentContext(config *k8sapi.Config) (cluster k8sapi.Cluster, user k8sapi.AuthInfo, ns string, err error) {
if config.CurrentContext == "" {
return cluster, user, "", errors.New("kubeconfig has no current context")
}
context, ok := func() (k8sapi.Context, bool) {
for _, namedContext := range config.Contexts {
if namedContext.Name == config.CurrentContext {
return namedContext.Context, true
}
}
return k8sapi.Context{}, false
}()
if !ok {
return cluster, user, "", fmt.Errorf("no context named %q found", config.CurrentContext)
}
cluster, ok = func() (k8sapi.Cluster, bool) {
for _, namedCluster := range config.Clusters {
if namedCluster.Name == context.Cluster {
return namedCluster.Cluster, true
}
}
return k8sapi.Cluster{}, false
}()
if !ok {
return cluster, user, "", fmt.Errorf("no cluster named %q found", context.Cluster)
}
user, ok = func() (k8sapi.AuthInfo, bool) {
for _, namedAuthInfo := range config.AuthInfos {
if namedAuthInfo.Name == context.AuthInfo {
return namedAuthInfo.AuthInfo, true
}
}
return k8sapi.AuthInfo{}, false
}()
if !ok {
return cluster, user, "", fmt.Errorf("no user named %q found", context.AuthInfo)
}
return cluster, user, context.Namespace, nil
}
func newInClusterClient() (*client, error) {
return nil, nil
}

View file

@ -0,0 +1,2 @@
// Package kubernetes provides a storage interface using Kubernetes third party APIs.
package kubernetes

View file

@ -0,0 +1,29 @@
package kubernetes
import (
"fmt"
"log"
)
// TODO(ericchiang): Complete this.
type multiErr []error
func (m multiErr) Error() string {
return fmt.Sprintf("errors encountered: %s", m)
}
func (cli *client) gcAuthRequests() error {
var authRequests AuthRequestList
if err := cli.list(resourceAuthRequest, &authRequests); err != nil {
return err
}
for _, authRequest := range authRequests.AuthRequests {
if cli.now().After(authRequest.Expiry) {
if err := cli.delete(resourceAuthRequest, authRequest.ObjectMeta.Name); err != nil {
log.Printf("failed to detele auth request: %v", err)
}
}
}
return nil
}

View file

@ -0,0 +1,140 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
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 k8sapi
// Where possible, json tags match the cli argument names.
// Top level config objects and all values required for proper functioning are not "omitempty". Any truly optional piece of config is allowed to be omitted.
// Config holds the information needed to build connect to remote kubernetes clusters as a given user
type Config struct {
// Legacy field from pkg/api/types.go TypeMeta.
// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
Kind string `yaml:"kind,omitempty"`
// DEPRECATED: APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc).
// Because a cluster can run multiple API groups and potentially multiple versions of each, it no longer makes sense to specify
// a single value for the cluster version.
// This field isn't really needed anyway, so we are deprecating it without replacement.
// It will be ignored if it is present.
APIVersion string `yaml:"apiVersion,omitempty"`
// Preferences holds general information to be use for cli interactions
Preferences Preferences `yaml:"preferences"`
// Clusters is a map of referencable names to cluster configs
Clusters []NamedCluster `yaml:"clusters"`
// AuthInfos is a map of referencable names to user configs
AuthInfos []NamedAuthInfo `yaml:"users"`
// Contexts is a map of referencable names to context configs
Contexts []NamedContext `yaml:"contexts"`
// CurrentContext is the name of the context that you would like to use by default
CurrentContext string `yaml:"current-context"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
Extensions []NamedExtension `yaml:"extensions,omitempty"`
}
// Preferences contains information about the users command line experience preferences.
type Preferences struct {
Colors bool `yaml:"colors,omitempty"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
Extensions []NamedExtension `yaml:"extensions,omitempty"`
}
// Cluster contains information about how to communicate with a kubernetes cluster
type Cluster struct {
// Server is the address of the kubernetes cluster (https://hostname:port).
Server string `yaml:"server"`
// APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc).
APIVersion string `yaml:"api-version,omitempty"`
// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
InsecureSkipTLSVerify bool `yaml:"insecure-skip-tls-verify,omitempty"`
// CertificateAuthority is the path to a cert file for the certificate authority.
CertificateAuthority string `yaml:"certificate-authority,omitempty"`
// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
CertificateAuthorityData []byte `yaml:"certificate-authority-data,omitempty"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
Extensions []NamedExtension `yaml:"extensions,omitempty"`
}
// AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are.
type AuthInfo struct {
// ClientCertificate is the path to a client cert file for TLS.
ClientCertificate string `yaml:"client-certificate,omitempty"`
// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
ClientCertificateData []byte `yaml:"client-certificate-data,omitempty"`
// ClientKey is the path to a client key file for TLS.
ClientKey string `yaml:"client-key,omitempty"`
// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
ClientKeyData []byte `yaml:"client-key-data,omitempty"`
// Token is the bearer token for authentication to the kubernetes cluster.
Token string `yaml:"token,omitempty"`
// Impersonate is the username to imperonate. The name matches the flag.
Impersonate string `yaml:"as,omitempty"`
// Username is the username for basic authentication to the kubernetes cluster.
Username string `yaml:"username,omitempty"`
// Password is the password for basic authentication to the kubernetes cluster.
Password string `yaml:"password,omitempty"`
// AuthProvider specifies a custom authentication plugin for the kubernetes cluster.
AuthProvider *AuthProviderConfig `yaml:"auth-provider,omitempty"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
Extensions []NamedExtension `yaml:"extensions,omitempty"`
}
// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
type Context struct {
// Cluster is the name of the cluster for this context
Cluster string `yaml:"cluster"`
// AuthInfo is the name of the authInfo for this context
AuthInfo string `yaml:"user"`
// Namespace is the default namespace to use on unspecified requests
Namespace string `yaml:"namespace,omitempty"`
// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
Extensions []NamedExtension `yaml:"extensions,omitempty"`
}
// NamedCluster relates nicknames to cluster information
type NamedCluster struct {
// Name is the nickname for this Cluster
Name string `yaml:"name"`
// Cluster holds the cluster information
Cluster Cluster `yaml:"cluster"`
}
// NamedContext relates nicknames to context information
type NamedContext struct {
// Name is the nickname for this Context
Name string `yaml:"name"`
// Context holds the context information
Context Context `yaml:"context"`
}
// NamedAuthInfo relates nicknames to auth information
type NamedAuthInfo struct {
// Name is the nickname for this AuthInfo
Name string `yaml:"name"`
// AuthInfo holds the auth information
AuthInfo AuthInfo `yaml:"user"`
}
// NamedExtension relates nicknames to extension information
type NamedExtension struct {
// Name is the nickname for this Extension
Name string `yaml:"name"`
}
// AuthProviderConfig holds the configuration for a specified auth provider.
type AuthProviderConfig struct {
Name string `yaml:"name"`
Config map[string]string `yaml:"config"`
}

View file

@ -0,0 +1,2 @@
// Package k8sapi holds vendored Kubernetes types.
package k8sapi

View file

@ -0,0 +1,49 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package k8sapi
// A ThirdPartyResource is a generic representation of a resource, it is used by add-ons and plugins to add new resource
// types to the API. It consists of one or more Versions of the api.
type ThirdPartyResource struct {
TypeMeta `json:",inline"`
// Standard object metadata
ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Description is the description of this object.
Description string `json:"description,omitempty" protobuf:"bytes,2,opt,name=description"`
// Versions are versions for this third party object
Versions []APIVersion `json:"versions,omitempty" protobuf:"bytes,3,rep,name=versions"`
}
// ThirdPartyResourceList is a list of ThirdPartyResources.
type ThirdPartyResourceList struct {
TypeMeta `json:",inline"`
// Standard list metadata.
ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Items is the list of ThirdPartyResources.
Items []ThirdPartyResource `json:"items" protobuf:"bytes,2,rep,name=items"`
}
// An APIVersion represents a single concrete version of an object model.
type APIVersion struct {
// Name of this version (e.g. 'v1').
Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
}

View file

@ -0,0 +1,138 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
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 k8sapi
import (
"encoding/json"
"time"
)
// Time is a wrapper around time.Time which supports correct
// marshaling to YAML and JSON. Wrappers are provided for many
// of the factory methods that the time package offers.
//
// +protobuf.options.marshal=false
// +protobuf.as=Timestamp
type Time struct {
time.Time `protobuf:"-"`
}
// NewTime returns a wrapped instance of the provided time
func NewTime(time time.Time) Time {
return Time{time}
}
// Date returns the Time corresponding to the supplied parameters
// by wrapping time.Date.
func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time {
return Time{time.Date(year, month, day, hour, min, sec, nsec, loc)}
}
// Now returns the current local time.
func Now() Time {
return Time{time.Now()}
}
// IsZero returns true if the value is nil or time is zero.
func (t *Time) IsZero() bool {
if t == nil {
return true
}
return t.Time.IsZero()
}
// Before reports whether the time instant t is before u.
func (t Time) Before(u Time) bool {
return t.Time.Before(u.Time)
}
// Equal reports whether the time instant t is equal to u.
func (t Time) Equal(u Time) bool {
return t.Time.Equal(u.Time)
}
// Unix returns the local time corresponding to the given Unix time
// by wrapping time.Unix.
func Unix(sec int64, nsec int64) Time {
return Time{time.Unix(sec, nsec)}
}
// Rfc3339Copy returns a copy of the Time at second-level precision.
func (t Time) Rfc3339Copy() Time {
copied, _ := time.Parse(time.RFC3339, t.Format(time.RFC3339))
return Time{copied}
}
// UnmarshalJSON implements the json.Unmarshaller interface.
func (t *Time) UnmarshalJSON(b []byte) error {
if len(b) == 4 && string(b) == "null" {
t.Time = time.Time{}
return nil
}
var str string
json.Unmarshal(b, &str)
pt, err := time.Parse(time.RFC3339, str)
if err != nil {
return err
}
t.Time = pt.Local()
return nil
}
// UnmarshalQueryParameter converts from a URL query parameter value to an object
func (t *Time) UnmarshalQueryParameter(str string) error {
if len(str) == 0 {
t.Time = time.Time{}
return nil
}
// Tolerate requests from older clients that used JSON serialization to build query params
if len(str) == 4 && str == "null" {
t.Time = time.Time{}
return nil
}
pt, err := time.Parse(time.RFC3339, str)
if err != nil {
return err
}
t.Time = pt.Local()
return nil
}
// MarshalJSON implements the json.Marshaler interface.
func (t Time) MarshalJSON() ([]byte, error) {
if t.IsZero() {
// Encode unset/nil objects as JSON's "null".
return []byte("null"), nil
}
return json.Marshal(t.UTC().Format(time.RFC3339))
}
// MarshalQueryParameter converts to a URL query parameter value
func (t Time) MarshalQueryParameter() (string, error) {
if t.IsZero() {
// Encode unset/nil objects as an empty string
return "", nil
}
return t.UTC().Format(time.RFC3339), nil
}

View file

@ -0,0 +1,52 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
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 k8sapi
// TypeMeta describes an individual object in an API response or request
// with strings representing the type of the object and its API schema version.
// Structures that are versioned or persisted should inline TypeMeta.
type TypeMeta struct {
// Kind is a string value representing the REST resource this object represents.
// Servers may infer this from the endpoint the client submits requests to.
// Cannot be updated.
// In CamelCase.
// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#types-kinds
Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"`
// APIVersion defines the versioned schema of this representation of an object.
// Servers should convert recognized schemas to the latest internal value, and
// may reject unrecognized values.
// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#resources
APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"`
}
// ListMeta describes metadata that synthetic resources must have, including lists and
// various status objects. A resource may have only one of {ObjectMeta, ListMeta}.
type ListMeta struct {
// SelfLink is a URL representing this object.
// Populated by the system.
// Read-only.
SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,1,opt,name=selfLink"`
// String that identifies the server's internal version of this object that
// can be used by clients to determine when objects have changed.
// Value must be treated as opaque by clients and passed unmodified back to the server.
// Populated by the system.
// Read-only.
// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#concurrency-control-and-consistency
ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,2,opt,name=resourceVersion"`
}

View file

@ -0,0 +1,162 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
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 k8sapi
// ObjectMeta is metadata that all persisted resources must have, which includes all objects
// users must create.
type ObjectMeta struct {
// Name must be unique within a namespace. Is required when creating resources, although
// some resources may allow a client to request the generation of an appropriate name
// automatically. Name is primarily intended for creation idempotence and configuration
// definition.
// Cannot be updated.
// More info: http://releases.k8s.io/release-1.3/docs/user-guide/identifiers.md#names
Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
// GenerateName is an optional prefix, used by the server, to generate a unique
// name ONLY IF the Name field has not been provided.
// If this field is used, the name returned to the client will be different
// than the name passed. This value will also be combined with a unique suffix.
// The provided value has the same validation rules as the Name field,
// and may be truncated by the length of the suffix required to make the value
// unique on the server.
//
// If this field is specified and the generated name exists, the server will
// NOT return a 409 - instead, it will either return 201 Created or 500 with Reason
// ServerTimeout indicating a unique name could not be found in the time allotted, and the client
// should retry (optionally after the time indicated in the Retry-After header).
//
// Applied only if Name is not specified.
// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#idempotency
GenerateName string `json:"generateName,omitempty" protobuf:"bytes,2,opt,name=generateName"`
// Namespace defines the space within each name must be unique. An empty namespace is
// equivalent to the "default" namespace, but "default" is the canonical representation.
// Not all objects are required to be scoped to a namespace - the value of this field for
// those objects will be empty.
//
// Must be a DNS_LABEL.
// Cannot be updated.
// More info: http://releases.k8s.io/release-1.3/docs/user-guide/namespaces.md
Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"`
// SelfLink is a URL representing this object.
// Populated by the system.
// Read-only.
SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,4,opt,name=selfLink"`
// UID is the unique in time and space value for this object. It is typically generated by
// the server on successful creation of a resource and is not allowed to change on PUT
// operations.
//
// Populated by the system.
// Read-only.
// More info: http://releases.k8s.io/release-1.3/docs/user-guide/identifiers.md#uids
UID string `json:"uid,omitempty" protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"`
// An opaque value that represents the internal version of this object that can
// be used by clients to determine when objects have changed. May be used for optimistic
// concurrency, change detection, and the watch operation on a resource or set of resources.
// Clients must treat these values as opaque and passed unmodified back to the server.
// They may only be valid for a particular resource or set of resources.
//
// Populated by the system.
// Read-only.
// Value must be treated as opaque by clients and .
// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#concurrency-control-and-consistency
ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"`
// A sequence number representing a specific generation of the desired state.
// Populated by the system. Read-only.
Generation int64 `json:"generation,omitempty" protobuf:"varint,7,opt,name=generation"`
// CreationTimestamp is a timestamp representing the server time when this object was
// created. It is not guaranteed to be set in happens-before order across separate operations.
// Clients may not set this value. It is represented in RFC3339 form and is in UTC.
//
// Populated by the system.
// Read-only.
// Null for lists.
// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#metadata
CreationTimestamp Time `json:"creationTimestamp,omitempty" protobuf:"bytes,8,opt,name=creationTimestamp"`
// DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This
// field is set by the server when a graceful deletion is requested by the user, and is not
// directly settable by a client. The resource will be deleted (no longer visible from
// resource lists, and not reachable by name) after the time in this field. Once set, this
// value may not be unset or be set further into the future, although it may be shortened
// or the resource may be deleted prior to this time. For example, a user may request that
// a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination
// signal to the containers in the pod. Once the resource is deleted in the API, the Kubelet
// will send a hard termination signal to the container.
// If not set, graceful deletion of the object has not been requested.
//
// Populated by the system when a graceful deletion is requested.
// Read-only.
// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#metadata
DeletionTimestamp *Time `json:"deletionTimestamp,omitempty" protobuf:"bytes,9,opt,name=deletionTimestamp"`
// Number of seconds allowed for this object to gracefully terminate before
// it will be removed from the system. Only set when deletionTimestamp is also set.
// May only be shortened.
// Read-only.
DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty" protobuf:"varint,10,opt,name=deletionGracePeriodSeconds"`
// Map of string keys and values that can be used to organize and categorize
// (scope and select) objects. May match selectors of replication controllers
// and services.
// More info: http://releases.k8s.io/release-1.3/docs/user-guide/labels.md
// TODO: replace map[string]string with labels.LabelSet type
Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"`
// Annotations is an unstructured key value map stored with a resource that may be
// set by external tools to store and retrieve arbitrary metadata. They are not
// queryable and should be preserved when modifying objects.
// More info: http://releases.k8s.io/release-1.3/docs/user-guide/annotations.md
Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"`
// List of objects depended by this object. If ALL objects in the list have
// been deleted, this object will be garbage collected. If this object is managed by a controller,
// then an entry in this list will point to this controller, with the controller field set to true.
// There cannot be more than one managing controller.
OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid" protobuf:"bytes,13,rep,name=ownerReferences"`
// Must be empty before the object is deleted from the registry. Each entry
// is an identifier for the responsible component that will remove the entry
// from the list. If the deletionTimestamp of the object is non-nil, entries
// in this list can only be removed.
Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"`
}
// OwnerReference contains enough information to let you identify an owning
// object. Currently, an owning object must be in the same namespace, so there
// is no namespace field.
type OwnerReference struct {
// API version of the referent.
APIVersion string `json:"apiVersion" protobuf:"bytes,5,opt,name=apiVersion"`
// Kind of the referent.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds
Kind string `json:"kind" protobuf:"bytes,1,opt,name=kind"`
// Name of the referent.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#names
Name string `json:"name" protobuf:"bytes,3,opt,name=name"`
// UID of the referent.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#uids
UID string `json:"uid" protobuf:"bytes,4,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"`
// If true, this reference points to the managing controller.
Controller *bool `json:"controller,omitempty" protobuf:"varint,6,opt,name=controller"`
}

View file

@ -0,0 +1,234 @@
package kubernetes
import (
"errors"
"fmt"
"os"
"path/filepath"
homedir "github.com/mitchellh/go-homedir"
"github.com/coreos/poke/storage"
"github.com/coreos/poke/storage/kubernetes/k8sapi"
)
const (
kindAuthCode = "AuthCode"
kindAuthRequest = "AuthRequest"
kindClient = "OAuth2Client"
kindRefreshToken = "RefreshToken"
kindKeys = "SigningKey"
)
const (
resourceAuthCode = "authcodes"
resourceAuthRequest = "authrequests"
resourceClient = "oauth2clients"
resourceRefreshToken = "refreshtokens"
resourceKeys = "signingkeies" // Kubernetes attempts to pluralize.
)
// Config values for the Kubernetes storage type.
type Config struct {
InCluster bool `yaml:"inCluster"`
KubeConfigPath string `yaml:"kubeConfigPath"`
}
// Open returns a storage using Kubernetes third party resource.
func (c *Config) Open() (storage.Storage, error) {
if c.InCluster && (c.KubeConfigPath != "") {
return nil, errors.New("cannot specify both 'inCluster' and 'kubeConfigPath'")
}
var (
cluster k8sapi.Cluster
user k8sapi.AuthInfo
namespace string
err error
)
if c.InCluster {
cluster, user, namespace, err = inClusterConfig()
} else {
kubeConfigPath := c.KubeConfigPath
if kubeConfigPath == "" {
kubeConfigPath = os.Getenv("KUBECONFIG")
}
if kubeConfigPath == "" {
p, err := homedir.Dir()
if err != nil {
return nil, fmt.Errorf("finding homedir: %v", err)
}
kubeConfigPath = filepath.Join(p, ".kube", "config")
}
cluster, user, namespace, err = loadKubeConfig(kubeConfigPath)
}
if err != nil {
return nil, err
}
return newClient(cluster, user, namespace)
}
func (cli *client) Close() error {
return nil
}
func (cli *client) CreateAuthRequest(a storage.AuthRequest) error {
return cli.post(resourceAuthRequest, cli.fromStorageAuthRequest(a))
}
func (cli *client) CreateClient(c storage.Client) error {
return cli.post(resourceClient, cli.fromStorageClient(c))
}
func (cli *client) CreateAuthCode(c storage.AuthCode) error {
return cli.post(resourceAuthCode, cli.fromStorageAuthCode(c))
}
func (cli *client) CreateRefresh(r storage.Refresh) error {
refresh := Refresh{
TypeMeta: k8sapi.TypeMeta{
Kind: kindRefreshToken,
APIVersion: cli.apiVersionForResource(resourceRefreshToken),
},
ObjectMeta: k8sapi.ObjectMeta{
Name: r.RefreshToken,
Namespace: cli.namespace,
},
ClientID: r.ClientID,
ConnectorID: r.ConnectorID,
Scopes: r.Scopes,
Nonce: r.Nonce,
Identity: fromStorageIdentity(r.Identity),
}
return cli.post(resourceRefreshToken, refresh)
}
func (cli *client) GetAuthRequest(id string) (storage.AuthRequest, error) {
var req AuthRequest
if err := cli.get(resourceAuthRequest, id, &req); err != nil {
return storage.AuthRequest{}, err
}
return toStorageAuthRequest(req), nil
}
func (cli *client) GetAuthCode(id string) (storage.AuthCode, error) {
var code AuthCode
if err := cli.get(resourceAuthCode, id, &code); err != nil {
return storage.AuthCode{}, err
}
return toStorageAuthCode(code), nil
}
func (cli *client) GetClient(id string) (storage.Client, error) {
var c Client
if err := cli.get(resourceClient, id, &c); err != nil {
return storage.Client{}, err
}
return toStorageClient(c), nil
}
func (cli *client) GetKeys() (storage.Keys, error) {
var keys Keys
if err := cli.get(resourceKeys, keysName, &keys); err != nil {
return storage.Keys{}, err
}
return toStorageKeys(keys), nil
}
func (cli *client) GetRefresh(id string) (storage.Refresh, error) {
var r Refresh
if err := cli.get(resourceRefreshToken, id, &r); err != nil {
return storage.Refresh{}, err
}
return storage.Refresh{
RefreshToken: r.ObjectMeta.Name,
ClientID: r.ClientID,
ConnectorID: r.ConnectorID,
Scopes: r.Scopes,
Nonce: r.Nonce,
Identity: toStorageIdentity(r.Identity),
}, nil
}
func (cli *client) ListClients() ([]storage.Client, error) {
return nil, errors.New("not implemented")
}
func (cli *client) ListRefreshTokens() ([]storage.Refresh, error) {
return nil, errors.New("not implemented")
}
func (cli *client) DeleteAuthRequest(id string) error {
return cli.delete(resourceAuthRequest, id)
}
func (cli *client) DeleteAuthCode(code string) error {
return cli.delete(resourceAuthCode, code)
}
func (cli *client) DeleteClient(id string) error {
return cli.delete(resourceClient, id)
}
func (cli *client) DeleteRefresh(id string) error {
return cli.delete(resourceRefreshToken, id)
}
func (cli *client) UpdateClient(id string, updater func(old storage.Client) (storage.Client, error)) error {
var c Client
if err := cli.get(resourceClient, id, &c); err != nil {
return err
}
updated, err := updater(toStorageClient(c))
if err != nil {
return err
}
newClient := cli.fromStorageClient(updated)
newClient.ObjectMeta = c.ObjectMeta
return cli.put(resourceClient, id, newClient)
}
func (cli *client) UpdateKeys(updater func(old storage.Keys) (storage.Keys, error)) error {
firstUpdate := false
var keys Keys
if err := cli.get(resourceKeys, keysName, &keys); err != nil {
if err != storage.ErrNotFound {
return err
}
firstUpdate = true
}
var oldKeys storage.Keys
if !firstUpdate {
oldKeys = toStorageKeys(keys)
}
updated, err := updater(oldKeys)
if err != nil {
return err
}
newKeys := cli.fromStorageKeys(updated)
if firstUpdate {
return cli.post(resourceKeys, newKeys)
}
newKeys.ObjectMeta = keys.ObjectMeta
return cli.put(resourceKeys, keysName, newKeys)
}
func (cli *client) UpdateAuthRequest(id string, updater func(a storage.AuthRequest) (storage.AuthRequest, error)) error {
var req AuthRequest
err := cli.get(resourceAuthRequest, id, &req)
if err != nil {
return err
}
updated, err := updater(toStorageAuthRequest(req))
if err != nil {
return err
}
newReq := cli.fromStorageAuthRequest(updated)
newReq.ObjectMeta = req.ObjectMeta
return cli.put(resourceAuthRequest, id, newReq)
}

View file

@ -0,0 +1,78 @@
package kubernetes
import (
"os"
"testing"
"github.com/coreos/poke/storage"
"github.com/coreos/poke/storage/storagetest"
)
func TestLoadClient(t *testing.T) {
loadClient(t)
}
func loadClient(t *testing.T) storage.Storage {
if os.Getenv("KUBECONFIG") == "" {
t.Skip()
}
var config Config
s, err := config.Open()
if err != nil {
t.Fatal(err)
}
return s
}
func TestURLFor(t *testing.T) {
tests := []struct {
apiVersion, namespace, resource, name string
baseURL string
want string
}{
{
"v1", "default", "pods", "a",
"https://k8s.example.com",
"https://k8s.example.com/api/v1/namespaces/default/pods/a",
},
{
"foo/v1", "default", "bar", "a",
"https://k8s.example.com",
"https://k8s.example.com/apis/foo/v1/namespaces/default/bar/a",
},
{
"foo/v1", "default", "bar", "a",
"https://k8s.example.com/",
"https://k8s.example.com/apis/foo/v1/namespaces/default/bar/a",
},
{
"foo/v1", "default", "bar", "a",
"https://k8s.example.com/",
"https://k8s.example.com/apis/foo/v1/namespaces/default/bar/a",
},
{
// no namespace
"foo/v1", "", "bar", "a",
"https://k8s.example.com",
"https://k8s.example.com/apis/foo/v1/bar/a",
},
}
for _, test := range tests {
c := &client{baseURL: test.baseURL, prependResourceNameToAPIGroup: false}
got := c.urlFor(test.apiVersion, test.namespace, test.resource, test.name)
if got != test.want {
t.Errorf("(&client{baseURL:%q}).urlFor(%q, %q, %q, %q): expected %q got %q",
test.baseURL,
test.apiVersion, test.namespace, test.resource, test.name,
test.want, got,
)
}
}
}
func TestStorage(t *testing.T) {
client := loadClient(t)
storagetest.RunTestSuite(t, client)
}

309
storage/kubernetes/types.go Normal file
View file

@ -0,0 +1,309 @@
package kubernetes
import (
"time"
jose "gopkg.in/square/go-jose.v2"
"github.com/coreos/poke/storage"
"github.com/coreos/poke/storage/kubernetes/k8sapi"
)
// There will only ever be a single keys resource. Maintain this by setting a
// common name.
const keysName = "openid-connect-keys"
// Client is a mirrored struct from storage with JSON struct tags and
// Kubernetes type metadata.
type Client struct {
k8sapi.TypeMeta `json:",inline"`
k8sapi.ObjectMeta `json:"metadata,omitempty"`
Secret string `json:"secret,omitempty"`
RedirectURIs []string `json:"redirectURIs,omitempty"`
TrustedPeers []string `json:"trustedPeers,omitempty"`
Public bool `json:"public"`
Name string `json:"name,omitempty"`
LogoURL string `json:"logoURL,omitempty"`
}
// ClientList is a list of Clients.
type ClientList struct {
k8sapi.TypeMeta `json:",inline"`
k8sapi.ListMeta `json:"metadata,omitempty"`
Clients []Client `json:"items"`
}
func (cli *client) fromStorageClient(c storage.Client) Client {
return Client{
TypeMeta: k8sapi.TypeMeta{
Kind: kindClient,
APIVersion: cli.apiVersionForResource(resourceClient),
},
ObjectMeta: k8sapi.ObjectMeta{
Name: c.ID,
Namespace: cli.namespace,
},
Secret: c.Secret,
RedirectURIs: c.RedirectURIs,
TrustedPeers: c.TrustedPeers,
Public: c.Public,
Name: c.Name,
LogoURL: c.LogoURL,
}
}
func toStorageClient(c Client) storage.Client {
return storage.Client{
ID: c.ObjectMeta.Name,
Secret: c.Secret,
RedirectURIs: c.RedirectURIs,
TrustedPeers: c.TrustedPeers,
Public: c.Public,
Name: c.Name,
LogoURL: c.LogoURL,
}
}
// Identity is a mirrored struct from storage with JSON struct tags.
type Identity struct {
UserID string `json:"userID"`
Username string `json:"username"`
Email string `json:"email"`
EmailVerified bool `json:"emailVerified"`
Groups []string `json:"groups,omitempty"`
ConnectorData []byte `json:"connectorData,omitempty"`
}
func fromStorageIdentity(i storage.Identity) Identity {
return Identity{
UserID: i.UserID,
Username: i.Username,
Email: i.Email,
EmailVerified: i.EmailVerified,
Groups: i.Groups,
ConnectorData: i.ConnectorData,
}
}
func toStorageIdentity(i Identity) storage.Identity {
return storage.Identity{
UserID: i.UserID,
Username: i.Username,
Email: i.Email,
EmailVerified: i.EmailVerified,
Groups: i.Groups,
ConnectorData: i.ConnectorData,
}
}
// AuthRequest is a mirrored struct from storage with JSON struct tags and
// Kubernetes type metadata.
type AuthRequest struct {
k8sapi.TypeMeta `json:",inline"`
k8sapi.ObjectMeta `json:"metadata,omitempty"`
ClientID string `json:"clientID"`
ResponseTypes []string `json:"responseTypes,omitempty"`
Scopes []string `json:"scopes,omitempty"`
RedirectURI string `json:"redirectURI"`
Nonce string `json:"nonce,omitempty"`
State string `json:"state,omitempty"`
// The client has indicated that the end user must be shown an approval prompt
// on all requests. The server cannot cache their initial action for subsequent
// attempts.
ForceApprovalPrompt bool `json:"forceApprovalPrompt,omitempty"`
// The identity of the end user. Generally nil until the user authenticates
// with a backend.
Identity *Identity `json:"identity,omitempty"`
// The connector used to login the user. Set when the user authenticates.
ConnectorID string `json:"connectorID,omitempty"`
Expiry time.Time `json:"expiry"`
}
// AuthRequestList is a list of AuthRequests.
type AuthRequestList struct {
k8sapi.TypeMeta `json:",inline"`
k8sapi.ListMeta `json:"metadata,omitempty"`
AuthRequests []AuthRequest `json:"items"`
}
func toStorageAuthRequest(req AuthRequest) storage.AuthRequest {
a := storage.AuthRequest{
ID: req.ObjectMeta.Name,
ClientID: req.ClientID,
ResponseTypes: req.ResponseTypes,
Scopes: req.Scopes,
RedirectURI: req.RedirectURI,
Nonce: req.Nonce,
State: req.State,
ForceApprovalPrompt: req.ForceApprovalPrompt,
ConnectorID: req.ConnectorID,
Expiry: req.Expiry,
}
if req.Identity != nil {
i := toStorageIdentity(*req.Identity)
a.Identity = &i
}
return a
}
func (cli *client) fromStorageAuthRequest(a storage.AuthRequest) AuthRequest {
req := AuthRequest{
TypeMeta: k8sapi.TypeMeta{
Kind: kindAuthRequest,
APIVersion: cli.apiVersionForResource(resourceAuthRequest),
},
ObjectMeta: k8sapi.ObjectMeta{
Name: a.ID,
Namespace: cli.namespace,
},
ClientID: a.ClientID,
ResponseTypes: a.ResponseTypes,
Scopes: a.Scopes,
RedirectURI: a.RedirectURI,
Nonce: a.Nonce,
State: a.State,
ForceApprovalPrompt: a.ForceApprovalPrompt,
ConnectorID: a.ConnectorID,
Expiry: a.Expiry,
}
if a.Identity != nil {
i := fromStorageIdentity(*a.Identity)
req.Identity = &i
}
return req
}
// AuthCode is a mirrored struct from storage with JSON struct tags and
// Kubernetes type metadata.
type AuthCode struct {
k8sapi.TypeMeta `json:",inline"`
k8sapi.ObjectMeta `json:"metadata,omitempty"`
ClientID string `json:"clientID"`
Scopes []string `json:"scopes,omitempty"`
RedirectURI string `json:"redirectURI"`
Nonce string `json:"nonce,omitempty"`
State string `json:"state,omitempty"`
Identity Identity `json:"identity,omitempty"`
ConnectorID string `json:"connectorID,omitempty"`
Expiry time.Time `json:"expiry"`
}
// AuthCodeList is a list of AuthCodes.
type AuthCodeList struct {
k8sapi.TypeMeta `json:",inline"`
k8sapi.ListMeta `json:"metadata,omitempty"`
AuthCodes []AuthCode `json:"items"`
}
func (cli *client) fromStorageAuthCode(a storage.AuthCode) AuthCode {
return AuthCode{
TypeMeta: k8sapi.TypeMeta{
Kind: kindAuthCode,
APIVersion: cli.apiVersionForResource(resourceAuthCode),
},
ObjectMeta: k8sapi.ObjectMeta{
Name: a.ID,
Namespace: cli.namespace,
},
ClientID: a.ClientID,
RedirectURI: a.RedirectURI,
ConnectorID: a.ConnectorID,
Nonce: a.Nonce,
Scopes: a.Scopes,
Identity: fromStorageIdentity(a.Identity),
Expiry: a.Expiry,
}
}
func toStorageAuthCode(a AuthCode) storage.AuthCode {
return storage.AuthCode{
ID: a.ObjectMeta.Name,
ClientID: a.ClientID,
RedirectURI: a.RedirectURI,
ConnectorID: a.ConnectorID,
Nonce: a.Nonce,
Scopes: a.Scopes,
Identity: toStorageIdentity(a.Identity),
Expiry: a.Expiry,
}
}
// Refresh is a mirrored struct from storage with JSON struct tags and
// Kubernetes type metadata.
type Refresh struct {
k8sapi.TypeMeta `json:",inline"`
k8sapi.ObjectMeta `json:"metadata,omitempty"`
ClientID string `json:"clientID"`
Scopes []string `json:"scopes,omitempty"`
Nonce string `json:"nonce,omitempty"`
Identity Identity `json:"identity,omitempty"`
ConnectorID string `json:"connectorID,omitempty"`
}
// RefreshList is a list of refresh tokens.
type RefreshList struct {
k8sapi.TypeMeta `json:",inline"`
k8sapi.ListMeta `json:"metadata,omitempty"`
RefreshTokens []Refresh `json:"items"`
}
// Keys is a mirrored struct from storage with JSON struct tags and Kubernetes
// type metadata.
type Keys struct {
k8sapi.TypeMeta `json:",inline"`
k8sapi.ObjectMeta `json:"metadata,omitempty"`
// Key for creating and verifying signatures. These may be nil.
SigningKey *jose.JSONWebKey `json:"signingKey,omitempty"`
SigningKeyPub *jose.JSONWebKey `json:"signingKeyPub,omitempty"`
// Old signing keys which have been rotated but can still be used to validate
// existing signatures.
VerificationKeys []storage.VerificationKey `json:"verificationKeys,omitempty"`
// The next time the signing key will rotate.
//
// For caching purposes, implementations MUST NOT update keys before this time.
NextRotation time.Time `json:"nextRotation"`
}
func (cli *client) fromStorageKeys(keys storage.Keys) Keys {
return Keys{
TypeMeta: k8sapi.TypeMeta{
Kind: kindKeys,
APIVersion: cli.apiVersionForResource(resourceKeys),
},
ObjectMeta: k8sapi.ObjectMeta{
Name: keysName,
Namespace: cli.namespace,
},
SigningKey: keys.SigningKey,
SigningKeyPub: keys.SigningKeyPub,
VerificationKeys: keys.VerificationKeys,
NextRotation: keys.NextRotation,
}
}
func toStorageKeys(keys Keys) storage.Keys {
return storage.Keys{
SigningKey: keys.SigningKey,
SigningKeyPub: keys.SigningKeyPub,
VerificationKeys: keys.VerificationKeys,
NextRotation: keys.NextRotation,
}
}

244
storage/memory/memory.go Normal file
View file

@ -0,0 +1,244 @@
// Package memory provides an in memory implementation of the storage interface.
package memory
import (
"errors"
"sync"
"github.com/coreos/poke/storage"
)
func init() {
storage.Register("memory", new(driver))
}
// New returns an in memory storage.
func New() storage.Storage {
return &memStorage{
clients: make(map[string]storage.Client),
authCodes: make(map[string]storage.AuthCode),
refreshTokens: make(map[string]storage.Refresh),
authReqs: make(map[string]storage.AuthRequest),
}
}
type driver struct{}
func (f *driver) Open(config map[string]string) (storage.Storage, error) {
if len(config) != 0 {
return nil, errors.New("in memory storage does not take any arguments")
}
return New(), nil
}
type memStorage struct {
mu sync.Mutex
clients map[string]storage.Client
authCodes map[string]storage.AuthCode
refreshTokens map[string]storage.Refresh
authReqs map[string]storage.AuthRequest
keys storage.Keys
}
func (s *memStorage) tx(f func()) {
s.mu.Lock()
defer s.mu.Unlock()
f()
}
var errAlreadyExists = errors.New("already exists")
func (s *memStorage) Close() error { return nil }
func (s *memStorage) CreateClient(c storage.Client) error {
s.tx(func() { s.clients[c.ID] = c })
return nil
}
func (s *memStorage) CreateAuthCode(c storage.AuthCode) error {
s.tx(func() { s.authCodes[c.ID] = c })
return nil
}
func (s *memStorage) CreateRefresh(r storage.Refresh) error {
s.tx(func() { s.refreshTokens[r.RefreshToken] = r })
return nil
}
func (s *memStorage) CreateAuthRequest(a storage.AuthRequest) error {
s.tx(func() { s.authReqs[a.ID] = a })
return nil
}
func (s *memStorage) GetClient(id string) (client storage.Client, err error) {
s.tx(func() {
var ok bool
if client, ok = s.clients[id]; !ok {
err = storage.ErrNotFound
}
})
return
}
func (s *memStorage) GetKeys() (keys storage.Keys, err error) {
s.tx(func() { keys = s.keys })
return
}
func (s *memStorage) GetRefresh(token string) (tok storage.Refresh, err error) {
s.tx(func() {
var ok bool
if tok, ok = s.refreshTokens[token]; !ok {
err = storage.ErrNotFound
return
}
})
return
}
func (s *memStorage) GetAuthRequest(id string) (req storage.AuthRequest, err error) {
s.tx(func() {
var ok bool
if req, ok = s.authReqs[id]; !ok {
err = storage.ErrNotFound
return
}
})
return
}
func (s *memStorage) ListClients() (clients []storage.Client, err error) {
s.tx(func() {
for _, client := range s.clients {
clients = append(clients, client)
}
})
return
}
func (s *memStorage) ListRefreshTokens() (tokens []storage.Refresh, err error) {
s.tx(func() {
for _, refresh := range s.refreshTokens {
tokens = append(tokens, refresh)
}
})
return
}
func (s *memStorage) DeleteClient(id string) (err error) {
s.tx(func() {
if _, ok := s.clients[id]; !ok {
err = storage.ErrNotFound
return
}
delete(s.clients, id)
})
return
}
func (s *memStorage) DeleteRefresh(token string) (err error) {
s.tx(func() {
if _, ok := s.refreshTokens[token]; !ok {
err = storage.ErrNotFound
return
}
delete(s.refreshTokens, token)
})
return
}
func (s *memStorage) DeleteAuthCode(id string) (err error) {
s.tx(func() {
if _, ok := s.authCodes[id]; !ok {
err = storage.ErrNotFound
return
}
delete(s.authCodes, id)
})
return
}
func (s *memStorage) DeleteAuthRequest(id string) (err error) {
s.tx(func() {
if _, ok := s.authReqs[id]; !ok {
err = storage.ErrNotFound
return
}
delete(s.authReqs, id)
})
return
}
func (s *memStorage) GetAuthCode(id string) (c storage.AuthCode, err error) {
s.tx(func() {
var ok bool
if c, ok = s.authCodes[id]; !ok {
err = storage.ErrNotFound
return
}
})
return
}
func (s *memStorage) ClaimCode(id string) (err error) {
s.tx(func() {
if _, ok := s.authCodes[id]; !ok {
err = storage.ErrNotFound
return
}
delete(s.authCodes, id)
})
return
}
func (s *memStorage) ClaimRefresh(refreshToken string) (token storage.Refresh, err error) {
s.tx(func() {
var ok bool
if token, ok = s.refreshTokens[refreshToken]; !ok {
err = storage.ErrNotFound
return
}
delete(s.refreshTokens, refreshToken)
})
return
}
func (s *memStorage) UpdateClient(id string, updater func(old storage.Client) (storage.Client, error)) (err error) {
s.tx(func() {
client, ok := s.clients[id]
if !ok {
err = storage.ErrNotFound
return
}
if client, err = updater(client); err == nil {
s.clients[id] = client
}
})
return
}
func (s *memStorage) UpdateKeys(updater func(old storage.Keys) (storage.Keys, error)) (err error) {
s.tx(func() {
var keys storage.Keys
if keys, err = updater(s.keys); err == nil {
s.keys = keys
}
})
return
}
func (s *memStorage) UpdateAuthRequest(id string, updater func(old storage.AuthRequest) (storage.AuthRequest, error)) (err error) {
s.tx(func() {
req, ok := s.authReqs[id]
if !ok {
err = storage.ErrNotFound
return
}
if req, err := updater(req); err == nil {
s.authReqs[id] = req
}
})
return
}

View file

@ -0,0 +1,12 @@
package memory
import (
"testing"
"github.com/coreos/poke/storage/storagetest"
)
func TestStorage(t *testing.T) {
s := New()
storagetest.RunTestSuite(t, s)
}

263
storage/storage.go Normal file
View file

@ -0,0 +1,263 @@
package storage
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"encoding/base32"
"errors"
"fmt"
"io"
"strings"
"time"
jose "gopkg.in/square/go-jose.v2"
)
var (
drivers = make(map[string]Driver)
// stubbed out for testing
now = time.Now
)
// ErrNotFound is the error returned by storages if a resource cannot be found.
var ErrNotFound = errors.New("not found")
// Kubernetes only allows lower case letters for names.
//
// TODO(ericchiang): refactor ID creation onto the storage.
var encoding = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567")
// NewNonce returns a new ID for the objects.
func NewNonce() string {
buff := make([]byte, 8) // 64 bit random ID.
if _, err := io.ReadFull(rand.Reader, buff); err != nil {
panic(err)
}
// Trim padding
return strings.TrimRight(encoding.EncodeToString(buff), "=")
}
// Driver is the interface implemented by storage drivers.
type Driver interface {
// Open returns a storage implementation. It should only validate its
// arguments and not return an error if the underlying storage is
// unavailable.
Open(config map[string]string) (Storage, error)
}
// Register makes a storage driver available by the provided name. If Register
// is called twice with the same name or if driver is nil, it panics.
func Register(name string, driver Driver) {
if driver == nil {
panic("driver cannot be nil")
}
if _, ok := drivers[name]; ok {
panic("driver " + name + " is already registered")
}
drivers[name] = driver
}
// Open returns a new storage object with a given key rotation strategy.
func Open(driverName string, config map[string]string) (Storage, error) {
driver, ok := drivers[driverName]
if !ok {
return nil, fmt.Errorf("no driver of type %s found", driverName)
}
return driver.Open(config)
}
// Storage is the storage interface used by the server. Implementations, at minimum
// require compare-and-swap atomic actions.
//
// Implementations are expected to perform their own garbage collection of
// expired objects (expect keys which are handled by rotation).
type Storage interface {
Close() error
CreateAuthRequest(a AuthRequest) error
CreateClient(c Client) error
CreateAuthCode(c AuthCode) error
CreateRefresh(r Refresh) error
// TODO(ericchiang): return (T, bool, error) so we can indicate not found
// requests that way.
GetAuthRequest(id string) (AuthRequest, error)
GetAuthCode(id string) (AuthCode, error)
GetClient(id string) (Client, error)
GetKeys() (Keys, error)
GetRefresh(id string) (Refresh, error)
ListClients() ([]Client, error)
ListRefreshTokens() ([]Refresh, error)
// Delete methods MUST be atomic.
DeleteAuthRequest(id string) error
DeleteAuthCode(code string) error
DeleteClient(id string) error
DeleteRefresh(id string) error
// Update functions are assumed to be a performed within a single object transaction.
UpdateClient(id string, updater func(old Client) (Client, error)) error
UpdateKeys(updater func(old Keys) (Keys, error)) error
UpdateAuthRequest(id string, updater func(a AuthRequest) (AuthRequest, error)) error
}
// Client is an OAuth2 client.
//
// For further reading see:
// * Trusted peers: https://developers.google.com/identity/protocols/CrossClientAuth
// * Public clients: https://developers.google.com/api-client-library/python/auth/installed-app
type Client struct {
ID string
Secret string
RedirectURIs []string
// TrustedPeers are a list of peers which can issue tokens on this client's behalf.
// Clients inherently trust themselves.
TrustedPeers []string
// Public clients must use either use a redirectURL 127.0.0.1:X or "urn:ietf:wg:oauth:2.0:oob"
Public bool
Name string
LogoURL string
}
// Identity represents the ID Token claims supported by the server.
type Identity struct {
UserID string
Username string
Email string
EmailVerified bool
Groups []string
// ConnectorData holds data used by the connector for subsequent requests after initial
// authentication, such as access tokens for upstream provides.
//
// This data is never shared with end users, OAuth clients, or through the API.
ConnectorData []byte
}
// AuthRequest represents a OAuth2 client authorization request. It holds the state
// of a single auth flow up to the point that the user authorizes the client.
type AuthRequest struct {
ID string
ClientID string
ResponseTypes []string
Scopes []string
RedirectURI string
Nonce string
State string
// The client has indicated that the end user must be shown an approval prompt
// on all requests. The server cannot cache their initial action for subsequent
// attempts.
ForceApprovalPrompt bool
// The identity of the end user. Generally nil until the user authenticates
// with a backend.
Identity *Identity
// The connector used to login the user. Set when the user authenticates.
ConnectorID string
Expiry time.Time
}
// AuthCode represents a code which can be exchanged for an OAuth2 token response.
type AuthCode struct {
ID string
ClientID string
RedirectURI string
ConnectorID string
Nonce string
Scopes []string
Identity Identity
Expiry time.Time
}
// Refresh is an OAuth2 refresh token.
type Refresh struct {
// The actual refresh token.
RefreshToken string
// Client this refresh token is valid for.
ClientID string
ConnectorID string
// Scopes present in the initial request. Refresh requests may specify a set
// of scopes different from the initial request when refreshing a token,
// however those scopes must be encompassed by this set.
Scopes []string
Nonce string
Identity Identity
}
// VerificationKey is a rotated signing key which can still be used to verify
// signatures.
type VerificationKey struct {
PublicKey *jose.JSONWebKey `json:"publicKey"`
Expiry time.Time `json:"expiry"`
}
// Keys hold encryption and signing keys.
type Keys struct {
// Key for creating and verifying signatures. These may be nil.
SigningKey *jose.JSONWebKey
SigningKeyPub *jose.JSONWebKey
// Old signing keys which have been rotated but can still be used to validate
// existing signatures.
VerificationKeys []VerificationKey
// The next time the signing key will rotate.
//
// For caching purposes, implementations MUST NOT update keys before this time.
NextRotation time.Time
}
// Sign creates a JWT using the signing key.
func (k Keys) Sign(payload []byte) (jws string, err error) {
if k.SigningKey == nil {
return "", fmt.Errorf("no key to sign payload with")
}
signingKey := jose.SigningKey{Key: k.SigningKey}
switch key := k.SigningKey.Key.(type) {
case *rsa.PrivateKey:
// TODO(ericchiang): Allow different cryptographic hashes.
signingKey.Algorithm = jose.RS256
case *ecdsa.PrivateKey:
switch key.Params() {
case elliptic.P256().Params():
signingKey.Algorithm = jose.ES256
case elliptic.P384().Params():
signingKey.Algorithm = jose.ES384
case elliptic.P521().Params():
signingKey.Algorithm = jose.ES512
default:
return "", errors.New("unsupported ecdsa curve")
}
}
signer, err := jose.NewSigner(signingKey, &jose.SignerOptions{})
if err != nil {
return "", fmt.Errorf("new signier: %v", err)
}
signature, err := signer.Sign(payload)
if err != nil {
return "", fmt.Errorf("signing payload: %v", err)
}
return signature.CompactSerialize()
}

View file

@ -0,0 +1,84 @@
// +build go1.7
// Package storagetest provides conformance tests for storage implementations.
package storagetest
import (
"reflect"
"testing"
"time"
"github.com/coreos/poke/storage"
)
var neverExpire = time.Now().Add(time.Hour * 24 * 365 * 100)
// RunTestSuite runs a set of conformance tests against a storage.
func RunTestSuite(t *testing.T, s storage.Storage) {
t.Run("UpdateAuthRequest", func(t *testing.T) { testUpdateAuthRequest(t, s) })
t.Run("CreateRefresh", func(t *testing.T) { testCreateRefresh(t, s) })
}
func testUpdateAuthRequest(t *testing.T, s storage.Storage) {
a := storage.AuthRequest{
ID: storage.NewNonce(),
ClientID: "foobar",
ResponseTypes: []string{"code"},
Scopes: []string{"openid", "email"},
RedirectURI: "https://localhost:80/callback",
Expiry: neverExpire,
}
identity := storage.Identity{Email: "foobar"}
if err := s.CreateAuthRequest(a); err != nil {
t.Fatalf("failed creating auth request: %v", err)
}
if err := s.UpdateAuthRequest(a.ID, func(old storage.AuthRequest) (storage.AuthRequest, error) {
old.Identity = &identity
old.ConnectorID = "connID"
return old, nil
}); err != nil {
t.Fatalf("failed to update auth request: %v", err)
}
got, err := s.GetAuthRequest(a.ID)
if err != nil {
t.Fatalf("failed to get auth req: %v", err)
}
if got.Identity == nil {
t.Fatalf("no identity in auth request")
}
if !reflect.DeepEqual(*got.Identity, identity) {
t.Fatalf("update failed, wanted identity=%#v got %#v", identity, *got.Identity)
}
}
func testCreateRefresh(t *testing.T, s storage.Storage) {
id := storage.NewNonce()
refresh := storage.Refresh{
RefreshToken: id,
ClientID: "client_id",
ConnectorID: "client_secret",
Scopes: []string{"openid", "email", "profile"},
}
if err := s.CreateRefresh(refresh); err != nil {
t.Fatalf("create refresh token: %v", err)
}
gotRefresh, err := s.GetRefresh(id)
if err != nil {
t.Fatalf("get refresh: %v", err)
}
if !reflect.DeepEqual(gotRefresh, refresh) {
t.Errorf("refresh returned did not match expected")
}
if err := s.DeleteRefresh(id); err != nil {
t.Fatalf("failed to delete refresh request: %v", err)
}
if _, err := s.GetRefresh(id); err != storage.ErrNotFound {
t.Errorf("after deleting refresh expected storage.ErrNotFound, got %v", err)
}
}

202
vendor/github.com/ericchiang/oidc/LICENSE generated vendored Normal file
View file

@ -0,0 +1,202 @@
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.

99
vendor/github.com/ericchiang/oidc/README.md generated vendored Normal file
View file

@ -0,0 +1,99 @@
# OpenID Connect client support for Go
[![GoDoc](https://godoc.org/github.com/ericchiang/oidc?status.svg)](https://godoc.org/github.com/ericchiang/oidc)
This package implements OpenID Connect client logic for the golang.org/x/oauth2 package.
```go
provider, err := oidc.NewProvider(ctx, "https://accounts.example.com")
if err != nil {
return err
}
// Configure an OpenID Connect aware OAuth2 client.
oauth2Config := oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
```
OAuth2 redirects are unchanged.
```go
func handleRedirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
})
```
For callbacks the provider can be used to query for [user information](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo) such as email.
```go
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
// Verify state...
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
userinfo, err := provider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token))
if err != nil {
http.Error(w, "Failed to get userinfo: "+err.Error(), http.StatusInternalServerError)
return
}
// ...
})
```
Or the provider can be used to verify and inspect the OpenID Connect
[ID Token](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) in the
[token response](https://openid.net/specs/openid-connect-core-1_0.html#TokenResponse).
```go
verifier := provider.NewVerifier(ctx)
```
The returned verifier can be used to ensure the ID Token (a JWT) is signed by the provider.
```go
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
// Verify state...
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
// Extract the ID Token from oauth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
http.Error(w, "No ID Token found", http.StatusInternalServerError)
return
}
// Verify that the ID Token is signed by the provider.
payload, err := verifier.Verify(rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
// Unmarshal ID Token for expected custom claims.
var idToken struct {
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
}
if err := json.Unmarshal(payload, &idToken); err != nil {
http.Error(w, "Failed to unmarshal ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
// ...
})
```

145
vendor/github.com/ericchiang/oidc/doc.go generated vendored Normal file
View file

@ -0,0 +1,145 @@
/*
Package oidc implements OpenID Connect client logic for the golang.org/x/oauth2 package.
provider, err := oidc.NewProvider(ctx, "https://accounts.example.com")
if err != nil {
return err
}
// Configure an OpenID Connect aware OAuth2 client.
oauth2Config := oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
OAuth2 redirects are unchanged.
func handleRedirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
})
For callbacks the provider can be used to query for user information such as email.
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
// Verify state...
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
userinfo, err := provider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token))
if err != nil {
http.Error(w, "Failed to get userinfo: "+err.Error(), http.StatusInternalServerError)
return
}
// ...
})
The provider also has the ability to verify ID Tokens.
verifier := provider.NewVerifier(ctx)
The returned verifier can be used to perform basic validation on ID Token issued by the provider,
including verifying the JWT signature. It then returns the payload.
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
// Verify state...
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
// Extract the ID Token from oauth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
http.Error(w, "No ID Token found", http.StatusInternalServerError)
return
}
// Verify that the ID Token is signed by the provider.
payload, err := verifier.Verify(rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
// Unmarshal ID Token for expected custom claims.
var idToken struct {
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
}
if err := json.Unmarshal(payload, &idToken); err != nil {
http.Error(w, "Failed to unmarshal ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
// ...
})
ID Token nonces are supported.
First, provide a nonce source for nonce validation. This will then be used to wrap the existing
provider ID Token verifier.
// A verifier which boths verifies the ID Token signature and nonce.
nonceEnabledVerifier := provider.NewVerifier(ctx, oidc.VerifyNonce(nonceSource))
For the redirect provide a nonce auth code option. This will be placed as a URL parameter during
the client redirect.
func handleRedirect(w http.ResponseWriter, r *http.Request) {
nonce, err := newNonce()
if err != nil {
// ...
}
// Provide a nonce for the OpenID Connect ID Token.
http.Redirect(w, r, oauth2Config.AuthCodeURL(state, oidc.Nonce(nonce)), http.StatusFound)
})
The nonce enabled verifier can then be used to verify the nonce while unpacking the ID Token.
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
// Verify state...
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
// Extract the ID Token from oauth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
http.Error(w, "No ID Token found", http.StatusInternalServerError)
return
}
// Verify that the ID Token is signed by the provider and verify the nonce.
payload, err := nonceEnabledVerifier.Verify(rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
// Continue as above...
})
This package uses contexts to derive HTTP clients in the same way as the oauth2 package. To configure
a custom client, use the oauth2 packages HTTPClient context key when constructing the context.
myClient := &http.Client{}
myCtx := context.WithValue(parentCtx, oauth2.HTTPClient, myClient)
// NewProvider will use myClient to make the request.
provider, err := oidc.NewProvider(myCtx, "https://accounts.example.com")
*/
package oidc

15
vendor/github.com/ericchiang/oidc/examples/README.md generated vendored Normal file
View file

@ -0,0 +1,15 @@
# Examples
These are example uses of the oidc package. Each requires a Google account and the
client ID and secret of a registered OAuth2 application. The client ID and secret
should be set as the following environment variables:
```
GOOGLE_OAUTH2_CLIENT_ID
GOOGLE_OAUTH2_CLIENT_SECRET
```
See Google's documentation on how to set up an OAuth2 app:
https://developers.google.com/identity/protocols/OpenIDConnect?hl=en
Note that one of the redirect URL's must be "http://127.0.0.1:5556/auth/google/callback"

View file

@ -0,0 +1,86 @@
/*
This is an example application to demonstrate parsing an ID Token.
*/
package main
import (
"encoding/json"
"log"
"net/http"
"os"
"github.com/ericchiang/oidc"
"golang.org/x/net/context"
"golang.org/x/oauth2"
)
var (
clientID = os.Getenv("GOOGLE_OAUTH2_CLIENT_ID")
clientSecret = os.Getenv("GOOGLE_OAUTH2_CLIENT_SECRET")
)
func main() {
ctx := context.Background()
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil {
log.Fatal(err)
}
verifier := provider.NewVerifier(ctx)
config := oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: provider.Endpoint(),
RedirectURL: "http://127.0.0.1:5556/auth/google/callback",
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
state := "foobar" // Don't do this in production.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, config.AuthCodeURL(state), http.StatusFound)
})
http.HandleFunc("/auth/google/callback", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("state") != state {
http.Error(w, "state did not match", http.StatusBadRequest)
return
}
oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
return
}
log.Println(rawIDToken)
idTokenPayload, err := verifier.Verify(rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
oauth2Token.AccessToken = "*REDACTED*"
rawMessage := json.RawMessage(idTokenPayload)
resp := struct {
OAuth2Token *oauth2.Token
IDTokenClaims *json.RawMessage // ID Token payload is just JSON.
}{oauth2Token, &rawMessage}
data, err := json.MarshalIndent(resp, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(data)
})
log.Printf("listening on http://%s/", "127.0.0.1:5556")
log.Fatal(http.ListenAndServe("127.0.0.1:5556", nil))
}

View file

@ -0,0 +1,99 @@
/*
This is an example application to demonstrate verifying an ID Token with a nonce.
*/
package main
import (
"encoding/json"
"errors"
"log"
"net/http"
"os"
"github.com/ericchiang/oidc"
"golang.org/x/net/context"
"golang.org/x/oauth2"
)
var (
clientID = os.Getenv("GOOGLE_OAUTH2_CLIENT_ID")
clientSecret = os.Getenv("GOOGLE_OAUTH2_CLIENT_SECRET")
)
const appNonce = "a super secret nonce"
// Create a nonce source.
type nonceSource struct{}
func (n nonceSource) ClaimNonce(nonce string) error {
if nonce != appNonce {
return errors.New("unregonized nonce")
}
return nil
}
func main() {
ctx := context.Background()
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil {
log.Fatal(err)
}
// Use the nonce source to create a custom ID Token verifier.
nonceEnabledVerifier := provider.NewVerifier(ctx, oidc.VerifyNonce(nonceSource{}))
config := oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: provider.Endpoint(),
RedirectURL: "http://127.0.0.1:5556/auth/google/callback",
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
state := "foobar" // Don't do this in production.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, config.AuthCodeURL(state, oidc.Nonce(appNonce)), http.StatusFound)
})
http.HandleFunc("/auth/google/callback", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("state") != state {
http.Error(w, "state did not match", http.StatusBadRequest)
return
}
oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
return
}
// Verify the ID Token signature and nonce.
idTokenPayload, err := nonceEnabledVerifier.Verify(rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
rawMessage := json.RawMessage(idTokenPayload)
resp := struct {
OAuth2Token *oauth2.Token
IDToken *json.RawMessage // ID Token payload is just JSON.
}{oauth2Token, &rawMessage}
data, err := json.MarshalIndent(resp, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(data)
})
log.Printf("listening on http://%s/", "127.0.0.1:5556")
log.Fatal(http.ListenAndServe("127.0.0.1:5556", nil))
}

View file

@ -0,0 +1,76 @@
/*
This is an example application to demonstrate querying the user info endpoint.
*/
package main
import (
"encoding/json"
"log"
"net/http"
"os"
"github.com/ericchiang/oidc"
"golang.org/x/net/context"
"golang.org/x/oauth2"
)
var (
clientID = os.Getenv("GOOGLE_OAUTH2_CLIENT_ID")
clientSecret = os.Getenv("GOOGLE_OAUTH2_CLIENT_SECRET")
)
func main() {
ctx := context.Background()
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil {
log.Fatal(err)
}
config := oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: provider.Endpoint(),
RedirectURL: "http://127.0.0.1:5556/auth/google/callback",
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
state := "foobar" // Don't do this in production.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, config.AuthCodeURL(state), http.StatusFound)
})
http.HandleFunc("/auth/google/callback", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("state") != state {
http.Error(w, "state did not match", http.StatusBadRequest)
return
}
oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
return
}
userInfo, err := provider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token))
if err != nil {
http.Error(w, "Failed to get userinfo: "+err.Error(), http.StatusInternalServerError)
return
}
resp := struct {
OAuth2Token *oauth2.Token
UserInfo *oidc.UserInfo
}{oauth2Token, userInfo}
data, err := json.MarshalIndent(resp, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(data)
})
log.Printf("listening on http://%s/", "127.0.0.1:5556")
log.Fatal(http.ListenAndServe("127.0.0.1:5556", nil))
}

7
vendor/github.com/ericchiang/oidc/internal/oidc.go generated vendored Normal file
View file

@ -0,0 +1,7 @@
// Package internal contains support packages for the oidc package.
package internal
// ContextKey is just an empty struct. It exists so context keys can be an immutable
// public variable with a unique type. It's immutable because nobody else can create
// a ContextKey, being unexported.
type ContextKey struct{}

188
vendor/github.com/ericchiang/oidc/jwks.go generated vendored Normal file
View file

@ -0,0 +1,188 @@
package oidc
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"sync"
"sync/atomic"
"time"
"github.com/pquerna/cachecontrol"
"golang.org/x/net/context"
jose "gopkg.in/square/go-jose.v1"
)
// No matter what insist on caching keys. This is so our request code can be
// asynchronous from matching keys. If the request code retrieved keys that
// expired immediately, the goroutine to match a JWT to a key would always see
// expired keys.
//
// TODO(ericchiang): Review this logic.
var minCache = 2 * time.Minute
type cachedKeys struct {
keys map[string]jose.JsonWebKey // immutable
expiry time.Time
}
type remoteKeySet struct {
client *http.Client
// "jwks_uri" from discovery.
keysURL string
// The value is always of type *cachedKeys.
//
// To ensure consistency always call keyCache.Store when holding cond.L.
keyCache atomic.Value
// cond.L guards all following fields. sync.Cond is used in place of a mutex
// so multiple processes can wait on a single request to update keys.
cond sync.Cond
// Is there an existing request to get the remote keys?
inflight bool
// If the last attempt to refresh keys failed, the error will be saved here.
//
// TODO(ericchiang): If a routine sets this before calling cond.Broadcast(),
// there's no guarentee that a routine calling cond.Wait() will actual see
// the error called by the previous routine. Since Broadcast() unlocks
// cond.L and Wait() must reacquire the lock, other routines waiting on the
// lock might acquire it first. Maybe just log the error?
lastErr error
}
func newRemoteKeySet(ctx context.Context, jwksURL string) *remoteKeySet {
r := &remoteKeySet{
client: contextClient(ctx),
keysURL: jwksURL,
cond: sync.Cond{L: new(sync.Mutex)},
}
return r
}
func (r *remoteKeySet) verifyJWT(jwt string) (payload []byte, err error) {
jws, err := jose.ParseSigned(jwt)
if err != nil {
return nil, fmt.Errorf("parsing jwt: %v", err)
}
keyIDs := make([]string, len(jws.Signatures))
for i, signature := range jws.Signatures {
keyIDs[i] = signature.Header.KeyID
}
key, err := r.getKey(keyIDs)
if err != nil {
return nil, fmt.Errorf("oidc: %s", err)
}
return jws.Verify(key)
}
func (r *remoteKeySet) getKeyFromCache(keyIDs []string) (*jose.JsonWebKey, bool) {
cachedKeys, ok := r.keyCache.Load().(*cachedKeys)
if !ok {
return nil, false
}
if time.Now().After(cachedKeys.expiry) {
return nil, false
}
for _, keyID := range keyIDs {
if key, ok := cachedKeys.keys[keyID]; ok {
return &key, true
}
}
return nil, false
}
func (r *remoteKeySet) getKey(keyIDs []string) (*jose.JsonWebKey, error) {
// Fast path. Just do an atomic load.
if key, ok := r.getKeyFromCache(keyIDs); ok {
return key, nil
}
// Didn't find keys, use the slow path.
r.cond.L.Lock()
defer r.cond.L.Unlock()
// Check again within the mutex.
if key, ok := r.getKeyFromCache(keyIDs); ok {
return key, nil
}
// Keys have expired or we're trying to verify a JWT we don't have a key for.
if !r.inflight {
// There isn't currently an inflight request to update keys, start a
// goroutine to do so.
r.inflight = true
go func() {
newKeys, newExpiry, err := requestKeys(r.client, r.keysURL)
r.cond.L.Lock()
defer r.cond.L.Unlock()
r.inflight = false
if err != nil {
r.lastErr = err
} else {
r.keyCache.Store(&cachedKeys{newKeys, newExpiry})
r.lastErr = nil
}
r.cond.Broadcast() // Wake all r.cond.Wait() calls.
}()
}
// Wait for r.cond.Broadcast() to be called. This unlocks r.cond.L and
// reacquires it after its done waiting.
r.cond.Wait()
if key, ok := r.getKeyFromCache(keyIDs); ok {
return key, nil
}
if r.lastErr != nil {
return nil, r.lastErr
}
return nil, errors.New("no signing keys can validate the signature")
}
func requestKeys(client *http.Client, keysURL string) (map[string]jose.JsonWebKey, time.Time, error) {
req, err := http.NewRequest("GET", keysURL, nil)
if err != nil {
return nil, time.Time{}, fmt.Errorf("can't create request: %v", err)
}
resp, err := client.Do(req)
if err != nil {
return nil, time.Time{}, fmt.Errorf("can't GET new keys %v", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
if err != nil {
return nil, time.Time{}, fmt.Errorf("can't fetch new keys: %v", err)
}
if resp.StatusCode != http.StatusOK {
return nil, time.Time{}, fmt.Errorf("can't fetch new keys: %s %s", resp.Status, body)
}
var keySet jose.JsonWebKeySet
if err := json.Unmarshal(body, &keySet); err != nil {
return nil, time.Time{}, fmt.Errorf("can't decode keys: %v %s", err, body)
}
keys := make(map[string]jose.JsonWebKey, len(keySet.Keys))
for _, key := range keySet.Keys {
keys[key.KeyID] = key
}
minExpiry := time.Now().Add(minCache)
if _, expiry, err := cachecontrol.CachableResponse(req, resp, cachecontrol.Options{}); err == nil {
if minExpiry.Before(expiry) {
return keys, expiry, nil
}
}
return keys, minExpiry, nil
}

285
vendor/github.com/ericchiang/oidc/jwks_test.go generated vendored Normal file

File diff suppressed because one or more lines are too long

43
vendor/github.com/ericchiang/oidc/nonce.go generated vendored Normal file
View file

@ -0,0 +1,43 @@
package oidc
import (
"encoding/json"
"errors"
"fmt"
"golang.org/x/oauth2"
)
// Nonce returns an auth code option which requires the ID Token created by the
// OpenID Connect provider to contain the specified nonce.
func Nonce(nonce string) oauth2.AuthCodeOption {
return oauth2.SetAuthURLParam("nonce", nonce)
}
// NonceSource represents a source which can verify a nonce is valid and has not
// been claimed before.
type NonceSource interface {
ClaimNonce(nonce string) error
}
// VerifyNonce ensures that the ID Token contains a nonce which can be claimed by the nonce source.
func VerifyNonce(source NonceSource) VerificationOption {
return nonceVerifier{source}
}
type nonceVerifier struct {
nonceSource NonceSource
}
func (n nonceVerifier) verifyIDTokenPayload(payload []byte) error {
var token struct {
Nonce string `json:"nonce"`
}
if err := json.Unmarshal(payload, &token); err != nil {
return fmt.Errorf("oidc: failed to unmarshal nonce: %v", err)
}
if token.Nonce == "" {
return errors.New("oidc: no nonce present in ID Token")
}
return n.nonceSource.ClaimNonce(token.Nonce)
}

236
vendor/github.com/ericchiang/oidc/oidc.go generated vendored Normal file
View file

@ -0,0 +1,236 @@
package oidc
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
"golang.org/x/net/context"
"golang.org/x/oauth2"
)
var (
// ErrTokenExpired indicates that a token parsed by a verifier has expired.
ErrTokenExpired = errors.New("ID Token expired")
// ErrNotSupported indicates that the requested optional OpenID Connect endpoint is not supported by the provider.
ErrNotSupported = errors.New("endpoint not supported")
)
const (
// ScopeOpenID is the mandatory scope for all OpenID Connect OAuth2 requests.
ScopeOpenID = "openid"
// ScopeOfflineAccess is an optional scope defined by OpenID Connect for requesting
// OAuth2 refresh tokens.
//
// Support for this scope differs between OpenID Connect providers. For instance
// Google rejects it, favoring appending "access_type=offline" as part of the
// authorization request instead.
//
// See: https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
ScopeOfflineAccess = "offline_access"
)
// Provider contains the subset of the OpenID Connect provider metadata needed to request
// and verify ID Tokens.
type Provider struct {
Issuer string `json:"issuer"`
AuthURL string `json:"authorization_endpoint"`
TokenURL string `json:"token_endpoint"`
JWKSURL string `json:"jwks_uri"`
UserInfoURL string `json:"userinfo_endpoint"`
// Optionally contains extra claims.
raw map[string]interface{}
}
// NewProvider uses the OpenID Connect disovery mechanism to construct a Provider.
func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration"
resp, err := contextClient(ctx).Get(wellKnown)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s: %s", resp.Status, body)
}
defer resp.Body.Close()
var p Provider
if err := json.Unmarshal(body, &p); err != nil {
return nil, fmt.Errorf("oidc: failed to decode provider discovery object: %v", err)
}
// raw claims do not get error checks
json.Unmarshal(body, &p.raw)
if p.Issuer != issuer {
return nil, fmt.Errorf("oidc: issuer did not match the issuer returned by provider, expected %q got %q", issuer, p.Issuer)
}
return &p, nil
}
// Extra returns additional fields returned by the server during discovery.
func (p *Provider) Extra(key string) interface{} {
if p.raw != nil {
return p.raw[key]
}
return nil
}
// Endpoint returns the OAuth2 auth and token endpoints for the given provider.
func (p *Provider) Endpoint() oauth2.Endpoint {
return oauth2.Endpoint{AuthURL: p.AuthURL, TokenURL: p.TokenURL}
}
// UserInfo represents the OpenID Connect userinfo claims.
type UserInfo struct {
Subject string `json:"sub"`
Profile string `json:"profile"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
// Optionally contains extra claims.
raw map[string]interface{}
}
// Extra returns additional claims returned by the server.
func (u *UserInfo) Extra(key string) interface{} {
if u.raw != nil {
return u.raw[key]
}
return nil
}
// UserInfo uses the token source to query the provider's user info endpoint.
func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource) (*UserInfo, error) {
if p.UserInfoURL == "" {
return nil, ErrNotSupported
}
cli := oauth2.NewClient(ctx, tokenSource)
resp, err := cli.Get(p.UserInfoURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s: %s", resp.Status, body)
}
var userInfo UserInfo
if err := json.Unmarshal(body, &userInfo); err != nil {
return nil, fmt.Errorf("oidc: failed to decode userinfo: %v", err)
}
// raw claims do not get error checks
json.Unmarshal(body, &userInfo.raw)
return &userInfo, nil
}
// IDTokenVerifier provides verification for ID Tokens.
type IDTokenVerifier struct {
issuer string
keySet *remoteKeySet
options []VerificationOption
}
// Verify parse the raw ID Token, verifies it's been signed by the provider, preforms
// additional verification, such as checking the expiration, and returns the claims.
func (v *IDTokenVerifier) Verify(rawIDToken string) (payload []byte, err error) {
payload, err = v.keySet.verifyJWT(rawIDToken)
if err != nil {
return nil, err
}
var token struct {
Exp float64 `json:"exp"` // JSON numbers are always float64s.
Issuer string `json:"iss"`
}
if err := json.Unmarshal(payload, &token); err != nil {
return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err)
}
if v.issuer != token.Issuer {
return nil, fmt.Errorf("oidc: iss field did not match provider issuer")
}
if time.Unix(int64(token.Exp), 0).Before(time.Now().Round(time.Second)) {
return nil, ErrTokenExpired
}
for _, option := range v.options {
if err := option.verifyIDTokenPayload(payload); err != nil {
return nil, err
}
}
return payload, nil
}
// NewVerifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
//
// The verifier queries the provider to update keys when a signature cannot be verified by the
// set of keys cached from the previous request.
func (p *Provider) NewVerifier(ctx context.Context, options ...VerificationOption) *IDTokenVerifier {
return &IDTokenVerifier{
issuer: p.Issuer,
keySet: newRemoteKeySet(ctx, p.JWKSURL),
options: options,
}
}
// VerificationOption is an option provided to Provider.NewVerifier.
type VerificationOption interface {
verifyIDTokenPayload(raw []byte) error
}
// VerifyAudience ensures that an ID Token was issued for the specific client.
//
// Note that a verified token may be valid for other clients, as OpenID Connect allows a token to have
// multiple audiences.
func VerifyAudience(clientID string) VerificationOption {
return clientVerifier{clientID}
}
type clientVerifier struct {
clientID string
}
func (c clientVerifier) verifyIDTokenPayload(payload []byte) error {
var token struct {
Aud string `json:"aud"`
}
if err := json.Unmarshal(payload, &token); err == nil {
if token.Aud != c.clientID {
return errors.New("oidc: id token aud field did not match client_id")
}
return nil
}
// Aud can optionally be an array of strings
var token2 struct {
Aud []string `json:"aud"`
}
if err := json.Unmarshal(payload, &token2); err != nil {
return fmt.Errorf("oidc: failed to unmarshal aud claim: %v", err)
}
for _, aud := range token2.Aud {
if aud == c.clientID {
return nil
}
}
return errors.New("oidc: id token aud field did not match client_id")
}
// This method is internal to golang.org/x/oauth2. Just copy it.
func contextClient(ctx context.Context) *http.Client {
if ctx != nil {
if hc, ok := ctx.Value(oauth2.HTTPClient).(*http.Client); ok {
return hc
}
}
return http.DefaultClient
}

54
vendor/github.com/ericchiang/oidc/oidc_test.go generated vendored Normal file
View file

@ -0,0 +1,54 @@
package oidc
import "testing"
func TestClientVerifier(t *testing.T) {
tests := []struct {
clientID string
payload string
wantErr bool
}{
{
clientID: "1",
payload: `{"aud":"1"}`,
},
{
clientID: "1",
payload: `{"aud":"2"}`,
wantErr: true,
},
{
clientID: "1",
payload: `{"aud":["1"]}`,
},
{
clientID: "1",
payload: `{"aud":["1", "2"]}`,
},
{
clientID: "3",
payload: `{"aud":["1", "2"]}`,
wantErr: true,
},
{
clientID: "3",
payload: `{"aud":}`, // invalid JSON
wantErr: true,
},
{
clientID: "1",
payload: `{}`,
wantErr: true,
},
}
for i, tc := range tests {
err := (clientVerifier{tc.clientID}).verifyIDTokenPayload([]byte(tc.payload))
if err != nil && !tc.wantErr {
t.Errorf("case %d: %v", i)
}
if err == nil && tc.wantErr {
t.Errorf("case %d: expected error")
}
}
}

283
vendor/github.com/ericchiang/oidc/oidcproxy/main.go generated vendored Normal file
View file

@ -0,0 +1,283 @@
package main
import (
"crypto/rand"
"encoding/gob"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"regexp"
"strconv"
"strings"
"time"
"github.com/ericchiang/oidc"
"github.com/gorilla/securecookie"
"golang.org/x/net/context"
"golang.org/x/oauth2"
)
const (
cookieName = "oidc-proxy"
// This header will be set by oidcproxy during authentication and
// passed to the backend.
emailHeaderName = "X-User-Email"
)
// Session represents a logged in user's active session.
type Session struct {
Email string
Expires time.Time
}
func init() {
gob.Register(&Session{})
}
var (
// Flags.
issuer string
backend string
scopes string
allow string
httpAddr string
httpsAddr string
cookieExp time.Duration
// Set up during initial configuration.
oauth2Config = new(oauth2.Config)
oidcProvider *oidc.Provider
backendHandler *httputil.ReverseProxy
verifier *oidc.IDTokenVerifier
// Regexps of emails to allow.
allowEmail []*regexp.Regexp
nonceSource *memNonceSource
cookieEncrypter *securecookie.SecureCookie
)
func main() {
flag.StringVar(&issuer, "issuer", "https://accounts.google.com", "The issuer URL of the OpenID Connect provider.")
flag.StringVar(&backend, "backend", "", "The URL of the backened to proxy to.")
flag.StringVar(&oauth2Config.RedirectURL, "redirect-url", "", "A full OAuth2 redirect URL.")
flag.StringVar(&oauth2Config.ClientID, "client-id", "", "The client ID of the OAuth2 client.")
flag.StringVar(&oauth2Config.ClientSecret, "client-secret", "", "The client secret of the OAuth2 client.")
flag.StringVar(&scopes, "scopes", "openid,email,profile", `A comma seprated list of OAuth2 scopes to request ("openid" required).`)
flag.StringVar(&allow, "allow-email", ".*", "Comma seperated list of email regexp's to match for access to the backend.")
flag.StringVar(&httpAddr, "http", "127.0.0.1:5556", "Default address to listen on.")
flag.DurationVar(&cookieExp, "cookie-exp", time.Hour*24, "Duration for which a login cookie is valid for.")
flag.Parse()
// Set flags from environment variables.
flag.VisitAll(func(f *flag.Flag) {
if f.Value.String() != f.DefValue {
return
}
// Convert flag name, e.g. "redirect-url" becomes "OIDC_PROXY_REDIRECT_URL"
envVar := "OIDC_PROXY_" + strings.ToUpper(strings.Replace(f.Name, "-", "_", -1))
if envVal := os.Getenv(envVar); envVal != "" {
if err := flag.Set(f.Name, envVal); err != nil {
log.Fatal(err)
}
}
// All flags are manditory.
if f.Value.String() == "" {
flag.Usage()
os.Exit(2)
}
})
// compile email regexps
for _, expr := range strings.Split(allow, ",") {
allowEmailRegexp, err := regexp.Compile(expr)
if err != nil {
log.Fatalf("invalid regexp: %q %v", expr, err)
}
allowEmail = append(allowEmail, allowEmailRegexp)
}
// configure reverse proxy
backendURL, err := url.Parse(backend)
if err != nil {
log.Fatalf("failed to parse backend: %v", err)
}
backendHandler = httputil.NewSingleHostReverseProxy(backendURL)
redirectURL, err := url.Parse(oauth2Config.RedirectURL)
if err != nil {
log.Fatalf("failed to parse redirect URL: %v", err)
}
// Query for the provider.
oidcProvider, err = oidc.NewProvider(context.TODO(), issuer)
if err != nil {
log.Fatalf("failed to get provider: %v", err)
}
nonceSource = newNonceSource(context.TODO())
verifier = oidcProvider.NewVerifier(context.TODO(), oidc.VerifyNonce(nonceSource))
oauth2Config.Endpoint = oidcProvider.Endpoint()
oauth2Config.Scopes = strings.Split(scopes, ",")
// Initialize secure cookies.
// TODO(ericchiang): make these configurable
hashKey := make([]byte, 64)
blockKey := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, hashKey); err != nil {
log.Fatalf("failed to initialize hash key: %v", err)
}
if _, err := io.ReadFull(rand.Reader, blockKey); err != nil {
log.Fatalf("failed to initialize block key: %v", err)
}
cookieEncrypter = securecookie.New(hashKey, blockKey)
mux := http.NewServeMux()
mux.HandleFunc("/", handleProxy)
mux.HandleFunc("/login", handleRedirect)
mux.HandleFunc("/logout", handleLogout)
mux.HandleFunc(redirectURL.Path, handleCallback)
log.Printf("Listening on: %s", httpAddr)
http.ListenAndServe(httpAddr, mux)
}
// httpRedirect returns a handler which redirects to the provided path.
func httpRedirect(path string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, path, http.StatusFound)
})
}
// httpError returns a handler which presents an error to the end user.
func httpError(status int, format string, a ...interface{}) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, fmt.Sprintf(format, a...), http.StatusInternalServerError)
})
}
func handleCallback(w http.ResponseWriter, r *http.Request) {
func() http.Handler {
state := r.URL.Query().Get("state")
if state == "" {
log.Printf("State not set")
return httpError(http.StatusInternalServerError, "Authentication failed")
}
if err := nonceSource.ClaimNonce(state); err != nil {
log.Printf("Failed to claim nonce: %v", err)
return httpError(http.StatusInternalServerError, "Authentication failed")
}
oauth2Token, err := oauth2Config.Exchange(context.TODO(), r.URL.Query().Get("code"))
if err != nil {
log.Printf("Failed to exchange token: %v", err)
return httpError(http.StatusInternalServerError, "Authentication failed")
}
// Extract the ID Token from oauth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
log.Println("No ID Token found")
return httpError(http.StatusInternalServerError, "Authentication failed")
}
payload, err := verifier.Verify(rawIDToken)
if err != nil {
log.Printf("Failed to verify token: %v", err)
return httpError(http.StatusInternalServerError, "Authentication failed")
}
var claims struct {
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
}
if err := json.Unmarshal(payload, &claims); err != nil {
log.Printf("Failed to decode claims: %v", err)
return httpError(http.StatusInternalServerError, "Authentication failed")
}
if !claims.EmailVerified || claims.Email == "" {
log.Println("Failed to verify email")
return httpError(http.StatusInternalServerError, "Authentication failed")
}
s := Session{Email: claims.Email, Expires: time.Now().Add(cookieExp)}
encoded, err := cookieEncrypter.Encode(cookieName, s)
if err != nil {
log.Printf("Failed to encrypt session: %v", err)
return httpError(http.StatusInternalServerError, "Authentication failed")
}
// Set the encoded cookie
cookie := &http.Cookie{Name: cookieName, Value: encoded, HttpOnly: true, Path: "/"}
http.SetCookie(w, cookie)
return httpRedirect("/")
}().ServeHTTP(w, r)
}
func handleRedirect(w http.ResponseWriter, r *http.Request) {
// TODO(ericchiang): since arbitrary requests can create nonces, rate limit this endpoint.
func() http.Handler {
nonce, err := nonceSource.Nonce()
if err != nil {
log.Printf("Failed to create nonce: %v", err)
return httpError(http.StatusInternalServerError, "Failed to generate redirect")
}
state, err := nonceSource.Nonce()
if err != nil {
log.Printf("Failed to create state: %v", err)
return httpError(http.StatusInternalServerError, "Failed to generate redirect")
}
return httpRedirect(oauth2Config.AuthCodeURL(state, oauth2.ApprovalForce, oidc.Nonce(nonce)))
}().ServeHTTP(w, r)
}
func handleLogout(w http.ResponseWriter, r *http.Request) {
cookie := &http.Cookie{Name: cookieName, Value: "", HttpOnly: true, Path: "/"}
http.SetCookie(w, cookie)
httpRedirect("/login").ServeHTTP(w, r)
}
func handleProxy(w http.ResponseWriter, r *http.Request) {
func() http.Handler {
cookie, err := r.Cookie(cookieName)
if err != nil {
// Only error can be ErrNoCookie https://goo.gl/o5fZ49
return httpRedirect("/login")
}
var s Session
if err := cookieEncrypter.Decode(cookieName, cookie.Value, &s); err != nil {
log.Printf("Failed to decode cookie: %v", err)
return http.HandlerFunc(handleLogout) // clear the cookie
}
if time.Now().After(s.Expires) {
log.Printf("Cookie for %q expired", s.Email)
return http.HandlerFunc(handleLogout) // clear the cookie
}
for _, allow := range allowEmail {
if allow.MatchString(s.Email) {
r.Header.Set(emailHeaderName, s.Email)
return backendHandler
}
}
log.Printf("Denying %q", s.Email)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := []byte(`<html><head></head><body>Provided email does not have permission to login. <a href="/logout">Try a different account.</a></body></html>`)
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Content-Length", strconv.Itoa(len(resp)))
w.WriteHeader(http.StatusForbidden)
w.Write(resp)
})
}().ServeHTTP(w, r)
}

72
vendor/github.com/ericchiang/oidc/oidcproxy/nonce.go generated vendored Normal file
View file

@ -0,0 +1,72 @@
package main
import (
"crypto/rand"
"encoding/base64"
"errors"
"io"
"sync"
"time"
"golang.org/x/net/context"
)
var (
gcInterval = time.Minute
expiresIn = time.Minute * 10
)
type memNonceSource struct {
mu sync.Mutex
nonces map[string]time.Time
}
func newNonceSource(ctx context.Context) *memNonceSource {
s := &memNonceSource{nonces: make(map[string]time.Time)}
go s.garbageCollect(ctx)
return s
}
func (s *memNonceSource) Nonce() (string, error) {
buff := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, buff); err != nil {
return "", err
}
nonce := base64.RawURLEncoding.EncodeToString(buff)
s.mu.Lock()
defer s.mu.Unlock()
s.nonces[nonce] = time.Now().Add(expiresIn)
return nonce, nil
}
func (s *memNonceSource) ClaimNonce(nonce string) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.nonces[nonce]; ok {
delete(s.nonces, nonce)
return nil
}
return errors.New("invalid nonce")
}
func (s *memNonceSource) garbageCollect(ctx context.Context) {
for {
select {
case <-ctx.Done():
case <-time.After(gcInterval):
s.mu.Lock()
now := time.Now()
for nonce, exp := range s.nonces {
if now.After(exp) {
delete(s.nonces, nonce)
}
}
s.mu.Unlock()
}
}
}

View file

@ -0,0 +1,7 @@
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIA3zFNhDsB70vMBOzeK48Zn6oic5wfx9xto4ErduEVKFST0aJEfjLO
/kzNrKDgXArCEl2KYJQfb8J9lslA7cLvpVSgBwYFK4EEACOhgYkDgYYABABxyN4Y
6VxH/86lgejSlHGrjKVSzn6YeOukabBSiU8PS/o/wfGXKX4eKCkJYqVq18zGAfcL
q+UM09ZQv/De7mGkXwC67qQv7fS7tJ/t0uFcxriQNtVGPsL4/+YmWrFJBTlK0OgD
mkSBG7ERdb5x/JzNFbajSbX0wKzs4VOZU0VVj/2DJw==
-----END EC PRIVATE KEY-----

View file

@ -0,0 +1,7 @@
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIAtlsqZlQW4bWtSmDgLjbhcCgmmWEVwtzHMTZRoQnUv4rdTxCeKLY3
hjxzd5hPCmtfP68GyJhKQgKofKYD/DgQLc2gBwYFK4EEACOhgYkDgYYABAABrcbo
t8KEfgzslg4Bb7t0khCgFrT2hX5htSWnwwHiScs1yO9egRcftZg/WAoIo/QDID+i
OB4f5Flg5PygZpm/SwE4B1E8dGpyLRmBg3cC0/PfuRkGZ2E5POKZqsiRU5TkvC2D
AkwVr6UPpXPheStrp2qh6ptBUtZRzn8Q4lVFKoKe9g==
-----END EC PRIVATE KEY-----

View file

@ -0,0 +1,7 @@
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIAzUq62J5hiC8B5xw9M/e9KlSeO66uq9PHRSGcFY4d9MFgFKILKU0u
cfUBCwbhOnDWkdUTp1DkLWeNhE0UUvN2FgegBwYFK4EEACOhgYkDgYYABAHH7ZyR
YXJ9oDJ/KohiQFFXqkvspk3ljvBAFUFRyfL4Q40TtgKGt5YBNmU3SHNHn3fJdjQy
xe2OZcmKYxzwCjx6mgACI0IoiIdgZN0RBy8UMhgn810C/iDg+nOZScl7P0t3DFcv
H3K5+tkVWe8lLBIUOkqEyHmmfHGYcn6Kc5jHEnAebA==
-----END EC PRIVATE KEY-----

View file

@ -0,0 +1,7 @@
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIBt+T9wRbTfN3T5kSqfT5nqCt65w+SGAQ5DXQgcf7gCXId+Ux/57MA
/Dld+PvG+T8mobr1/jaFiGOLLRsjtnc5Ml+gBwYFK4EEACOhgYkDgYYABAE/ka2T
p7MsBezSgeATljES2xBY4wDOcjMmI6MzHdiO9hU/xcIQnhc2tjML2QZSMTuLy1ZQ
Yjhu0ZRg5Dxj4m7mgAFp2f/FqtOSAR5vuikaYPzHwosvNFIIpRDJCZ23j6qbtemF
5qXUlSXf2+W491rfb2njNwTWx8BLn1M3fFhobK+O9Q==
-----END EC PRIVATE KEY-----

15
vendor/github.com/ericchiang/oidc/testdata/gen.sh generated vendored Executable file
View file

@ -0,0 +1,15 @@
#!/bin/bash
set -e
for i in $(seq 1 4); do
openssl ecparam -out ecdsa_521_${i}.pem -name secp521r1 -genkey -noout
done
for i in $(seq 1 4); do
openssl genrsa -out rsa_2048_${i}.pem 2048
done
for i in $(seq 1 4); do
openssl genrsa -out rsa_4096_${i}.pem 4096
done

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA05kMh0SJfLTbcAhpq1w8jpTuo+Shy95WgvYc1KzxcH6ol/nq
vFSn1VCT/OHyg1t8BGeIfeuckIppT+cRtTjrsqS6FEmJA3lAiRqJZLWbewyXgoaZ
eCsmMS0s3w8KUon4Z9t8rFVqv2fkki6p7FtLPjPD0PsTRNCPwtOGM8Ci+0qFuCrt
4flUr6DpiALkqN1PSJCAwL22y8C86S6PPBU2seR3TWdD+iMAmr3Rezh+J/JqYXK+
4qORzE6mA3hjn+goULQif5fUbYG0vRSyEp8UrlzevS88+ZzxyS9iTZ6H1ympLcsV
PeqvCIXPd1OJXn6ZGSuIgOlgZuaieKeuYLHDCQIDAQABAoIBAHrxC+R0H+YDNxRq
7uqPlufJBLbZGmDXeDBzSuEO8uFH1jEnFgoCrdk1Dib6KOvFddMhTJ7NDJS2tuWj
/hfrUJblOvCaoS8Rfjuq3XVUR1hBQq6mAfleKLyd4NphZL/8RgYh8tg2cOVxOc7t
qfEYQimL7hQ4LUPoYf7y46CiJpAV+BIwrR74k9Y3vcpumLwWrkwlfLTMWmcaiJE+
gQBVl5+CQZmVKohTPDfCnQ9+ISzxvh4nesiQORMljG/ssQaZi5h7VEJDqGOaDgVB
CsFp9fxLrQzx1Gjxv/uEhG/k45uAU1qgNZcL3/XQhyCgadUsuMM3yOndIaiQNbNN
7bm1b9kCgYEA6XICJjg6vRbIO7nS5VYW1efFhLlqYjFimjv5f5vaTWA3FM8S1crL
HG4Q8yh+CBOwY0mtcwr8RjlnX5Dsi+wGJgFjaL7OG0MDojv9/YKZse4dIHELUPKC
Wj1zxiE23JsMAWKXvkhgGJgkC4mksZHVVszcLOEvmn2uZghDtTxhpw8CgYEA6Aqr
NipU2MPPOe0Hu2Ar6Kb/EaJyJA2G8lABWILwyI6Wv13dpZz1FfjcpnsZVRNBuYla
O3OKZun537kQOfHMWJfZyj2fOrJ0z3rsWZ+nbbUy1um8Z6jHTPqQyNoelE5/QTvs
CJ0jDzotbUYsvE4TdOH2EzSneecAgBN7Brz/tGcCgYEAuSpcOBKbzMZYVr+DX7NU
c6Dek/M6Rd6kNnBh620k0AEET7YcW4X6a3eGbEjvBtsPKwIS2VCaX91CeJQMfMPe
8KBjSH8oHomeRT3Orhm8bVzQr53a+v8QlCFwRnSr/nnhIOwiLqVby8ZJuPkZsFtb
W/ksn1CSoLkV7wqZIhVd49MCgYEArkFU0hh4H1DtDlMyu0Q9tTmz00pq7Sg7bz0l
xZKPwA1Up+GV0glNBHMfQOaw33LWqL69RGhAR4juXVRdGya6js16gKZGLY5Wqnll
hOigk4K/6yUcl7vn76c7k5o53KYWaqbVWqKm8Yh/FNDeR4takSwf38xq+ODBP21h
tm24mYECgYBUS4Ox2dzDBPIKVfxyuV+FviM9wYKBd9G2xUYavF5Id/0byYOkOy11
K9L4NJI4Xztzw6KZw7ngUsBmK9AN60mLO1SkHyMr7dLanyt03X46jhtEQem+wDqf
HgKcqIz0gxaU6+widaEM33/cTi+kafH2uLr13aHU23ZfMV7oeRk3XQ==
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAsB/GrXd+Ygz/Gq0nxKX06cLh4ECe+AfINVz+LdBCY1R8ossV
1v2Rk6M59EMZwsR3XuzCoioEGzI+QyEUUQO+FpsDMpZ3iL4+dTCZIo9XXfHyQEnq
JCy2w6Al2Xr6qTvfHBCMn61IGwAgDhDDb0djclJhqLhgxAVwXyxt3+pVSnUqdKtt
h0tgLqyEgqX19QjDEh0xhFT+zB/IHmolaJO1DelvDLYmyaxoOCizGb2WyMcjkZ6m
sdV/rYpzBW1BjA9N1+gnTsPF1cQ6wJJTtDRjovKWtqCQSmmPnQSVNwIz8+zI9FUc
qPM6gP6v56bcpmqPtPylY6T3GbGxBQtW+1Z2/QIDAQABAoIBAFNvikiNTlMXAxdZ
JnjTgfXn++en1Wd9EEyvdD6x5XF3CeB5QyxpTbjaX88mpqKNPlu63+3A59cWc0aL
+jrzAe9lmhsyCwi9z4rm7fTgYSxBPVlVatWeVSrRyHyB9RONKIH8GRJgHcOkyIrB
SESEVklHW7p5NmZGiVidDKRCOAugMpnYbOB2Nf7wI7cxHT8QcxcKFaQTdCSnYv0D
eMWpEmP8vsEmnme8Q6Uax470yBHQQvI7JfWUIbh3wZVdDLllbr+E17ej8EHatCyD
A6J1lLZ72DJy37G/n3kLLtHy0oWjVk9ZT2m+HEQHSEnAqgHz0UzGKGVoeSnbAxUw
FATBNYUCgYEA3KowvUgRyc1ydi2L6YqQQg8Mhq6SdSPkC91p3pI8pMJGwco0tI2y
95EFMsFUg3m+v2URDKxjt3itx+vqWxSqeQBgUzKk+0TRuKt0+8dav5BUA3njtLEk
VdlzwofbvagUnE+slg0lQKsK/IzODPCckcOjBcUyJI1PNYdDjfQjv7cCgYEAzFO1
vtexZEUw1QGsrxlxrwBu7ui3NvpfC7rGqJIiUxm7cLrfDbOSHJxutCNOZH90zDZl
xiVBTc3tvpdXdj+zuUkf9Q4dKNTIq3+Hwm3iGXS+yw6rT2C2I3h+IaSCF1YpKakw
MSjOwlmnYIckeYGHe2kfEgNiL7qRD+SxJCjSVusCgYEA2/3Mc5h7K35oQ8tqtl1P
LpyEN22pU6GBhBasqpmOXg/VrPPjkbHHH6tzzEMT97OTaIrg8YqYK1zjm/HmBgHX
ZqTqY2eVNXBJyVseWLlKDrtcFs8ZJZaJDBGrp9/8QdtlGOURwdK/NfaQEHJsJlhn
L6ckSudq8yfyNQJyZf5k+YcCgYEArSPqCBNiICN5Y6YNnDqlWLO3TP8p8Y5rZ9cX
a9SY/W36pWXUiRm3IEN2k3KvhP10DW+zAhqjobh0U2KPHIaSVtmeGNui3eyhNqHU
em7+fq+s1QhTJeo/rQL3bq6mBfxe2Qyi56U6vvmVmXgq8kNOeMb1KyBu3R7suVkC
ui9VPY0CgYEArXpn5sZz8ERHXjeRHTVxaTk2djvgYtzVKjEGR1LzX6Bi7drbw/lF
M1Fjqog/k0tsDM7pC30EoxpRR10hSFdC7dQ+STTeTr1gmB6xJmA8boe1jpAfhFXm
sjjrsFsw3mUpsJD3Ck482T3BA0iZP3NvC/+ge0IkRUC1/j8KP0zQKZI=
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAv9Dpk+nfW9lOSHdt8UO5f/ELRA+JM4vX9GkbJy9/M/NTv7gg
HBNpxYA/RhIMnifeXFAomLYMov0Gxh4+85Dv4YT3lhxlUiwi2yaOIXlgy15R8kGR
OCFVKgfqfQw3WnWhjGyH3Rb4Znk4IGh2qj+w2lBlnbeKCxXq+SMB/Kc3TMmJg4Cx
P73vVmT3VifW9goOO4DDNAv0uTl/KgWnaVoPc8eRmYxBc9chTxjlmoLHsCy8z9+p
pn33eTyfcNF5DS310sMHLcvpP25fPMpkbzDDjnCzBouI0YkJJ1F2m1GPEqnsN0is
1F9TGJppcAOpb9aaPmtU2wlzMs5Rwm5vQ6/9wQIDAQABAoIBAQCPczl7+QeltRoq
b8a1DCUKXcZDHCtLdWYHzyMTZx4GSA917cl1tb8AiSzIxm7RSJevCfOSYXOJ4RjT
yYLivJ3pVnuis5HCpmda5badqhyNevhl6EsmYydBy7G92wj6icZLMk9ZNPiIClfD
RNyZ7g/g9QdJsB14tOeJcnjl7lgY/8RQOv52mWC2AfvCRGf6fHoopMP4ZlSTDP3q
2LMGNUW+cEwjZg+AIheJCoCOs51pvm/B2DXeb9T/GMzway3qFBKU/RYi+ceOm6UF
+BZwStxpXMRbnXrXv9wS3S6260+NSdgIVF7ErzYjinLhZL9Wc6khoRVOCg2ESewr
+2sJqoABAoGBAOST6/PgAfvODs0m3tzb8dDYMcCtD7p3QoC3HiG7mEu6vnb/GdZZ
6B1XHiK36xLL0/8tf2BtJIqmrDqOJ8pdiT1sbQUtv8lmbdr2PoV2P86v11F8ZBkq
szjpQ6gGaItT3dAjJxSGCiwdvMw4za5GJAUPTQZk+t1XasiaAQznMimBAoGBANbT
9tv+mqwvzK4/Pw/gyIJkIQrbgXTFdMjhZJkNVxCm2R7YnNDRLSxmng0nJCB2Tgkp
EouWNYi9rsWnmR+AOinSSoAb6znQwOZhQzQb2KzkfBnlZ4XFUxvW4OZmJDZILmyr
/8uHnEcT7xwT7L90j9cSxq/+WCbB7GGpFmmVpXRBAoGAd7l/ElsXzuOsXwpoGzjd
HS3QSYKcRWfoHnFLyBFxgOEMmFmgF+U5rfyOnVLGPy8iGHulR0WDqVgJyBXjg5yg
oNqk89x1ozESg2kNcGxymXkDB/xmlcQG4d1UgbLxmWDRQw7Wjmpy846T8Egke47j
mP7dsma7+6mpFe+Mc0y5uoECgYBp1D+u/oz5uA5v5G5Phx+fxG3WqG3stX0jnI1v
LHgwltEs9e7Cm9lSHzdLKXYNm9ozfw1IwHWc6DyZ2EeBkiyU/6h91cMaVzFADLgL
ipBCE8jjBPTrnFqlw0RFnBnIt+RO2qiHfkXJahOH1HTzmBtoCzLf7j9E0JF/Rsno
t7SrQQKBgDxAKKHMLT2zm0kz6eTSLSB7WDNxZ14+u32fZj2ln4JMERTk3byGojPX
zf9poWwCWJOLd7uVtLl2dQZFUZG9GJuZXO15qmKAaXMI5yVwH1ygzzZvDLOlH8Nn
19ZjFhRLreVeAFLMTyOapEH+5QZxaszMR8Xzna9BJnschht/kPM7
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEArmoiX5G36MKPiVGS1sicruEaGRrbhPbIKOf97aGGQRjXVngo
Knwd2L4T9CRyABgQm3tLHHcT5crODoy46wX2g9onTZWViWWuhJ5wxXNmUbCAPWHb
j9SunW53WuLYZ/IJLNZt5XYCAFPjAakWp8uMuuDwWo5EyFaw85X3FSMhVmmaYDd0
cn+1H4+NS/52wX7tWmyvGUNJ8lzjFAnnOtBJByvkyIC7HDphkLQV4j//sMNY1mPX
HbsYgFv2J/LIJtkjdYO2UoDhZG3Gvj16fMy2JE2owA8IX4/s+XAmA2PiTfd0J5b4
drAKEcdDl83G6L3depEkTkfvp0ZLsh9xupAvIwIDAQABAoIBABKGgWonPyKA7+AF
AxS/MC0/CZebC6/+ylnV8lm4K1tkuRKdJp8EmeL4pYPsDxPFepYZLWwzlbB1rxdK
iSWld36fwEb0WXLDkxrQ/Wdrj3Wjyqs6ZqjLTVS5dAH6UEQSKDlT+U5DD4lbX6RA
goCGFUeQNtdXfyTMWHU2+4yKM7NKzUpczFky+0d10Mg0ANj3/4IILdr3hqkmMSI9
1TB9ksWBXJxt3nGxAjzSFihQFUlc231cey/HhYbvAX5fN0xhLxOk88adDcdXE7br
3Ser1q6XaaFQSMj4oi1+h3RAT9MUjJ6johEqjw0PbEZtOqXvA1x5vfFdei6SqgKn
Am3BspkCgYEA2lIiKEkT/Je6ZH4Omhv9atbGoBdETAstL3FnNQjkyVau9f6bxQkl
4/sz985JpaiasORQBiTGY8JDT/hXjROkut91agi2Vafhr29L/mto7KZglfDsT4b2
9z/EZH8wHw7eYhvdoBbMbqNDSI8RrGa4mpLpuN+E0wsFTzSZEL+QMQUCgYEAzIQh
xnreQvDAhNradMqLmxRpayn1ORaPReD4/off+mi7hZRLKtP0iNgEVEWHJ6HEqqi1
r38XAc8ap/lfOVMar2MLyCFOhYspdHZ+TGLZfr8gg/Fzeq9IRGKYadmIKVwjMeyH
REPqg1tyrvMOE0HI5oqkko8JTDJ0OyVC0Vc6+AcCgYAqCzkywugLc/jcU35iZVOH
WLdFq1Vmw5w/D7rNdtoAgCYPj6nV5y4Z2o2mgl6ifXbU7BMRK9Hc8lNeOjg6HfdS
WahV9DmRA1SuIWPkKjE5qczd81i+9AHpmakrpWbSBF4FTNKAewOBpwVVGuBPcDTK
59IE3V7J+cxa9YkotYuCNQKBgCwGla7AbHBEm2z+H+DcaUktD7R+B8gOTzFfyLoi
Tdj+CsAquDO0BQQgXG43uWySql+CifoJhc5h4v8d853HggsXa0XdxaWB256yk2Wm
MePTCRDePVm/ufLetqiyp1kf+IOaw1Oyux0j5oA62mDS3Iikd+EE4Z+BjPvefY/L
E2qpAoGAZo5Wwwk7q8b1n9n/ACh4LpE+QgbFdlJxlfFLJCKstl37atzS8UewOSZj
FDWV28nTP9sqbtsmU8Tem2jzMvZ7C/Q0AuDoKELFUpux8shm8wfIhyaPnXUGZoAZ
Np4vUwMSYV5mopESLWOg3loBxKyLGFtgGKVCjGiQvy6zISQ4fQo=
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEAsj0DJZqC86gp5P0B/KXk4CoWLa06uEpMu4K+0QFNR6XwbB7A
FFXtcjemUnuqnwqHqouG7pcc/KJAiakWPYPbAtwb0JQo3lRPRf6EewjHmspk9vqd
YBqump9VZedCs7acHCT/xoHhsMI4PyTX5VzhTJoZSMSm1aTX91ZTG2R/KkFhchPd
b4kMjc2LK9O1CN8vQoX/IolCOlUOVcVrF20jEiOIFpc+5t4V+jRvx4A52dG0DqBm
Isx3+OioRHD1qRKbi8L851a4UAeX16rW0xsuyCy5htfh3Kbts6AFCtzmvR12QYBl
+aL1Ksj4lraUmQOkwuj6p+w6x2AU3z5Z48qOmUdcmBnSJE9k37wQEqT4Y09O4+Zb
UcaAdcexT1YLEKaL6ghGw9pE7gPTmohk+gTYpiPnQ4H/M7HamN8dHN/Ch1hvaI0/
ZdxSEU2jWlkEeg7c5n7Bmdbc+yMAXeWvS/BeH4yGNh6TtwvTYpq7aDcP4cYw7L7j
ODIWEmCVgOzdDxn5REHmrOx3qSjimCA/Dia2PHSqkO/pJ0Lu/YNHWdEhxJXYtX8x
Ldk7i3syJl+s3Qbi1GrNzPRr+enyAWVk5wM4XVwiNlRvvHh8c+gPs7a5qTS2FulW
psAUB+pCC9Znv8ixgVR9ZMxLALZRIzgZ3s16OZP7pl9t8u87LnbyeihY03sCAwEA
AQKCAgEAm4gqCtI9mykPBcbRyQlqI0IWgF09dDtBog6BPBiKuw7OMUrUCerBfH2b
ITbQuF+T6vo+EEzE+p8K+hUWVy+MGX7Ats3Sq8+eLVHfgQ00QJqEaBBg68/ctQh8
mKOozPF4YAbZOvtzWa7hLhiUXI0j/JgroBgaDSv/WNF3S9vyK4lJ4yX6gK1yyvql
iuT+gHNg5gfPju9/Xy+Bhs7ymEqf4+AljLEGLqd1PhQrxkbaNHyNRoYpGgyaVBWR
X8fCVnrqSJcp4SUHSK6XjZaCR0zdEcgVTNltOgJgQfJM9CG3JydiXd4RHjlY/rDI
W5uPJ8bKK1rp/0ZgNEJfdD8QaXoD28AzoSiL2U8w2wipuq0zAExX9H7ZhgdCMv8n
1yZ3UWhva1LKzjP/fs9ER4CGK7S5U4tmzF8rYSw0A/RuP5bBhGRAJoCs9ZHmM0TU
Z9JDAPLy1/P2ICRuHH6mIVOZkHp0IedSJIPqSJ8LywpPNCyh2lIxksnyL0WnI0ai
8sIu+1t8NGOv/41yUKwJ9LUG+oaWmnUNqduECvYmuX0ByoU/E9YY3RsadiCd5P8l
xviITyRQG+M5BqOTzd7Xa7K9VBy2V7Vnk/gf6bX8fbv/2TYVD4nnjxNd/H6Uzlj5
i1R7/gip4rEA6n/ZbioM8TATaJLG2ZAlnxFAGDw10S3cj1W8gMECggEBANxNwBSx
zbm2vFWCyYY73D+el5cgvSpoXhhu6WotN1aQMBMg4DVQxUSD92erSWvYlb1HOBJL
wjNLMz5NOanRy42mEXso+oN0PMpykGOb1WmoMx186xj7o7p9NK9AFF7ll2DQMYHd
9Y/F8ggmj+4orvMMBL//pYDmdguZ2O+KAr/VZAVj6YTnO/tdmk6n4EhAvB5Hy5wZ
kqi+YD3W01l6vmtWUQBKquaO5XktsAJtv5NapOmy1Fu08gEYwAgADI5Y8CLyMtzX
NmCSoTZ+GNYPIbW4DocSqyH+N6YpyqLDifb3wE7uvyFwYK6y68TNKhO1F7JLQQNM
7pM7F1YkUagzE5kCggEBAM8eYUkXqdno5TTubYcH0KOt7kX2ZB4ABFcYnQsMLAC1
29yElr562jMqgYhpIV+pPMSxKec91g42p6Uiqo1VeciCkFkIcVUyZO1yBFsHnWcS
z1GFDb0ePDAddLIrOmpKQveRpubPThfioqnrrVjm4YObWVwaO/BiAvFF9ZIPgB7N
VmXINKJ75Zof6NsCq4Z27dnnXlh1N2kRDEVV2P/x1HRCq1BB84IePGmSjcHBXRqU
PD0F8PdIYlcW1xDlQuj/x9iWso1MWBWzeuGROTp0LYGA0A6NChU5ldTK3T3+evNo
KO8xzrjbUH6I6XvWitWG1hvHf31UhoiYOuxjF/++zDMCggEAVgSRrELkdc/w516C
u0PiMoEE5YBl/An2O4oK32c6RTVVYBKlGIwqCh+Q2UybBV3y0Y3eSd6EvCxvnLLg
gfslhHBEQRd2AR/AoLdsw0fUY0XGd4wP65hNjIJYsNjPW2I/4hBIVFHLENEUOLR9
3FrMPKADtsfl4leZ3du7RYRYoHh8blJdmoQC+pnIp0+LFgsYqKYVzSR7DCIRR/P6
X+S6NwTj6b49znobBV6ea8RYWfu5inpFymzzVRRJ3pXOUUJOuQZib7IkTD7UbYd8
wQ/1dJOiMIFMiqBNMDb/JOA+nUyNLQSxYigTyAKaZiRJeppp3zbc8qH2QUyARyU1
MPyIeQKCAQA5WO4S8Oxkm6mrKEFHXBCW4XfSA1DhRZvuCbCh+HLOl4wS2NtsTlPQ
SvqmrIVDGXbr9ynlDygPs25juN+EVqBrtksFe+L1dgif/ivakJcyjPC+X5rYPGDp
6Z4AHxwDhiBYsAmIaunyjxv+9HSA4xyZ9g+eAt2Jx3mNGJPQJ16QKMa9U9vPCYMf
U6qDyY94ocFlzjw/PeVjwAanxAdbhrgOoM8SX9BuvLR5fsylU0bWLykmtFht/6rK
9lYCJZiLLxdEjyVNHlBdYd6qSi2QU86txt7UyJR8H/+udaUgny+n6bU71YypfoAh
KQOM+HBkgvsRogFY0GiXtZ7LCP0CIPAlAoIBAG49Ot1AghC9OwLXjcneGRbsML4w
vkQT9vBkStonR56RkCnN8xGDvLwQQXOJ0Fodo8C3Tup5AHrTNi+V69YVQYCtuM4+
heWNZxpYDxzZ1IU5a7ITlH5TJFP5paaaH+tIqRZRp+p2+j5y/CyAe5hoYY4K1Xog
Zuz2piLO5IWKxtznwlQGjlsZ1n6p+WS89NFHuIF2r9NrovPjkEDDqnEcLRTDUnLT
8FcpdlNkarS/X01ZTBFja5EfW49UWW8sQNRLzVfnP+MECvGFy+oA+4vGprSBuxmt
WBY7y+z5VD3o+JVHLqkdETwIP9YKLMVhqUb5DQb2EYwggU+1HgZNqTdbfDk=
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKgIBAAKCAgEAobIQNPQNtGZlPSpbtZC/c/gb5OxoJfoq07h6X8kyeR3xq9qv
js8u8YQMt/AtgYw1tSdgNeaYzsvMgaSVgexf8ysqys5obWjcB/sjRR6UJY4hgSm5
oLoGy8fi8lbSEtpthHStA7UVUGU95Ypga8r++Kkizk84wDq+0jZAGaEPvhO7RJne
nbEKhhZVIFb13LlkaX1eNOAxAcTszFgX/DaZWFcfzKop6gS7agU5NLCtyAjKdSRz
CXYMVyVnXhZaxZtLawk9Ld8qIqwmytiPwnlRugoFhTrCPIPfAb1UaESKj4EuupUi
brlByun/ENiWvbzTZyGLSuYA6rruyfUll4oDT/SwsDMJ0jZiu5C1bTgA+gY6kB+I
A3EvcP2Eh1uiUvwy5adAr29wlP7/8YwWvyEAAaO9SKbh36xcbsJH9OIQbFati99L
kn7jfyktUGKxgmwYilF2ehtB1tPOMKRor8HJGoMEcrjiu4Qvc3ygaZtomfY4zxL1
uNeZ8c51pm5lmo30kJ5q5aWX6iS4Sp+ncFiPjxj7bV7nXOGqwmxK23tk4qdUOK4s
z26uc+CChP6VdOwLU1uu2FdRvBCPSnvBFIk5qaFZkhbk/gA+iS8R82TUf1DWfvZ4
why6XJDPQRnscySiIckN3X30TD68dpJ5VtzpRzCvNN9GxoRFY6apcJXoV0sCAwEA
AQKCAgAKR01scE8mtpOc7cJiqk7hSlZLmRONxndOeh2dVSbWOCcSq5YZV+Y+CAze
7G+YGpeXamddRclU6/OWEiZG2gXHaWkQ90oAGnhSMY6uaCE2ufA7S7G3G9wuvAgb
K5WzCRuJHfmZkLtIHwduPfufHopSuD20K6kJ3zIeHsC4YFql1I9E7xsNnyFyIJ1M
rvp2C3rskcGZTt8Oo7wByV/M8pOQ4Ajvc6mybJaVSLu4M7r4SkbEZ4rAgTaLm58U
hgtDIHoM1cuDzPnatmLI5jdNP3UIhHaRX4jVW/SjIavp7OF5+dZEmhJUQ4aBJZrH
MV1ztjsiBSnbmv9X7IYdZG39UhKfuqph20l3NQQ9lGhJH68/V5YEt/301O7Jz2jo
hPP56TvvP8sNJZqkiD3JQ9Do4V4s0pUR6RQiim34gdivhRNntPjL1hfBZVoIIxOY
Ek//7OmsfrPwHEatZP6UT+IJTVZRbGp6a5Qu0YmQUh1/MVK+h2PU+FVpkgYALm1u
6e2dXqRK1NI0v0icFMUTBZAw8h7mob7IlkoA/uLWjgkVZf48/2wCiCtxjDiU31Hu
QGV3AZFccjKPwjc+lcWjjH3+nM/q4Z9b2r9x5XaPOlq4mXF4QnheSgQ7URhiEG2I
RI1pmV20HCU/qmAiiGGBWOBYlOSBuRbun5/eSOY8c3FXIMOckQKCAQEAy7F3eqYR
0tXCJRzR9EQld13QVDeP4PpADbMVzuGXbG6+DDjXgRQiDh0gnS2hSKATd61ZRg+m
RHVEPAf9lfEyDutr6qZRZJs14auI0Z4eBfdq+pL23e5tcmj5GuqBZ6qR5ZYHblz+
OT0pEa0VfCW63e0XU/1iG0y6PGbSQiHmqas6E3s7ccDjZZE7lRCBn4+RouBBxtFo
leugrTK+ahObFeSYSQqjG5vGP13gatui5hib8cGG6QUV6GQvezvLvo/rBmCNFrjm
wtQ4vfICifadKYYxyTNNB/EKDrXB1E0NyDM8pmXcfFlajQU0fucvSCNmXIDF8nnL
TCB6mGQBX5VUPwKCAQEAyze5K7YQXe5lp7PLz9IHYyY5dtc4wzwHI4PGe2TAKEtQ
jEOjSi4bgUYGyalJ8vqmOrGkCBW4NCF8Ywb6wNHgK/O6BGbaSl8N2cYqA9wSnVYB
c57ieUCg1A/3mdEwO+jHng1G6Me2Tf7VzkO/XPXyA2Fy6XhIg4xf+mxlW010dbKS
kn4CWljY8XhuX1DZMjA+UAsMzaI6DFcxbIcgcVMP6lp9mnk0Fnm5T5x/EpNjuAHV
D0m9x9TlQ86akFdf+FljEsj/drDIZ8mkU4JTKiH88wrwfKHrOqhv1NX7hLCXs7wb
tkxVF71M2qOcYl5TAvJz9uv2U4O9G5l1jL9wT9eJ9QKCAQEAniUAwGajO+/eNfY0
Q9OMyyo5Dsm8mU1x4bEC44ZejD9GqjKPjpXVAuQ2aBH/QGWX97jMsQqBanEpMvp5
Nar31IGPXbUXSGcA5F7LcQO0B6nakwT7Sb9NliBOF0mugo/5iih7SIJGlqYXdrPN
FIAunxLuo7T8MHnXtgGWiOXNMjnQc0OgGWdKpZamjcss+Hb8+Vnnd7cp3gv8ybu1
/qGOLOc4HK13iX3d42C9VfmEdeTxXjeEyPG72pu+CY2ZWDBgpqjboaKY9vbRvxdg
RUEFMDISAUYlLl9EEbun626Pnrm5Au/eyWSOWyKJaWWQXg+t72/DP8izwD0PMbWj
I1TK/QKCAQEAiYSK5R6OUtIprmPILzlE0H6kclxQSCXN+uWIoiXatynINzLqRB+R
c1is7TiHF0swxBVEGEiCX5ytbOHjPCqKVZPYNHRZkexjFhS4h+YcHqZ90v0Y6s6m
RvsLJebeihwLQVRgwNOs9XjWvH8x9zlj7Y+7UGyaPZL3vCIwMKnofmE6OLHW68am
ADnsDspKQGFPOaFQp7L5LzKt+nAyrx1zbraPusH8Up1Knqobf7mHyJRM1syjBaB3
CPy9saG/CvOKTMMBxRL6eumELxLJLoDTiLDFbsGvygEDtHadfvx1nCZWZnWfO7JZ
WLdQ82w7JoplmRmylm9WwF+HoZhG63DDJQKCAQEAjkYiyHgh/drr0tQLlpELy3+8
3CfKgijIs4Q+UGBsygEkPUzS/oFYl9oJ/yFSSOCsyGTYF9ojXYbdaLyHFS1YcpWN
pcPcZRmd68RdVk2gOMAHBAvT2YN56OQJiDwghMA0bIoY2BI7+h5mFLAgudSZXHY8
RYt9IjEaFai9cvR88p7p9bQJNuIXWXia9PcdSldHJ01S+GhWHNbd6J0BfPhfAO+R
VJl98+U6jLyXyY1Ma1v9AfI6dC43SUKG4Z8b2B0ynxMANiOWXCj823owTk542yFi
ylWbhUGqQ4uMn8ojPArNY+Ndpmlgc8MpgXjaEzMil7BfnhlTHz5sQkMM4ByhaA==
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEAtZndOamd6RPyspdd28050is48R/ozNBdi5xa2023cQ/lWl7j
TFTvYyrp/pJ0GvIIUwG3BKQyQBTKcDhLrQzrTO8fxqKfNSQ9/9J2AVChFulVcaJR
dAJ+k+jjvR+1LMjOxpxjv+RJyTSB4Z6W9xOyqfwAyNsc3nZ2jukHlOCvkpwUJQW2
AMQFLlZoIZ4FyZ5qkSPP8kw6E6bA76/qSY8/bVMleX7HySIAfA7car3lZuxGMkP1
aV7Mak4/5kHZwB75SjuiQVwj3urMA1vEoCuwXgdisMS4lmB8iLyW/XJSWHmLrEnQ
JWbRpie7MYTz2tOJ5GlQpD0U93eV51sn3nQ7If+08IpfZwnsqf+Eew3+cb1scbrI
rwoS2JI9MW97I4iUiLnXl8ZDYy2pIIt3bs5gFIdNVEr29uDnmtvmZfFaj/RuPLO+
4YxNILYo42j8grJyuNikhvCMOWvB/6QMWGUc0BEz4QiZJLzqGWpTP0Nli3AedAxT
6CIbOhfl8RKyZUl4gDD9ZIsXUSZ0dL5CnIYU8yhq9yw45wh8gwPKTo/fiDog44WK
cFiQOlTNg1G6x0fUcbfrsfGwNfc9skXwDz+hcdD7dePZfN7jKeQxtX5dj5DIJAhX
9wanwaoqEj+G5KyXbsn4tuYu2SipSad3uA53UVAHzLapDkD9iiaJ+HrNn3ECAwEA
AQKCAgAW+spgspL13H1YlgjdeIG5k5iYAoat7Cv6L6XbnGD7IJzQK7OthA3qyZJk
kVm50yi0gEINh02IiFj5jFYfJsRbruKhexCUY+qohZRDJFXOFWang3e1K1+jDdRL
qUh+y0ZHIaEJtjSUDl3lE/FcgJSaJ/ZddESZ7fmgqeI4t5nf/noaGTfnruZM78gr
gNiQo8guZ463xWeP9wjxC5ylBEhtaBkU37MeQ3w2NpcztqXhuUJEuA7E76cESLST
SX/pbMH038jvZl5vpdx9DE68Ser+awbVAX+uH7WChALDPYUoBvFistBw+yrKULrC
UGWfKieHzL/UmJofmnVQmltYLfMRarjnGrit2uinJb+xxjt4yKMA6ZCHr6jF7ZkK
F+vKEbLMGZGZcOQvrhMwTkI6asoj3AoDl8pv1AtFlYJi43hlF8fqGHyaEE8KY90M
z5SC0wxhUfUGV4FVHRdq/UvCGQf/bJhRfGftnC1BNc9NkGQHZAbjeu4YSkIjFPEO
k2LwKt8XVFX2bhFNMApP9ktTFlUtOV1Ljjn7+R2L7P/hjRmajnMcSgDZYbQ5Ndw9
fsZ0gTr1NZERgud4CPLgtc6uwp0rYiIwWn2VoMGNM9WUVDhFmOlznKnPqrkw3zH8
uor7/+AI1AGltarvTcth2kriS34AUUoaGysZwIBcYgJSjNPvMQKCAQEA3UVUzjyn
J8UCl+KS5vkXJjIp3mvpT4ZlomSJwuWFChXRHiy033rb2Pz6gCKj8Vh/xHfRSn20
ZlR/hodqhgyZAEkqvvzcBXm6coEwFywZbhN+cJPpqFkrehLBOC+ukOejUo+0Fdm6
pF42hGZngq5GbAi5A60FJO1xOIjPj0chBOk5SUTPz7C8TZnE5ae3iSHEcqB2f7qY
OLLrAAs0YQmOOAB17S6UYcRXTAik6yzONboYFUiyTSid6fuMr9akWP7BGOMNTgdB
RbZB4PYcvMDqJdCOwJV1eqhNz87i3GPAq/D+K+YItxBi4flh8ijrPjpZ4hH22aeL
tmfxZVK5TilW9QKCAQEA0hqZbmnLCj0TV71VZsodb2G+6BRAnS15/ppW1BHuNvmF
noRMnTPAVqbzZ5VZshK0dVsQa66lB6z0uPzOG3euxozrK/Y0a4i0bbdsTO1C+MQ4
ssIasCWTiKaBWuQ+v9yglEvi9nhaQbMUGhwH029xd1e2t7m10O4HYXF1N640j6gu
sLvMc0xNvom/Sv+MP8YoXX0NwwLqqxFVQhPqFsim0EhpfGrxRxmWeby1UB0yxn1N
y87UrF8Ap8MxBImz9avL/5I8LI729xMZywa+RNG3YM9AYsnP2JbZPz3kc2oP0XsL
83lUd8QD+Z2sFB2rL15XnBD1GETOxSvHqjJCcSBBDQKCAQBDp10kqbraGAyQ7//G
i0aesRvIG+p8HDWbD25nntGsobsMpNKwudnaYI8e+nhx5IM8SP4+7mxoFVHgiirx
zYxCYBynxJxpOCzfscxIaX1lAKTaOv9oL8txSaa2TS3stEZlifaf77B3bS7yEHV5
qVty0L/w9cfq4IaLqJj9z9uyqrSPSHDZqcoJWAixxzQAw8hS2+kfaKf+PgZIPyTG
vqszSEDGQkWwFt4yKzpxhYOPPdT7PPz3RoHx9q2vXctmQo4708BPqTw12mIOLHHg
7IMrCLd8/rWqySbxcOpARGe2qrqsJWtovaPeP+fIqOY0Ypb03lVBe07meKWAO2jZ
Ex65AoIBAFDfIDPZ0OeN/sYFALxiC9Z1n1Ahi4V0ncKckdNrW3AZt47+iabw5pX0
CTjTygS7Im8RsE5imO9NaZ1S4dq8xK90SolPaXoC0sBwm+U4ZlDu5owYHsGylQlC
XgQoWubq+3xZgXExfjxPu+sY4wJFoT04rAIoH43eMUUWsPHPwjeRmvc4MkgnFL3E
s7cgilF56suheQyZMM7MCy82DyLZ9Suy07eqSlj9xmfxdTDzLDouvSU35bC7mLr6
bQG8J2Lmz8z98t+L4A/WcFUvsUk4GAfRfo0H9VL/LXwkTK0IJDKT1FPRXewDrSwF
vti3Ws8O11YhSNYglh5a7a3bTqvQqHkCggEBAM/oLCz33rLaNQ7Ogcqd3i08vHrT
CYL+Yxb1ZEIegGgfpIMlYYByhneCcqhXTp8jleJJjRDoaJLfG2rzm4yQ/xIXh2nA
c31BSicJffk+DV1N7BoZG/OC7CSo0GTlJZ8sAKhAfUApYjVHuDp1GZdHBpLmeYs/
zSilstxPBgaKUZ92QNt32gjT/nrq/xYmPDR8AEaurS7cZtkAIsmWEJajmqP9PBNZ
eeCpKaa8m8cSjnSDbTXrA3l7ga2gdEcy++fh+5+VPmpPfnegzImJidC3eBfbDqjn
/zmUYntVTJ6FepsULoaXz1mQGNzgqCx8y3s0TZ4KmwakiRxwS1OUOWSRvtg=
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKgIBAAKCAgEAoDHNjtZEnbu8o4nWZR8gV4ugYlysC1QP2mxVN66RSXbeuxHi
aYHjMTYxMSaMLBuj240kP5brwmZ/Tpwy6xpDKmfNFebd1HmE38KuWn9fnZQdgylo
w9glcsTLxNOsI/wSV4htF/nRzsvAx95nM7HHbdbHNtPwRhExoNJQ6AOuCVtIAe9B
yAvzdV5voOhWJIahblPbVwU5NX4elm+XGsvlDP66lKcss9lBTjDnWAP4ZcMABBvb
h5Kc5T9aRBz1WAmUP0qs6+b/F/XYPIKzrOfRpWt+I/l60h1a/RkmlMR3xujLBh96
r6ATrYdIHDrg33snCkMUyEe6WfPbD02UTZCIttrfZqWy3ddpmV2FKRiGqPvSHIQa
GBHYZtjV33bdSfmZE25c3LHLCCCP6xJfzK4JMGPEhsDhk3XPWeY3qpKiNAcKSsjg
B8ibgpwCtP8Hb+h1GG3ZLfoMLYmoladL4crnff/RXhab7vrM67Ggz4uyqB+lXbDx
NK3UnmkY6xkX86REMSvJw/tCWBpFxn/2X8UlhgDd7fjgxD7HecowWlXSk+DOdKeD
pDsEGfi2J6/kfRn02QUKnJO6x7gOB5PQPoOjWwQkCaFx4zRngHFXYGuBIv9qNk8W
EtkuSnCbuLnjZrUmSCjgMsxQQqaXo7Cy/UKH97PSaNsD7hXaUmZLd2+kdpcCAwEA
AQKCAgEAi1Ckpxsa015owIToKks2kkxAsCpOCRATNW7PcbxkZ9J0A5abJAysq6io
gUk30Eg9aXvG0XKMGCWRg6j9806EqQVa6zg7JUSFVR/3B4cMfXtJaz8A+IkqkDQr
zkITy7u1q+Bel+JQH5s9TdTSRbfPa2vFFp6csCLV2Tnu2MgSe9qhteUAfVw/X4xA
YlyMRfm7vLo63+QQC8BiE4x6ifhWe8WwOAVnMAW58KlBGF9jkARVKD2d3rqXrhs5
glD44ZZ7Ecv8tK/Qm2LXqlA0uCNnRIhGTDz0HnUfI0vTLL/sNtVPc0S/Kqt5UYl8
IejmlhSBMECEe2U94Grd0OI0HnybF6KtZ1/YDxDS0AQyO6z4CLs8hUKbaLm6V/i4
+9fkWvn4DcS7stTeUe4+I9ua/uaDrrvTNBRH0gaLsl3m3ptPiqN8yigApsCyGrgm
hnxvE1DQCvRw+yeNjgEgtHEJe9z6pcX/0jrw0zH9hIMFM7nSXWONXGQ5AvWuaD1b
cF3JhAP8IYXPqwNDj6dr61vszMqh7iJgQklJm0YbiUiaf00vGpcVKdHYzInIXdoD
rGgPtpoDWx2rAOAdw56IltWV35lSz+zYaPGqQglc+pcMfTqE+7FoFFJmOz2cXxIl
jZehd7dXmBx/yYYedcamLH1A4vmsazJJIxXSRtt2pQ52hdvkU7kCggEBANHr3Ldc
ZYH7KYcowfjfmreAJaRP9SVo+1FtqiwU+I9pzYtT4aXqXLzlJUbHwkWyf3ameghb
EWRKuutC39zz71UsongjVpIPCFZjXXPlo5qxMLAZ0I5KBq4xYEoAplNSJye02HvT
F9KU/J84YwZuJFhWcFXPp5JiFxpW8t1t8iEOuFdvtNkZ1eOByFCC83a/l4sjKCaZ
xemvQCAX9rmZptqAsWAgUmnhJaTcpjv5jfbwqmJDfGgpPxBap8HlpRlF8V0tG/jZ
O0EYoPiHloOD/QoCWaqttQGxxxJW+FfDFo4rlo9xst//6nIVXPU59GbHYBXhio9f
s2UJXOC8sSh9lUsCggEBAMNbouqyltGiX8w3vicEAJDzWubrVkVsRdNyKLh7ARuM
uO1faQ/HuCyoagFx0tt+AnlOQRPU4YprmBZj1f8PSHoCtwfm3FXo7mpJVDu8T5zD
Ja7YOAjmLA42m33ZeAxMdZPTvRYSAP4uxMozrg/tl4md8/lYvStAcoMy5jYvUXF1
E1iWum5EJV4gqkBI5c18QYIzd/hbluxOUhI/fA9AQLK+I56fGMw0hdT48M5Iu/OU
nBVF8tuw7CXNUhxbfIP4Q8ahBcTu3FDBl/qIqsd7Zv7ckaS5Xr7J57189x8zZqNf
k/oHZ0Tdwl5cr29B28YC1vtJ7TLotcDZXXPvOeY/sGUCggEABSkCHPPFfwN4it0C
n6aHfBlHU5mvkgLZoq/Kbhj53zSfm9wtANIZA3+ygeHpMaNopLcE6u2qKMf5fkz/
icPpTzOwrrlXqHF8J/t7UZ0Ef4n5g2qvCMBjF6cZEdigPg4X7k7wv2J6BHArIZLW
RFMyy4Ucb8+R8/Q7UyduAulv+UYOW//f9zI+YsBO90Owzmt5Qy9TDlfbWJo5PlC4
fOl9A4QEWDOTMw0Yysutvm2tArP5zD6ScVEKPtGrrAWEIHHqs/qm5GAap8f+NP3I
QmVdNADIyXxJpcgD97xxkF64UDhcFBycZAs7bSB/T3vkOR6PixonOM0GcOZhBRk+
VZt4rwKCAQEAoal+SwvYpMfi0KM8VxsHwOuxSLBs5uwvaEfrDKa1hu/PxJcU4Psc
HNCNUH65x+sh7vJkBh4/OgXJiJW7a+NgzZ7bic1wfiNQ0GG4M+qkUwxmbab9z9dx
k5161Q0WO8816UvqCI6DhdR8Avv7SbEKmtY8JBZcDKO7X3jKawKDOglxJfktc7wu
1BLh8GqiyIXPzAf9emeIoCo73l/ssM4x+/g+j7AGnE3GhjQvSfWEm5BaDXyh+U0S
TkH3dgH7K1ZR99geZxZm+OkLdEaOVJ943uT2HUNM9UMt42+7LHWjtQSN9vUTbzi3
9NBsWPw9+0E0WCSYBm3uohT+McdAuZnwxQKCAQEAvQhO9GqMMtOnN/QUdU4FHVKl
R8vuJpT3w0ywBcHj5aYwPgkLxCYmfnZtD0kNPkP1FjlYz6C+cGUgNPNifoj6CwCA
oRrw7mgyhHuoum+7qlFwJzhuyx94Z4B7RbINfEsqk4mXGRssUUUBGd4Sh29w54SU
dQMn7s0LiwN6AOwOaAnjD9RlBE+021N9Dax71DuBu1RzA/Z1sC5EFYkL1C5B4kSJ
BYd0Nvru5DRidDrJuxr8tnGqOkNqT2kaujkVmFy6ra6nshkbErbd1LXZksM2PP96
bM5Pta4jX4ylcS8e5F16zmZZrtuBDla1kizM7tGdPQQztXeBHZzvdQDlWTHqkw==
-----END RSA PRIVATE KEY-----

15
vendor/github.com/golang/protobuf/.gitignore generated vendored Normal file
View file

@ -0,0 +1,15 @@
.DS_Store
*.[568ao]
*.ao
*.so
*.pyc
._*
.nfs.*
[568a].out
*~
*.orig
core
_obj
_test
_testmain.go
protoc-gen-go/testdata/multi/*.pb.go

3
vendor/github.com/golang/protobuf/AUTHORS generated vendored Normal file
View file

@ -0,0 +1,3 @@
# This source code refers to The Go Authors for copyright purposes.
# The master list of authors is in the main Go distribution,
# visible at http://tip.golang.org/AUTHORS.

3
vendor/github.com/golang/protobuf/CONTRIBUTORS generated vendored Normal file
View file

@ -0,0 +1,3 @@
# This source code was written by the Go contributors.
# The master list of contributors is in the main Go distribution,
# visible at http://tip.golang.org/CONTRIBUTORS.

31
vendor/github.com/golang/protobuf/LICENSE generated vendored Normal file
View file

@ -0,0 +1,31 @@
Go support for Protocol Buffers - Google's data interchange format
Copyright 2010 The Go Authors. All rights reserved.
https://github.com/golang/protobuf
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.

40
vendor/github.com/golang/protobuf/Make.protobuf generated vendored Normal file
View file

@ -0,0 +1,40 @@
# Go support for Protocol Buffers - Google's data interchange format
#
# Copyright 2010 The Go Authors. All rights reserved.
# https://github.com/golang/protobuf
#
# 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.
# Includable Makefile to add a rule for generating .pb.go files from .proto files
# (Google protocol buffer descriptions).
# Typical use if myproto.proto is a file in package mypackage in this directory:
#
# include $(GOROOT)/src/pkg/github.com/golang/protobuf/Make.protobuf
%.pb.go: %.proto
protoc --go_out=. $<

54
vendor/github.com/golang/protobuf/Makefile generated vendored Normal file
View file

@ -0,0 +1,54 @@
# Go support for Protocol Buffers - Google's data interchange format
#
# Copyright 2010 The Go Authors. All rights reserved.
# https://github.com/golang/protobuf
#
# 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.
all: install
install:
go install ./proto ./jsonpb ./ptypes
go install ./protoc-gen-go
test:
go test ./proto ./jsonpb ./ptypes
make -C protoc-gen-go/testdata test
clean:
go clean ./...
nuke:
go clean -i ./...
regenerate:
make -C protoc-gen-go/descriptor regenerate
make -C protoc-gen-go/plugin regenerate
make -C protoc-gen-go/testdata regenerate
make -C proto/testdata regenerate
make -C jsonpb/jsonpb_test_proto regenerate

199
vendor/github.com/golang/protobuf/README.md generated vendored Normal file
View file

@ -0,0 +1,199 @@
# Go support for Protocol Buffers
Google's data interchange format.
Copyright 2010 The Go Authors.
https://github.com/golang/protobuf
This package and the code it generates requires at least Go 1.4.
This software implements Go bindings for protocol buffers. For
information about protocol buffers themselves, see
https://developers.google.com/protocol-buffers/
## Installation ##
To use this software, you must:
- Install the standard C++ implementation of protocol buffers from
https://developers.google.com/protocol-buffers/
- Of course, install the Go compiler and tools from
https://golang.org/
See
https://golang.org/doc/install
for details or, if you are using gccgo, follow the instructions at
https://golang.org/doc/install/gccgo
- Grab the code from the repository and install the proto package.
The simplest way is to run `go get -u github.com/golang/protobuf/{proto,protoc-gen-go}`.
The compiler plugin, protoc-gen-go, will be installed in $GOBIN,
defaulting to $GOPATH/bin. It must be in your $PATH for the protocol
compiler, protoc, to find it.
This software has two parts: a 'protocol compiler plugin' that
generates Go source files that, once compiled, can access and manage
protocol buffers; and a library that implements run-time support for
encoding (marshaling), decoding (unmarshaling), and accessing protocol
buffers.
There is support for gRPC in Go using protocol buffers.
See the note at the bottom of this file for details.
There are no insertion points in the plugin.
## Using protocol buffers with Go ##
Once the software is installed, there are two steps to using it.
First you must compile the protocol buffer definitions and then import
them, with the support library, into your program.
To compile the protocol buffer definition, run protoc with the --go_out
parameter set to the directory you want to output the Go code to.
protoc --go_out=. *.proto
The generated files will be suffixed .pb.go. See the Test code below
for an example using such a file.
The package comment for the proto library contains text describing
the interface provided in Go for protocol buffers. Here is an edited
version.
==========
The proto package converts data structures to and from the
wire format of protocol buffers. It works in concert with the
Go source code generated for .proto files by the protocol compiler.
A summary of the properties of the protocol buffer interface
for a protocol buffer variable v:
- Names are turned from camel_case to CamelCase for export.
- There are no methods on v to set fields; just treat
them as structure fields.
- There are getters that return a field's value if set,
and return the field's default value if unset.
The getters work even if the receiver is a nil message.
- The zero value for a struct is its correct initialization state.
All desired fields must be set before marshaling.
- A Reset() method will restore a protobuf struct to its zero state.
- Non-repeated fields are pointers to the values; nil means unset.
That is, optional or required field int32 f becomes F *int32.
- Repeated fields are slices.
- Helper functions are available to aid the setting of fields.
Helpers for getting values are superseded by the
GetFoo methods and their use is deprecated.
msg.Foo = proto.String("hello") // set field
- Constants are defined to hold the default values of all fields that
have them. They have the form Default_StructName_FieldName.
Because the getter methods handle defaulted values,
direct use of these constants should be rare.
- Enums are given type names and maps from names to values.
Enum values are prefixed with the enum's type name. Enum types have
a String method, and a Enum method to assist in message construction.
- Nested groups and enums have type names prefixed with the name of
the surrounding message type.
- Extensions are given descriptor names that start with E_,
followed by an underscore-delimited list of the nested messages
that contain it (if any) followed by the CamelCased name of the
extension field itself. HasExtension, ClearExtension, GetExtension
and SetExtension are functions for manipulating extensions.
- Oneof field sets are given a single field in their message,
with distinguished wrapper types for each possible field value.
- Marshal and Unmarshal are functions to encode and decode the wire format.
When the .proto file specifies `syntax="proto3"`, there are some differences:
- Non-repeated fields of non-message type are values instead of pointers.
- Getters are only generated for message and oneof fields.
- Enum types do not get an Enum method.
Consider file test.proto, containing
```proto
package example;
enum FOO { X = 17; };
message Test {
required string label = 1;
optional int32 type = 2 [default=77];
repeated int64 reps = 3;
optional group OptionalGroup = 4 {
required string RequiredField = 5;
}
}
```
To create and play with a Test object from the example package,
```go
package main
import (
"log"
"github.com/golang/protobuf/proto"
"path/to/example"
)
func main() {
test := &example.Test {
Label: proto.String("hello"),
Type: proto.Int32(17),
Reps: []int64{1, 2, 3},
Optionalgroup: &example.Test_OptionalGroup {
RequiredField: proto.String("good bye"),
},
}
data, err := proto.Marshal(test)
if err != nil {
log.Fatal("marshaling error: ", err)
}
newTest := &example.Test{}
err = proto.Unmarshal(data, newTest)
if err != nil {
log.Fatal("unmarshaling error: ", err)
}
// Now test and newTest contain the same data.
if test.GetLabel() != newTest.GetLabel() {
log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel())
}
// etc.
}
```
## Parameters ##
To pass extra parameters to the plugin, use a comma-separated
parameter list separated from the output directory by a colon:
protoc --go_out=plugins=grpc,import_path=mypackage:. *.proto
- `import_prefix=xxx` - a prefix that is added onto the beginning of
all imports. Useful for things like generating protos in a
subdirectory, or regenerating vendored protobufs in-place.
- `import_path=foo/bar` - used as the package if no input files
declare `go_package`. If it contains slashes, everything up to the
rightmost slash is ignored.
- `plugins=plugin1+plugin2` - specifies the list of sub-plugins to
load. The only plugin in this repo is `grpc`.
- `Mfoo/bar.proto=quux/shme` - declares that foo/bar.proto is
associated with Go package quux/shme. This is subject to the
import_prefix parameter.
## gRPC Support ##
If a proto file specifies RPC services, protoc-gen-go can be instructed to
generate code compatible with gRPC (http://www.grpc.io/). To do this, pass
the `plugins` parameter to protoc-gen-go; the usual way is to insert it into
the --go_out argument to protoc:
protoc --go_out=plugins=grpc:. *.proto
## Plugins ##
The `protoc-gen-go/generator` package exposes a plugin interface,
which is used by the gRPC code generation. This interface is not
supported and is subject to incompatible changes without notice.

832
vendor/github.com/golang/protobuf/jsonpb/jsonpb.go generated vendored Normal file
View file

@ -0,0 +1,832 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2015 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// 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.
/*
Package jsonpb provides marshaling and unmarshaling between protocol buffers and JSON.
It follows the specification at https://developers.google.com/protocol-buffers/docs/proto3#json.
This package produces a different output than the standard "encoding/json" package,
which does not operate correctly on protocol buffers.
*/
package jsonpb
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"reflect"
"sort"
"strconv"
"strings"
"time"
"github.com/golang/protobuf/proto"
)
// Marshaler is a configurable object for converting between
// protocol buffer objects and a JSON representation for them.
type Marshaler struct {
// Whether to render enum values as integers, as opposed to string values.
EnumsAsInts bool
// Whether to render fields with zero values.
EmitDefaults bool
// A string to indent each level by. The presence of this field will
// also cause a space to appear between the field separator and
// value, and for newlines to be appear between fields and array
// elements.
Indent string
// Whether to use the original (.proto) name for fields.
OrigName bool
}
// Marshal marshals a protocol buffer into JSON.
func (m *Marshaler) Marshal(out io.Writer, pb proto.Message) error {
writer := &errWriter{writer: out}
return m.marshalObject(writer, pb, "", "")
}
// MarshalToString converts a protocol buffer object to JSON string.
func (m *Marshaler) MarshalToString(pb proto.Message) (string, error) {
var buf bytes.Buffer
if err := m.Marshal(&buf, pb); err != nil {
return "", err
}
return buf.String(), nil
}
type int32Slice []int32
// For sorting extensions ids to ensure stable output.
func (s int32Slice) Len() int { return len(s) }
func (s int32Slice) Less(i, j int) bool { return s[i] < s[j] }
func (s int32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
type wkt interface {
XXX_WellKnownType() string
}
// marshalObject writes a struct to the Writer.
func (m *Marshaler) marshalObject(out *errWriter, v proto.Message, indent, typeURL string) error {
s := reflect.ValueOf(v).Elem()
// Handle well-known types.
if wkt, ok := v.(wkt); ok {
switch wkt.XXX_WellKnownType() {
case "DoubleValue", "FloatValue", "Int64Value", "UInt64Value",
"Int32Value", "UInt32Value", "BoolValue", "StringValue", "BytesValue":
// "Wrappers use the same representation in JSON
// as the wrapped primitive type, ..."
sprop := proto.GetProperties(s.Type())
return m.marshalValue(out, sprop.Prop[0], s.Field(0), indent)
case "Any":
// Any is a bit more involved.
return m.marshalAny(out, v, indent)
case "Duration":
// "Generated output always contains 3, 6, or 9 fractional digits,
// depending on required precision."
s, ns := s.Field(0).Int(), s.Field(1).Int()
d := time.Duration(s)*time.Second + time.Duration(ns)*time.Nanosecond
x := fmt.Sprintf("%.9f", d.Seconds())
x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, "000")
out.write(`"`)
out.write(x)
out.write(`s"`)
return out.err
case "Struct":
// Let marshalValue handle the `fields` map.
// TODO: pass the correct Properties if needed.
return m.marshalValue(out, &proto.Properties{}, s.Field(0), indent)
case "Timestamp":
// "RFC 3339, where generated output will always be Z-normalized
// and uses 3, 6 or 9 fractional digits."
s, ns := s.Field(0).Int(), s.Field(1).Int()
t := time.Unix(s, ns).UTC()
// time.RFC3339Nano isn't exactly right (we need to get 3/6/9 fractional digits).
x := t.Format("2006-01-02T15:04:05.000000000")
x = strings.TrimSuffix(x, "000")
x = strings.TrimSuffix(x, "000")
out.write(`"`)
out.write(x)
out.write(`Z"`)
return out.err
case "Value":
// Value has a single oneof.
kind := s.Field(0)
if kind.IsNil() {
// "absence of any variant indicates an error"
return errors.New("nil Value")
}
// oneof -> *T -> T -> T.F
x := kind.Elem().Elem().Field(0)
// TODO: pass the correct Properties if needed.
return m.marshalValue(out, &proto.Properties{}, x, indent)
}
}
out.write("{")
if m.Indent != "" {
out.write("\n")
}
firstField := true
if typeURL != "" {
if err := m.marshalTypeURL(out, indent, typeURL); err != nil {
return err
}
firstField = false
}
for i := 0; i < s.NumField(); i++ {
value := s.Field(i)
valueField := s.Type().Field(i)
if strings.HasPrefix(valueField.Name, "XXX_") {
continue
}
// IsNil will panic on most value kinds.
switch value.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
if value.IsNil() {
continue
}
}
if !m.EmitDefaults {
switch value.Kind() {
case reflect.Bool:
if !value.Bool() {
continue
}
case reflect.Int32, reflect.Int64:
if value.Int() == 0 {
continue
}
case reflect.Uint32, reflect.Uint64:
if value.Uint() == 0 {
continue
}
case reflect.Float32, reflect.Float64:
if value.Float() == 0 {
continue
}
case reflect.String:
if value.Len() == 0 {
continue
}
}
}
// Oneof fields need special handling.
if valueField.Tag.Get("protobuf_oneof") != "" {
// value is an interface containing &T{real_value}.
sv := value.Elem().Elem() // interface -> *T -> T
value = sv.Field(0)
valueField = sv.Type().Field(0)
}
prop := jsonProperties(valueField, m.OrigName)
if !firstField {
m.writeSep(out)
}
if err := m.marshalField(out, prop, value, indent); err != nil {
return err
}
firstField = false
}
// Handle proto2 extensions.
if ep, ok := v.(proto.Message); ok {
extensions := proto.RegisteredExtensions(v)
// Sort extensions for stable output.
ids := make([]int32, 0, len(extensions))
for id, desc := range extensions {
if !proto.HasExtension(ep, desc) {
continue
}
ids = append(ids, id)
}
sort.Sort(int32Slice(ids))
for _, id := range ids {
desc := extensions[id]
if desc == nil {
// unknown extension
continue
}
ext, extErr := proto.GetExtension(ep, desc)
if extErr != nil {
return extErr
}
value := reflect.ValueOf(ext)
var prop proto.Properties
prop.Parse(desc.Tag)
prop.JSONName = fmt.Sprintf("[%s]", desc.Name)
if !firstField {
m.writeSep(out)
}
if err := m.marshalField(out, &prop, value, indent); err != nil {
return err
}
firstField = false
}
}
if m.Indent != "" {
out.write("\n")
out.write(indent)
}
out.write("}")
return out.err
}
func (m *Marshaler) writeSep(out *errWriter) {
if m.Indent != "" {
out.write(",\n")
} else {
out.write(",")
}
}
func (m *Marshaler) marshalAny(out *errWriter, any proto.Message, indent string) error {
// "If the Any contains a value that has a special JSON mapping,
// it will be converted as follows: {"@type": xxx, "value": yyy}.
// Otherwise, the value will be converted into a JSON object,
// and the "@type" field will be inserted to indicate the actual data type."
v := reflect.ValueOf(any).Elem()
turl := v.Field(0).String()
val := v.Field(1).Bytes()
// Only the part of type_url after the last slash is relevant.
mname := turl
if slash := strings.LastIndex(mname, "/"); slash >= 0 {
mname = mname[slash+1:]
}
mt := proto.MessageType(mname)
if mt == nil {
return fmt.Errorf("unknown message type %q", mname)
}
msg := reflect.New(mt.Elem()).Interface().(proto.Message)
if err := proto.Unmarshal(val, msg); err != nil {
return err
}
if _, ok := msg.(wkt); ok {
out.write("{")
if m.Indent != "" {
out.write("\n")
}
if err := m.marshalTypeURL(out, indent, turl); err != nil {
return err
}
m.writeSep(out)
if m.Indent != "" {
out.write(indent)
out.write(m.Indent)
out.write(`"value": `)
} else {
out.write(`"value":`)
}
if err := m.marshalObject(out, msg, indent+m.Indent, ""); err != nil {
return err
}
if m.Indent != "" {
out.write("\n")
out.write(indent)
}
out.write("}")
return out.err
}
return m.marshalObject(out, msg, indent, turl)
}
func (m *Marshaler) marshalTypeURL(out *errWriter, indent, typeURL string) error {
if m.Indent != "" {
out.write(indent)
out.write(m.Indent)
}
out.write(`"@type":`)
if m.Indent != "" {
out.write(" ")
}
b, err := json.Marshal(typeURL)
if err != nil {
return err
}
out.write(string(b))
return out.err
}
// marshalField writes field description and value to the Writer.
func (m *Marshaler) marshalField(out *errWriter, prop *proto.Properties, v reflect.Value, indent string) error {
if m.Indent != "" {
out.write(indent)
out.write(m.Indent)
}
out.write(`"`)
out.write(prop.JSONName)
out.write(`":`)
if m.Indent != "" {
out.write(" ")
}
if err := m.marshalValue(out, prop, v, indent); err != nil {
return err
}
return nil
}
// marshalValue writes the value to the Writer.
func (m *Marshaler) marshalValue(out *errWriter, prop *proto.Properties, v reflect.Value, indent string) error {
var err error
v = reflect.Indirect(v)
// Handle repeated elements.
if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 {
out.write("[")
comma := ""
for i := 0; i < v.Len(); i++ {
sliceVal := v.Index(i)
out.write(comma)
if m.Indent != "" {
out.write("\n")
out.write(indent)
out.write(m.Indent)
out.write(m.Indent)
}
if err := m.marshalValue(out, prop, sliceVal, indent+m.Indent); err != nil {
return err
}
comma = ","
}
if m.Indent != "" {
out.write("\n")
out.write(indent)
out.write(m.Indent)
}
out.write("]")
return out.err
}
// Handle well-known types.
// Most are handled up in marshalObject (because 99% are messages).
type wkt interface {
XXX_WellKnownType() string
}
if wkt, ok := v.Interface().(wkt); ok {
switch wkt.XXX_WellKnownType() {
case "NullValue":
out.write("null")
return out.err
}
}
// Handle enumerations.
if !m.EnumsAsInts && prop.Enum != "" {
// Unknown enum values will are stringified by the proto library as their
// value. Such values should _not_ be quoted or they will be interpreted
// as an enum string instead of their value.
enumStr := v.Interface().(fmt.Stringer).String()
var valStr string
if v.Kind() == reflect.Ptr {
valStr = strconv.Itoa(int(v.Elem().Int()))
} else {
valStr = strconv.Itoa(int(v.Int()))
}
isKnownEnum := enumStr != valStr
if isKnownEnum {
out.write(`"`)
}
out.write(enumStr)
if isKnownEnum {
out.write(`"`)
}
return out.err
}
// Handle nested messages.
if v.Kind() == reflect.Struct {
return m.marshalObject(out, v.Addr().Interface().(proto.Message), indent+m.Indent, "")
}
// Handle maps.
// Since Go randomizes map iteration, we sort keys for stable output.
if v.Kind() == reflect.Map {
out.write(`{`)
keys := v.MapKeys()
sort.Sort(mapKeys(keys))
for i, k := range keys {
if i > 0 {
out.write(`,`)
}
if m.Indent != "" {
out.write("\n")
out.write(indent)
out.write(m.Indent)
out.write(m.Indent)
}
b, err := json.Marshal(k.Interface())
if err != nil {
return err
}
s := string(b)
// If the JSON is not a string value, encode it again to make it one.
if !strings.HasPrefix(s, `"`) {
b, err := json.Marshal(s)
if err != nil {
return err
}
s = string(b)
}
out.write(s)
out.write(`:`)
if m.Indent != "" {
out.write(` `)
}
if err := m.marshalValue(out, prop, v.MapIndex(k), indent+m.Indent); err != nil {
return err
}
}
if m.Indent != "" {
out.write("\n")
out.write(indent)
out.write(m.Indent)
}
out.write(`}`)
return out.err
}
// Default handling defers to the encoding/json library.
b, err := json.Marshal(v.Interface())
if err != nil {
return err
}
needToQuote := string(b[0]) != `"` && (v.Kind() == reflect.Int64 || v.Kind() == reflect.Uint64)
if needToQuote {
out.write(`"`)
}
out.write(string(b))
if needToQuote {
out.write(`"`)
}
return out.err
}
// Unmarshaler is a configurable object for converting from a JSON
// representation to a protocol buffer object.
type Unmarshaler struct {
// Whether to allow messages to contain unknown fields, as opposed to
// failing to unmarshal.
AllowUnknownFields bool
}
// UnmarshalNext unmarshals the next protocol buffer from a JSON object stream.
// This function is lenient and will decode any options permutations of the
// related Marshaler.
func (u *Unmarshaler) UnmarshalNext(dec *json.Decoder, pb proto.Message) error {
inputValue := json.RawMessage{}
if err := dec.Decode(&inputValue); err != nil {
return err
}
return u.unmarshalValue(reflect.ValueOf(pb).Elem(), inputValue, nil)
}
// Unmarshal unmarshals a JSON object stream into a protocol
// buffer. This function is lenient and will decode any options
// permutations of the related Marshaler.
func (u *Unmarshaler) Unmarshal(r io.Reader, pb proto.Message) error {
dec := json.NewDecoder(r)
return u.UnmarshalNext(dec, pb)
}
// UnmarshalNext unmarshals the next protocol buffer from a JSON object stream.
// This function is lenient and will decode any options permutations of the
// related Marshaler.
func UnmarshalNext(dec *json.Decoder, pb proto.Message) error {
return new(Unmarshaler).UnmarshalNext(dec, pb)
}
// Unmarshal unmarshals a JSON object stream into a protocol
// buffer. This function is lenient and will decode any options
// permutations of the related Marshaler.
func Unmarshal(r io.Reader, pb proto.Message) error {
return new(Unmarshaler).Unmarshal(r, pb)
}
// UnmarshalString will populate the fields of a protocol buffer based
// on a JSON string. This function is lenient and will decode any options
// permutations of the related Marshaler.
func UnmarshalString(str string, pb proto.Message) error {
return new(Unmarshaler).Unmarshal(strings.NewReader(str), pb)
}
// unmarshalValue converts/copies a value into the target.
// prop may be nil.
func (u *Unmarshaler) unmarshalValue(target reflect.Value, inputValue json.RawMessage, prop *proto.Properties) error {
targetType := target.Type()
// Allocate memory for pointer fields.
if targetType.Kind() == reflect.Ptr {
target.Set(reflect.New(targetType.Elem()))
return u.unmarshalValue(target.Elem(), inputValue, prop)
}
// Handle well-known types.
type wkt interface {
XXX_WellKnownType() string
}
if wkt, ok := target.Addr().Interface().(wkt); ok {
switch wkt.XXX_WellKnownType() {
case "DoubleValue", "FloatValue", "Int64Value", "UInt64Value",
"Int32Value", "UInt32Value", "BoolValue", "StringValue", "BytesValue":
// "Wrappers use the same representation in JSON
// as the wrapped primitive type, except that null is allowed."
// encoding/json will turn JSON `null` into Go `nil`,
// so we don't have to do any extra work.
return u.unmarshalValue(target.Field(0), inputValue, prop)
case "Any":
return fmt.Errorf("unmarshaling Any not supported yet")
case "Duration":
unq, err := strconv.Unquote(string(inputValue))
if err != nil {
return err
}
d, err := time.ParseDuration(unq)
if err != nil {
return fmt.Errorf("bad Duration: %v", err)
}
ns := d.Nanoseconds()
s := ns / 1e9
ns %= 1e9
target.Field(0).SetInt(s)
target.Field(1).SetInt(ns)
return nil
case "Timestamp":
unq, err := strconv.Unquote(string(inputValue))
if err != nil {
return err
}
t, err := time.Parse(time.RFC3339Nano, unq)
if err != nil {
return fmt.Errorf("bad Timestamp: %v", err)
}
ns := t.UnixNano()
s := ns / 1e9
ns %= 1e9
target.Field(0).SetInt(s)
target.Field(1).SetInt(ns)
return nil
}
}
// Handle enums, which have an underlying type of int32,
// and may appear as strings.
// The case of an enum appearing as a number is handled
// at the bottom of this function.
if inputValue[0] == '"' && prop != nil && prop.Enum != "" {
vmap := proto.EnumValueMap(prop.Enum)
// Don't need to do unquoting; valid enum names
// are from a limited character set.
s := inputValue[1 : len(inputValue)-1]
n, ok := vmap[string(s)]
if !ok {
return fmt.Errorf("unknown value %q for enum %s", s, prop.Enum)
}
if target.Kind() == reflect.Ptr { // proto2
target.Set(reflect.New(targetType.Elem()))
target = target.Elem()
}
target.SetInt(int64(n))
return nil
}
// Handle nested messages.
if targetType.Kind() == reflect.Struct {
var jsonFields map[string]json.RawMessage
if err := json.Unmarshal(inputValue, &jsonFields); err != nil {
return err
}
consumeField := func(prop *proto.Properties) (json.RawMessage, bool) {
// Be liberal in what names we accept; both orig_name and camelName are okay.
fieldNames := acceptedJSONFieldNames(prop)
vOrig, okOrig := jsonFields[fieldNames.orig]
vCamel, okCamel := jsonFields[fieldNames.camel]
if !okOrig && !okCamel {
return nil, false
}
// If, for some reason, both are present in the data, favour the camelName.
var raw json.RawMessage
if okOrig {
raw = vOrig
delete(jsonFields, fieldNames.orig)
}
if okCamel {
raw = vCamel
delete(jsonFields, fieldNames.camel)
}
return raw, true
}
sprops := proto.GetProperties(targetType)
for i := 0; i < target.NumField(); i++ {
ft := target.Type().Field(i)
if strings.HasPrefix(ft.Name, "XXX_") {
continue
}
valueForField, ok := consumeField(sprops.Prop[i])
if !ok {
continue
}
if err := u.unmarshalValue(target.Field(i), valueForField, sprops.Prop[i]); err != nil {
return err
}
}
// Check for any oneof fields.
if len(jsonFields) > 0 {
for _, oop := range sprops.OneofTypes {
raw, ok := consumeField(oop.Prop)
if !ok {
continue
}
nv := reflect.New(oop.Type.Elem())
target.Field(oop.Field).Set(nv)
if err := u.unmarshalValue(nv.Elem().Field(0), raw, oop.Prop); err != nil {
return err
}
}
}
if !u.AllowUnknownFields && len(jsonFields) > 0 {
// Pick any field to be the scapegoat.
var f string
for fname := range jsonFields {
f = fname
break
}
return fmt.Errorf("unknown field %q in %v", f, targetType)
}
return nil
}
// Handle arrays (which aren't encoded bytes)
if targetType.Kind() == reflect.Slice && targetType.Elem().Kind() != reflect.Uint8 {
var slc []json.RawMessage
if err := json.Unmarshal(inputValue, &slc); err != nil {
return err
}
len := len(slc)
target.Set(reflect.MakeSlice(targetType, len, len))
for i := 0; i < len; i++ {
if err := u.unmarshalValue(target.Index(i), slc[i], prop); err != nil {
return err
}
}
return nil
}
// Handle maps (whose keys are always strings)
if targetType.Kind() == reflect.Map {
var mp map[string]json.RawMessage
if err := json.Unmarshal(inputValue, &mp); err != nil {
return err
}
target.Set(reflect.MakeMap(targetType))
var keyprop, valprop *proto.Properties
if prop != nil {
// These could still be nil if the protobuf metadata is broken somehow.
// TODO: This won't work because the fields are unexported.
// We should probably just reparse them.
//keyprop, valprop = prop.mkeyprop, prop.mvalprop
}
for ks, raw := range mp {
// Unmarshal map key. The core json library already decoded the key into a
// string, so we handle that specially. Other types were quoted post-serialization.
var k reflect.Value
if targetType.Key().Kind() == reflect.String {
k = reflect.ValueOf(ks)
} else {
k = reflect.New(targetType.Key()).Elem()
if err := u.unmarshalValue(k, json.RawMessage(ks), keyprop); err != nil {
return err
}
}
// Unmarshal map value.
v := reflect.New(targetType.Elem()).Elem()
if err := u.unmarshalValue(v, raw, valprop); err != nil {
return err
}
target.SetMapIndex(k, v)
}
return nil
}
// 64-bit integers can be encoded as strings. In this case we drop
// the quotes and proceed as normal.
isNum := targetType.Kind() == reflect.Int64 || targetType.Kind() == reflect.Uint64
if isNum && strings.HasPrefix(string(inputValue), `"`) {
inputValue = inputValue[1 : len(inputValue)-1]
}
// Use the encoding/json for parsing other value types.
return json.Unmarshal(inputValue, target.Addr().Interface())
}
// jsonProperties returns parsed proto.Properties for the field and corrects JSONName attribute.
func jsonProperties(f reflect.StructField, origName bool) *proto.Properties {
var prop proto.Properties
prop.Init(f.Type, f.Name, f.Tag.Get("protobuf"), &f)
if origName || prop.JSONName == "" {
prop.JSONName = prop.OrigName
}
return &prop
}
type fieldNames struct {
orig, camel string
}
func acceptedJSONFieldNames(prop *proto.Properties) fieldNames {
opts := fieldNames{orig: prop.OrigName, camel: prop.OrigName}
if prop.JSONName != "" {
opts.camel = prop.JSONName
}
return opts
}
// Writer wrapper inspired by https://blog.golang.org/errors-are-values
type errWriter struct {
writer io.Writer
err error
}
func (w *errWriter) write(str string) {
if w.err != nil {
return
}
_, w.err = w.writer.Write([]byte(str))
}
// Map fields may have key types of non-float scalars, strings and enums.
// The easiest way to sort them in some deterministic order is to use fmt.
// If this turns out to be inefficient we can always consider other options,
// such as doing a Schwartzian transform.
//
// Numeric keys are sorted in numeric order per
// https://developers.google.com/protocol-buffers/docs/proto#maps.
type mapKeys []reflect.Value
func (s mapKeys) Len() int { return len(s) }
func (s mapKeys) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s mapKeys) Less(i, j int) bool {
if k := s[i].Kind(); k == s[j].Kind() {
switch k {
case reflect.Int32, reflect.Int64:
return s[i].Int() < s[j].Int()
case reflect.Uint32, reflect.Uint64:
return s[i].Uint() < s[j].Uint()
}
}
return fmt.Sprint(s[i].Interface()) < fmt.Sprint(s[j].Interface())
}

557
vendor/github.com/golang/protobuf/jsonpb/jsonpb_test.go generated vendored Normal file
View file

@ -0,0 +1,557 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2015 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// 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.
package jsonpb
import (
"bytes"
"encoding/json"
"io"
"reflect"
"strings"
"testing"
"github.com/golang/protobuf/proto"
pb "github.com/golang/protobuf/jsonpb/jsonpb_test_proto"
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
anypb "github.com/golang/protobuf/ptypes/any"
durpb "github.com/golang/protobuf/ptypes/duration"
stpb "github.com/golang/protobuf/ptypes/struct"
tspb "github.com/golang/protobuf/ptypes/timestamp"
wpb "github.com/golang/protobuf/ptypes/wrappers"
)
var (
marshaler = Marshaler{}
marshalerAllOptions = Marshaler{
Indent: " ",
}
simpleObject = &pb.Simple{
OInt32: proto.Int32(-32),
OInt64: proto.Int64(-6400000000),
OUint32: proto.Uint32(32),
OUint64: proto.Uint64(6400000000),
OSint32: proto.Int32(-13),
OSint64: proto.Int64(-2600000000),
OFloat: proto.Float32(3.14),
ODouble: proto.Float64(6.02214179e23),
OBool: proto.Bool(true),
OString: proto.String("hello \"there\""),
OBytes: []byte("beep boop"),
}
simpleObjectJSON = `{` +
`"oBool":true,` +
`"oInt32":-32,` +
`"oInt64":"-6400000000",` +
`"oUint32":32,` +
`"oUint64":"6400000000",` +
`"oSint32":-13,` +
`"oSint64":"-2600000000",` +
`"oFloat":3.14,` +
`"oDouble":6.02214179e+23,` +
`"oString":"hello \"there\"",` +
`"oBytes":"YmVlcCBib29w"` +
`}`
simpleObjectPrettyJSON = `{
"oBool": true,
"oInt32": -32,
"oInt64": "-6400000000",
"oUint32": 32,
"oUint64": "6400000000",
"oSint32": -13,
"oSint64": "-2600000000",
"oFloat": 3.14,
"oDouble": 6.02214179e+23,
"oString": "hello \"there\"",
"oBytes": "YmVlcCBib29w"
}`
repeatsObject = &pb.Repeats{
RBool: []bool{true, false, true},
RInt32: []int32{-3, -4, -5},
RInt64: []int64{-123456789, -987654321},
RUint32: []uint32{1, 2, 3},
RUint64: []uint64{6789012345, 3456789012},
RSint32: []int32{-1, -2, -3},
RSint64: []int64{-6789012345, -3456789012},
RFloat: []float32{3.14, 6.28},
RDouble: []float64{299792458, 6.62606957e-34},
RString: []string{"happy", "days"},
RBytes: [][]byte{[]byte("skittles"), []byte("m&m's")},
}
repeatsObjectJSON = `{` +
`"rBool":[true,false,true],` +
`"rInt32":[-3,-4,-5],` +
`"rInt64":["-123456789","-987654321"],` +
`"rUint32":[1,2,3],` +
`"rUint64":["6789012345","3456789012"],` +
`"rSint32":[-1,-2,-3],` +
`"rSint64":["-6789012345","-3456789012"],` +
`"rFloat":[3.14,6.28],` +
`"rDouble":[2.99792458e+08,6.62606957e-34],` +
`"rString":["happy","days"],` +
`"rBytes":["c2tpdHRsZXM=","bSZtJ3M="]` +
`}`
repeatsObjectPrettyJSON = `{
"rBool": [
true,
false,
true
],
"rInt32": [
-3,
-4,
-5
],
"rInt64": [
"-123456789",
"-987654321"
],
"rUint32": [
1,
2,
3
],
"rUint64": [
"6789012345",
"3456789012"
],
"rSint32": [
-1,
-2,
-3
],
"rSint64": [
"-6789012345",
"-3456789012"
],
"rFloat": [
3.14,
6.28
],
"rDouble": [
2.99792458e+08,
6.62606957e-34
],
"rString": [
"happy",
"days"
],
"rBytes": [
"c2tpdHRsZXM=",
"bSZtJ3M="
]
}`
innerSimple = &pb.Simple{OInt32: proto.Int32(-32)}
innerSimple2 = &pb.Simple{OInt64: proto.Int64(25)}
innerRepeats = &pb.Repeats{RString: []string{"roses", "red"}}
innerRepeats2 = &pb.Repeats{RString: []string{"violets", "blue"}}
complexObject = &pb.Widget{
Color: pb.Widget_GREEN.Enum(),
RColor: []pb.Widget_Color{pb.Widget_RED, pb.Widget_GREEN, pb.Widget_BLUE},
Simple: innerSimple,
RSimple: []*pb.Simple{innerSimple, innerSimple2},
Repeats: innerRepeats,
RRepeats: []*pb.Repeats{innerRepeats, innerRepeats2},
}
complexObjectJSON = `{"color":"GREEN",` +
`"rColor":["RED","GREEN","BLUE"],` +
`"simple":{"oInt32":-32},` +
`"rSimple":[{"oInt32":-32},{"oInt64":"25"}],` +
`"repeats":{"rString":["roses","red"]},` +
`"rRepeats":[{"rString":["roses","red"]},{"rString":["violets","blue"]}]` +
`}`
complexObjectPrettyJSON = `{
"color": "GREEN",
"rColor": [
"RED",
"GREEN",
"BLUE"
],
"simple": {
"oInt32": -32
},
"rSimple": [
{
"oInt32": -32
},
{
"oInt64": "25"
}
],
"repeats": {
"rString": [
"roses",
"red"
]
},
"rRepeats": [
{
"rString": [
"roses",
"red"
]
},
{
"rString": [
"violets",
"blue"
]
}
]
}`
colorPrettyJSON = `{
"color": 2
}`
colorListPrettyJSON = `{
"color": 1000,
"rColor": [
"RED"
]
}`
nummyPrettyJSON = `{
"nummy": {
"1": 2,
"3": 4
}
}`
objjyPrettyJSON = `{
"objjy": {
"1": {
"dub": 1
}
}
}`
realNumber = &pb.Real{Value: proto.Float64(3.14159265359)}
realNumberName = "Pi"
complexNumber = &pb.Complex{Imaginary: proto.Float64(0.5772156649)}
realNumberJSON = `{` +
`"value":3.14159265359,` +
`"[jsonpb.Complex.real_extension]":{"imaginary":0.5772156649},` +
`"[jsonpb.name]":"Pi"` +
`}`
anySimple = &pb.KnownTypes{
An: &anypb.Any{
TypeUrl: "something.example.com/jsonpb.Simple",
Value: []byte{
// &pb.Simple{OBool:true}
1 << 3, 1,
},
},
}
anySimpleJSON = `{"an":{"@type":"something.example.com/jsonpb.Simple","oBool":true}}`
anySimplePrettyJSON = `{
"an": {
"@type": "something.example.com/jsonpb.Simple",
"oBool": true
}
}`
anyWellKnown = &pb.KnownTypes{
An: &anypb.Any{
TypeUrl: "type.googleapis.com/google.protobuf.Duration",
Value: []byte{
// &durpb.Duration{Seconds: 1, Nanos: 212000000 }
1 << 3, 1, // seconds
2 << 3, 0x80, 0xba, 0x8b, 0x65, // nanos
},
},
}
anyWellKnownJSON = `{"an":{"@type":"type.googleapis.com/google.protobuf.Duration","value":"1.212s"}}`
anyWellKnownPrettyJSON = `{
"an": {
"@type": "type.googleapis.com/google.protobuf.Duration",
"value": "1.212s"
}
}`
)
func init() {
if err := proto.SetExtension(realNumber, pb.E_Name, &realNumberName); err != nil {
panic(err)
}
if err := proto.SetExtension(realNumber, pb.E_Complex_RealExtension, complexNumber); err != nil {
panic(err)
}
}
var marshalingTests = []struct {
desc string
marshaler Marshaler
pb proto.Message
json string
}{
{"simple flat object", marshaler, simpleObject, simpleObjectJSON},
{"simple pretty object", marshalerAllOptions, simpleObject, simpleObjectPrettyJSON},
{"repeated fields flat object", marshaler, repeatsObject, repeatsObjectJSON},
{"repeated fields pretty object", marshalerAllOptions, repeatsObject, repeatsObjectPrettyJSON},
{"nested message/enum flat object", marshaler, complexObject, complexObjectJSON},
{"nested message/enum pretty object", marshalerAllOptions, complexObject, complexObjectPrettyJSON},
{"enum-string flat object", Marshaler{},
&pb.Widget{Color: pb.Widget_BLUE.Enum()}, `{"color":"BLUE"}`},
{"enum-value pretty object", Marshaler{EnumsAsInts: true, Indent: " "},
&pb.Widget{Color: pb.Widget_BLUE.Enum()}, colorPrettyJSON},
{"unknown enum value object", marshalerAllOptions,
&pb.Widget{Color: pb.Widget_Color(1000).Enum(), RColor: []pb.Widget_Color{pb.Widget_RED}}, colorListPrettyJSON},
{"repeated proto3 enum", Marshaler{},
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
proto3pb.Message_PUNS,
proto3pb.Message_SLAPSTICK,
}},
`{"rFunny":["PUNS","SLAPSTICK"]}`},
{"repeated proto3 enum as int", Marshaler{EnumsAsInts: true},
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
proto3pb.Message_PUNS,
proto3pb.Message_SLAPSTICK,
}},
`{"rFunny":[1,2]}`},
{"empty value", marshaler, &pb.Simple3{}, `{}`},
{"empty value emitted", Marshaler{EmitDefaults: true}, &pb.Simple3{}, `{"dub":0}`},
{"map<int64, int32>", marshaler, &pb.Mappy{Nummy: map[int64]int32{1: 2, 3: 4}}, `{"nummy":{"1":2,"3":4}}`},
{"map<int64, int32>", marshalerAllOptions, &pb.Mappy{Nummy: map[int64]int32{1: 2, 3: 4}}, nummyPrettyJSON},
{"map<string, string>", marshaler,
&pb.Mappy{Strry: map[string]string{`"one"`: "two", "three": "four"}},
`{"strry":{"\"one\"":"two","three":"four"}}`},
{"map<int32, Object>", marshaler,
&pb.Mappy{Objjy: map[int32]*pb.Simple3{1: &pb.Simple3{Dub: 1}}}, `{"objjy":{"1":{"dub":1}}}`},
{"map<int32, Object>", marshalerAllOptions,
&pb.Mappy{Objjy: map[int32]*pb.Simple3{1: &pb.Simple3{Dub: 1}}}, objjyPrettyJSON},
{"map<int64, string>", marshaler, &pb.Mappy{Buggy: map[int64]string{1234: "yup"}},
`{"buggy":{"1234":"yup"}}`},
{"map<bool, bool>", marshaler, &pb.Mappy{Booly: map[bool]bool{false: true}}, `{"booly":{"false":true}}`},
// TODO: This is broken.
//{"map<string, enum>", marshaler, &pb.Mappy{Enumy: map[string]pb.Numeral{"XIV": pb.Numeral_ROMAN}}, `{"enumy":{"XIV":"ROMAN"}`},
{"map<string, enum as int>", Marshaler{EnumsAsInts: true}, &pb.Mappy{Enumy: map[string]pb.Numeral{"XIV": pb.Numeral_ROMAN}}, `{"enumy":{"XIV":2}}`},
{"map<int32, bool>", marshaler, &pb.Mappy{S32Booly: map[int32]bool{1: true, 3: false, 10: true, 12: false}}, `{"s32booly":{"1":true,"3":false,"10":true,"12":false}}`},
{"map<int64, bool>", marshaler, &pb.Mappy{S64Booly: map[int64]bool{1: true, 3: false, 10: true, 12: false}}, `{"s64booly":{"1":true,"3":false,"10":true,"12":false}}`},
{"map<uint32, bool>", marshaler, &pb.Mappy{U32Booly: map[uint32]bool{1: true, 3: false, 10: true, 12: false}}, `{"u32booly":{"1":true,"3":false,"10":true,"12":false}}`},
{"map<uint64, bool>", marshaler, &pb.Mappy{U64Booly: map[uint64]bool{1: true, 3: false, 10: true, 12: false}}, `{"u64booly":{"1":true,"3":false,"10":true,"12":false}}`},
{"proto2 map<int64, string>", marshaler, &pb.Maps{MInt64Str: map[int64]string{213: "cat"}},
`{"mInt64Str":{"213":"cat"}}`},
{"proto2 map<bool, Object>", marshaler,
&pb.Maps{MBoolSimple: map[bool]*pb.Simple{true: &pb.Simple{OInt32: proto.Int32(1)}}},
`{"mBoolSimple":{"true":{"oInt32":1}}}`},
{"oneof, not set", marshaler, &pb.MsgWithOneof{}, `{}`},
{"oneof, set", marshaler, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Title{"Grand Poobah"}}, `{"title":"Grand Poobah"}`},
{"force orig_name", Marshaler{OrigName: true}, &pb.Simple{OInt32: proto.Int32(4)},
`{"o_int32":4}`},
{"proto2 extension", marshaler, realNumber, realNumberJSON},
{"Any with message", marshaler, anySimple, anySimpleJSON},
{"Any with message and indent", marshalerAllOptions, anySimple, anySimplePrettyJSON},
{"Any with WKT", marshaler, anyWellKnown, anyWellKnownJSON},
{"Any with WKT and indent", marshalerAllOptions, anyWellKnown, anyWellKnownPrettyJSON},
{"Duration", marshaler, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 3}}, `{"dur":"3.000s"}`},
{"Struct", marshaler, &pb.KnownTypes{St: &stpb.Struct{
Fields: map[string]*stpb.Value{
"one": &stpb.Value{Kind: &stpb.Value_StringValue{"loneliest number"}},
"two": &stpb.Value{Kind: &stpb.Value_NullValue{stpb.NullValue_NULL_VALUE}},
},
}}, `{"st":{"one":"loneliest number","two":null}}`},
{"Timestamp", marshaler, &pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: 14e8, Nanos: 21e6}}, `{"ts":"2014-05-13T16:53:20.021Z"}`},
{"DoubleValue", marshaler, &pb.KnownTypes{Dbl: &wpb.DoubleValue{Value: 1.2}}, `{"dbl":1.2}`},
{"FloatValue", marshaler, &pb.KnownTypes{Flt: &wpb.FloatValue{Value: 1.2}}, `{"flt":1.2}`},
{"Int64Value", marshaler, &pb.KnownTypes{I64: &wpb.Int64Value{Value: -3}}, `{"i64":"-3"}`},
{"UInt64Value", marshaler, &pb.KnownTypes{U64: &wpb.UInt64Value{Value: 3}}, `{"u64":"3"}`},
{"Int32Value", marshaler, &pb.KnownTypes{I32: &wpb.Int32Value{Value: -4}}, `{"i32":-4}`},
{"UInt32Value", marshaler, &pb.KnownTypes{U32: &wpb.UInt32Value{Value: 4}}, `{"u32":4}`},
{"BoolValue", marshaler, &pb.KnownTypes{Bool: &wpb.BoolValue{Value: true}}, `{"bool":true}`},
{"StringValue", marshaler, &pb.KnownTypes{Str: &wpb.StringValue{Value: "plush"}}, `{"str":"plush"}`},
{"BytesValue", marshaler, &pb.KnownTypes{Bytes: &wpb.BytesValue{Value: []byte("wow")}}, `{"bytes":"d293"}`},
}
func TestMarshaling(t *testing.T) {
for _, tt := range marshalingTests {
json, err := tt.marshaler.MarshalToString(tt.pb)
if err != nil {
t.Errorf("%s: marshaling error: %v", tt.desc, err)
} else if tt.json != json {
t.Errorf("%s: got [%v] want [%v]", tt.desc, json, tt.json)
}
}
}
var unmarshalingTests = []struct {
desc string
unmarshaler Unmarshaler
json string
pb proto.Message
}{
{"simple flat object", Unmarshaler{}, simpleObjectJSON, simpleObject},
{"simple pretty object", Unmarshaler{}, simpleObjectPrettyJSON, simpleObject},
{"repeated fields flat object", Unmarshaler{}, repeatsObjectJSON, repeatsObject},
{"repeated fields pretty object", Unmarshaler{}, repeatsObjectPrettyJSON, repeatsObject},
{"nested message/enum flat object", Unmarshaler{}, complexObjectJSON, complexObject},
{"nested message/enum pretty object", Unmarshaler{}, complexObjectPrettyJSON, complexObject},
{"enum-string object", Unmarshaler{}, `{"color":"BLUE"}`, &pb.Widget{Color: pb.Widget_BLUE.Enum()}},
{"enum-value object", Unmarshaler{}, "{\n \"color\": 2\n}", &pb.Widget{Color: pb.Widget_BLUE.Enum()}},
{"unknown field with allowed option", Unmarshaler{AllowUnknownFields: true}, `{"unknown": "foo"}`, new(pb.Simple)},
{"proto3 enum string", Unmarshaler{}, `{"hilarity":"PUNS"}`, &proto3pb.Message{Hilarity: proto3pb.Message_PUNS}},
{"proto3 enum value", Unmarshaler{}, `{"hilarity":1}`, &proto3pb.Message{Hilarity: proto3pb.Message_PUNS}},
{"unknown enum value object",
Unmarshaler{},
"{\n \"color\": 1000,\n \"r_color\": [\n \"RED\"\n ]\n}",
&pb.Widget{Color: pb.Widget_Color(1000).Enum(), RColor: []pb.Widget_Color{pb.Widget_RED}}},
{"repeated proto3 enum", Unmarshaler{}, `{"rFunny":["PUNS","SLAPSTICK"]}`,
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
proto3pb.Message_PUNS,
proto3pb.Message_SLAPSTICK,
}}},
{"repeated proto3 enum as int", Unmarshaler{}, `{"rFunny":[1,2]}`,
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
proto3pb.Message_PUNS,
proto3pb.Message_SLAPSTICK,
}}},
{"repeated proto3 enum as mix of strings and ints", Unmarshaler{}, `{"rFunny":["PUNS",2]}`,
&proto3pb.Message{RFunny: []proto3pb.Message_Humour{
proto3pb.Message_PUNS,
proto3pb.Message_SLAPSTICK,
}}},
{"unquoted int64 object", Unmarshaler{}, `{"oInt64":-314}`, &pb.Simple{OInt64: proto.Int64(-314)}},
{"unquoted uint64 object", Unmarshaler{}, `{"oUint64":123}`, &pb.Simple{OUint64: proto.Uint64(123)}},
{"map<int64, int32>", Unmarshaler{}, `{"nummy":{"1":2,"3":4}}`, &pb.Mappy{Nummy: map[int64]int32{1: 2, 3: 4}}},
{"map<string, string>", Unmarshaler{}, `{"strry":{"\"one\"":"two","three":"four"}}`, &pb.Mappy{Strry: map[string]string{`"one"`: "two", "three": "four"}}},
{"map<int32, Object>", Unmarshaler{}, `{"objjy":{"1":{"dub":1}}}`, &pb.Mappy{Objjy: map[int32]*pb.Simple3{1: &pb.Simple3{Dub: 1}}}},
// TODO: This is broken.
//{"map<string, enum>", Unmarshaler{}, `{"enumy":{"XIV":"ROMAN"}`, &pb.Mappy{Enumy: map[string]pb.Numeral{"XIV": pb.Numeral_ROMAN}}},
{"map<string, enum as int>", Unmarshaler{}, `{"enumy":{"XIV":2}}`, &pb.Mappy{Enumy: map[string]pb.Numeral{"XIV": pb.Numeral_ROMAN}}},
{"oneof", Unmarshaler{}, `{"salary":31000}`, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Salary{31000}}},
{"oneof spec name", Unmarshaler{}, `{"country":"Australia"}`, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Country{"Australia"}}},
{"oneof orig_name", Unmarshaler{}, `{"Country":"Australia"}`, &pb.MsgWithOneof{Union: &pb.MsgWithOneof_Country{"Australia"}}},
{"orig_name input", Unmarshaler{}, `{"o_bool":true}`, &pb.Simple{OBool: proto.Bool(true)}},
{"camelName input", Unmarshaler{}, `{"oBool":true}`, &pb.Simple{OBool: proto.Bool(true)}},
{"Duration", Unmarshaler{}, `{"dur":"3.000s"}`, &pb.KnownTypes{Dur: &durpb.Duration{Seconds: 3}}},
{"Timestamp", Unmarshaler{}, `{"ts":"2014-05-13T16:53:20.021Z"}`, &pb.KnownTypes{Ts: &tspb.Timestamp{Seconds: 14e8, Nanos: 21e6}}},
{"DoubleValue", Unmarshaler{}, `{"dbl":1.2}`, &pb.KnownTypes{Dbl: &wpb.DoubleValue{Value: 1.2}}},
{"FloatValue", Unmarshaler{}, `{"flt":1.2}`, &pb.KnownTypes{Flt: &wpb.FloatValue{Value: 1.2}}},
{"Int64Value", Unmarshaler{}, `{"i64":"-3"}`, &pb.KnownTypes{I64: &wpb.Int64Value{Value: -3}}},
{"UInt64Value", Unmarshaler{}, `{"u64":"3"}`, &pb.KnownTypes{U64: &wpb.UInt64Value{Value: 3}}},
{"Int32Value", Unmarshaler{}, `{"i32":-4}`, &pb.KnownTypes{I32: &wpb.Int32Value{Value: -4}}},
{"UInt32Value", Unmarshaler{}, `{"u32":4}`, &pb.KnownTypes{U32: &wpb.UInt32Value{Value: 4}}},
{"BoolValue", Unmarshaler{}, `{"bool":true}`, &pb.KnownTypes{Bool: &wpb.BoolValue{Value: true}}},
{"StringValue", Unmarshaler{}, `{"str":"plush"}`, &pb.KnownTypes{Str: &wpb.StringValue{Value: "plush"}}},
{"BytesValue", Unmarshaler{}, `{"bytes":"d293"}`, &pb.KnownTypes{Bytes: &wpb.BytesValue{Value: []byte("wow")}}},
// `null` is also a permissible value. Let's just test one.
{"null DoubleValue", Unmarshaler{}, `{"dbl":null}`, &pb.KnownTypes{Dbl: &wpb.DoubleValue{}}},
}
func TestUnmarshaling(t *testing.T) {
for _, tt := range unmarshalingTests {
// Make a new instance of the type of our expected object.
p := reflect.New(reflect.TypeOf(tt.pb).Elem()).Interface().(proto.Message)
err := tt.unmarshaler.Unmarshal(strings.NewReader(tt.json), p)
if err != nil {
t.Errorf("%s: %v", tt.desc, err)
continue
}
// For easier diffs, compare text strings of the protos.
exp := proto.MarshalTextString(tt.pb)
act := proto.MarshalTextString(p)
if string(exp) != string(act) {
t.Errorf("%s: got [%s] want [%s]", tt.desc, act, exp)
}
}
}
func TestUnmarshalNext(t *testing.T) {
// We only need to check against a few, not all of them.
tests := unmarshalingTests[:5]
// Create a buffer with many concatenated JSON objects.
var b bytes.Buffer
for _, tt := range tests {
b.WriteString(tt.json)
}
dec := json.NewDecoder(&b)
for _, tt := range tests {
// Make a new instance of the type of our expected object.
p := reflect.New(reflect.TypeOf(tt.pb).Elem()).Interface().(proto.Message)
err := tt.unmarshaler.UnmarshalNext(dec, p)
if err != nil {
t.Errorf("%s: %v", tt.desc, err)
continue
}
// For easier diffs, compare text strings of the protos.
exp := proto.MarshalTextString(tt.pb)
act := proto.MarshalTextString(p)
if string(exp) != string(act) {
t.Errorf("%s: got [%s] want [%s]", tt.desc, act, exp)
}
}
p := &pb.Simple{}
err := new(Unmarshaler).UnmarshalNext(dec, p)
if err != io.EOF {
t.Errorf("eof: got %v, expected io.EOF", err)
}
}
var unmarshalingShouldError = []struct {
desc string
in string
pb proto.Message
}{
{"a value", "666", new(pb.Simple)},
{"gibberish", "{adskja123;l23=-=", new(pb.Simple)},
{"unknown field", `{"unknown": "foo"}`, new(pb.Simple)},
{"unknown enum name", `{"hilarity":"DAVE"}`, new(proto3pb.Message)},
}
func TestUnmarshalingBadInput(t *testing.T) {
for _, tt := range unmarshalingShouldError {
err := UnmarshalString(tt.in, tt.pb)
if err == nil {
t.Errorf("an error was expected when parsing %q instead of an object", tt.desc)
}
}
}

View file

@ -0,0 +1,33 @@
# Go support for Protocol Buffers - Google's data interchange format
#
# Copyright 2015 The Go Authors. All rights reserved.
# https://github.com/golang/protobuf
#
# 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.
regenerate:
protoc --go_out=Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any,Mgoogle/protobuf/duration.proto=github.com/golang/protobuf/ptypes/duration,Mgoogle/protobuf/struct.proto=github.com/golang/protobuf/ptypes/struct,Mgoogle/protobuf/timestamp.proto=github.com/golang/protobuf/ptypes/timestamp,Mgoogle/protobuf/wrappers.proto=github.com/golang/protobuf/ptypes/wrappers:. *.proto

View file

@ -0,0 +1,200 @@
// Code generated by protoc-gen-go.
// source: more_test_objects.proto
// DO NOT EDIT!
/*
Package jsonpb is a generated protocol buffer package.
It is generated from these files:
more_test_objects.proto
test_objects.proto
It has these top-level messages:
Simple3
Mappy
Simple
Repeats
Widget
Maps
MsgWithOneof
Real
Complex
KnownTypes
*/
package jsonpb
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Numeral int32
const (
Numeral_UNKNOWN Numeral = 0
Numeral_ARABIC Numeral = 1
Numeral_ROMAN Numeral = 2
)
var Numeral_name = map[int32]string{
0: "UNKNOWN",
1: "ARABIC",
2: "ROMAN",
}
var Numeral_value = map[string]int32{
"UNKNOWN": 0,
"ARABIC": 1,
"ROMAN": 2,
}
func (x Numeral) String() string {
return proto.EnumName(Numeral_name, int32(x))
}
func (Numeral) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
type Simple3 struct {
Dub float64 `protobuf:"fixed64,1,opt,name=dub" json:"dub,omitempty"`
}
func (m *Simple3) Reset() { *m = Simple3{} }
func (m *Simple3) String() string { return proto.CompactTextString(m) }
func (*Simple3) ProtoMessage() {}
func (*Simple3) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
type Mappy struct {
Nummy map[int64]int32 `protobuf:"bytes,1,rep,name=nummy" json:"nummy,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
Strry map[string]string `protobuf:"bytes,2,rep,name=strry" json:"strry,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Objjy map[int32]*Simple3 `protobuf:"bytes,3,rep,name=objjy" json:"objjy,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Buggy map[int64]string `protobuf:"bytes,4,rep,name=buggy" json:"buggy,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Booly map[bool]bool `protobuf:"bytes,5,rep,name=booly" json:"booly,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
Enumy map[string]Numeral `protobuf:"bytes,6,rep,name=enumy" json:"enumy,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value,enum=jsonpb.Numeral"`
S32Booly map[int32]bool `protobuf:"bytes,7,rep,name=s32booly" json:"s32booly,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
S64Booly map[int64]bool `protobuf:"bytes,8,rep,name=s64booly" json:"s64booly,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
U32Booly map[uint32]bool `protobuf:"bytes,9,rep,name=u32booly" json:"u32booly,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
U64Booly map[uint64]bool `protobuf:"bytes,10,rep,name=u64booly" json:"u64booly,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"`
}
func (m *Mappy) Reset() { *m = Mappy{} }
func (m *Mappy) String() string { return proto.CompactTextString(m) }
func (*Mappy) ProtoMessage() {}
func (*Mappy) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *Mappy) GetNummy() map[int64]int32 {
if m != nil {
return m.Nummy
}
return nil
}
func (m *Mappy) GetStrry() map[string]string {
if m != nil {
return m.Strry
}
return nil
}
func (m *Mappy) GetObjjy() map[int32]*Simple3 {
if m != nil {
return m.Objjy
}
return nil
}
func (m *Mappy) GetBuggy() map[int64]string {
if m != nil {
return m.Buggy
}
return nil
}
func (m *Mappy) GetBooly() map[bool]bool {
if m != nil {
return m.Booly
}
return nil
}
func (m *Mappy) GetEnumy() map[string]Numeral {
if m != nil {
return m.Enumy
}
return nil
}
func (m *Mappy) GetS32Booly() map[int32]bool {
if m != nil {
return m.S32Booly
}
return nil
}
func (m *Mappy) GetS64Booly() map[int64]bool {
if m != nil {
return m.S64Booly
}
return nil
}
func (m *Mappy) GetU32Booly() map[uint32]bool {
if m != nil {
return m.U32Booly
}
return nil
}
func (m *Mappy) GetU64Booly() map[uint64]bool {
if m != nil {
return m.U64Booly
}
return nil
}
func init() {
proto.RegisterType((*Simple3)(nil), "jsonpb.Simple3")
proto.RegisterType((*Mappy)(nil), "jsonpb.Mappy")
proto.RegisterEnum("jsonpb.Numeral", Numeral_name, Numeral_value)
}
func init() { proto.RegisterFile("more_test_objects.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 442 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x94, 0xcf, 0xab, 0xd3, 0x40,
0x10, 0xc7, 0x4d, 0xf3, 0xf2, 0x6b, 0xca, 0xd3, 0xb0, 0x08, 0x06, 0xdf, 0x45, 0x1e, 0x08, 0x45,
0x30, 0x87, 0x56, 0xf4, 0xa1, 0xa7, 0x56, 0x7a, 0x28, 0xd2, 0x14, 0x52, 0x8a, 0xc7, 0xd2, 0xe8,
0x52, 0xac, 0x49, 0x13, 0xf2, 0x43, 0xd8, 0x3f, 0x5e, 0x30, 0xb3, 0xd9, 0x34, 0x9b, 0xb0, 0xa5,
0xde, 0x36, 0x7c, 0x3f, 0x9f, 0xce, 0xec, 0xce, 0x50, 0x78, 0x95, 0xa4, 0x39, 0xdd, 0x97, 0xb4,
0x28, 0xf7, 0x69, 0x74, 0xa2, 0x3f, 0xca, 0xc2, 0xcf, 0xf2, 0xb4, 0x4c, 0x89, 0x79, 0x2a, 0xd2,
0x73, 0x16, 0x3d, 0x3e, 0x80, 0xb5, 0xfd, 0x95, 0x64, 0x31, 0x9d, 0x11, 0x17, 0xf4, 0x9f, 0x55,
0xe4, 0x69, 0x6f, 0xb4, 0x89, 0x16, 0xe2, 0xf1, 0xf1, 0xaf, 0x0d, 0xc6, 0xfa, 0x90, 0x65, 0x8c,
0xf8, 0x60, 0x9c, 0xab, 0x24, 0x61, 0x75, 0xaa, 0x4f, 0xc6, 0x53, 0xcf, 0x6f, 0x74, 0x9f, 0xa7,
0x7e, 0x80, 0xd1, 0xf2, 0x5c, 0xe6, 0x2c, 0x6c, 0x30, 0xe4, 0x8b, 0x32, 0xcf, 0x99, 0x37, 0x52,
0xf1, 0x5b, 0x8c, 0x04, 0xcf, 0x31, 0xe4, 0xeb, 0xfe, 0x4e, 0xcc, 0xd3, 0x55, 0xfc, 0x06, 0x23,
0xc1, 0x73, 0x0c, 0xf9, 0xa8, 0x3a, 0x1e, 0x99, 0x77, 0xa7, 0xe2, 0x17, 0x18, 0x09, 0x9e, 0x63,
0x9c, 0x4f, 0xd3, 0x98, 0x79, 0x86, 0x92, 0xc7, 0xa8, 0xe5, 0xf1, 0x8c, 0x3c, 0xad, 0x6f, 0xc2,
0x3c, 0x53, 0xc5, 0x2f, 0x31, 0x12, 0x3c, 0xc7, 0xc8, 0x27, 0xb0, 0x8b, 0xd9, 0xb4, 0x29, 0x61,
0x71, 0xe5, 0x61, 0x70, 0x65, 0x91, 0x36, 0xd6, 0x05, 0xe6, 0xe2, 0xc7, 0x0f, 0x8d, 0x68, 0x2b,
0x45, 0x91, 0xb6, 0xa2, 0xf8, 0x44, 0xb1, 0x6a, 0x2b, 0x3a, 0x2a, 0x71, 0xd7, 0xaf, 0x58, 0x49,
0x15, 0xab, 0xb6, 0x22, 0x28, 0xc5, 0x7e, 0xc5, 0x16, 0x7e, 0xfd, 0x04, 0xd0, 0x0d, 0x1a, 0xb7,
0xe5, 0x37, 0x65, 0x7c, 0x5b, 0xf4, 0x10, 0x8f, 0xe4, 0x25, 0x18, 0x7f, 0x0e, 0x71, 0x45, 0xeb,
0x99, 0x6b, 0x13, 0x23, 0x6c, 0x3e, 0x3e, 0x8f, 0x9e, 0x34, 0x34, 0xbb, 0x91, 0xcb, 0xa6, 0xa3,
0x30, 0x1d, 0xd9, 0x5c, 0x01, 0x74, 0xc3, 0x97, 0x4d, 0xa3, 0x31, 0xdf, 0xca, 0xe6, 0x78, 0xfa,
0xa2, 0xbd, 0x89, 0xd8, 0xe9, 0x41, 0x13, 0xdd, 0x5e, 0xdc, 0x6a, 0xdf, 0x19, 0x9a, 0x97, 0x07,
0x91, 0x4d, 0x5b, 0x61, 0xda, 0x83, 0xf6, 0xbb, 0x5d, 0x51, 0x5c, 0xbc, 0xd7, 0xfe, 0xf3, 0xae,
0xfd, 0xfa, 0x9d, 0x69, 0x7e, 0x88, 0xe5, 0x9f, 0xfa, 0x02, 0xf7, 0xbd, 0x1d, 0x52, 0x3c, 0xc6,
0xf5, 0x3e, 0x50, 0x96, 0xa7, 0x7a, 0xeb, 0xfa, 0x43, 0x79, 0x77, 0xad, 0xf2, 0xfd, 0xff, 0xc8,
0xd7, 0x2a, 0xdf, 0xdd, 0x90, 0xdf, 0xbd, 0x07, 0x4b, 0xbc, 0x04, 0x19, 0x83, 0xb5, 0x0b, 0xbe,
0x05, 0x9b, 0xef, 0x81, 0xfb, 0x8c, 0x00, 0x98, 0xf3, 0x70, 0xbe, 0x58, 0x7d, 0x75, 0x35, 0xe2,
0x80, 0x11, 0x6e, 0xd6, 0xf3, 0xc0, 0x1d, 0x45, 0x26, 0xff, 0x6b, 0x9b, 0xfd, 0x0b, 0x00, 0x00,
0xff, 0xff, 0xa2, 0x4b, 0xe1, 0x77, 0xf5, 0x04, 0x00, 0x00,
}

View file

@ -0,0 +1,57 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2015 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// 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.
syntax = "proto3";
package jsonpb;
message Simple3 {
double dub = 1;
}
enum Numeral {
UNKNOWN = 0;
ARABIC = 1;
ROMAN = 2;
}
message Mappy {
map<int64, int32> nummy = 1;
map<string, string> strry = 2;
map<int32, Simple3> objjy = 3;
map<int64, string> buggy = 4;
map<bool, bool> booly = 5;
map<string, Numeral> enumy = 6;
map<int32, bool> s32booly = 7;
map<int64, bool> s64booly = 8;
map<uint32, bool> u32booly = 9;
map<uint64, bool> u64booly = 10;
}

View file

@ -0,0 +1,739 @@
// Code generated by protoc-gen-go.
// source: test_objects.proto
// DO NOT EDIT!
package jsonpb
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_protobuf "github.com/golang/protobuf/ptypes/any"
import google_protobuf1 "github.com/golang/protobuf/ptypes/duration"
import google_protobuf2 "github.com/golang/protobuf/ptypes/struct"
import google_protobuf3 "github.com/golang/protobuf/ptypes/timestamp"
import google_protobuf4 "github.com/golang/protobuf/ptypes/wrappers"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
type Widget_Color int32
const (
Widget_RED Widget_Color = 0
Widget_GREEN Widget_Color = 1
Widget_BLUE Widget_Color = 2
)
var Widget_Color_name = map[int32]string{
0: "RED",
1: "GREEN",
2: "BLUE",
}
var Widget_Color_value = map[string]int32{
"RED": 0,
"GREEN": 1,
"BLUE": 2,
}
func (x Widget_Color) Enum() *Widget_Color {
p := new(Widget_Color)
*p = x
return p
}
func (x Widget_Color) String() string {
return proto.EnumName(Widget_Color_name, int32(x))
}
func (x *Widget_Color) UnmarshalJSON(data []byte) error {
value, err := proto.UnmarshalJSONEnum(Widget_Color_value, data, "Widget_Color")
if err != nil {
return err
}
*x = Widget_Color(value)
return nil
}
func (Widget_Color) EnumDescriptor() ([]byte, []int) { return fileDescriptor1, []int{2, 0} }
// Test message for holding primitive types.
type Simple struct {
OBool *bool `protobuf:"varint,1,opt,name=o_bool,json=oBool" json:"o_bool,omitempty"`
OInt32 *int32 `protobuf:"varint,2,opt,name=o_int32,json=oInt32" json:"o_int32,omitempty"`
OInt64 *int64 `protobuf:"varint,3,opt,name=o_int64,json=oInt64" json:"o_int64,omitempty"`
OUint32 *uint32 `protobuf:"varint,4,opt,name=o_uint32,json=oUint32" json:"o_uint32,omitempty"`
OUint64 *uint64 `protobuf:"varint,5,opt,name=o_uint64,json=oUint64" json:"o_uint64,omitempty"`
OSint32 *int32 `protobuf:"zigzag32,6,opt,name=o_sint32,json=oSint32" json:"o_sint32,omitempty"`
OSint64 *int64 `protobuf:"zigzag64,7,opt,name=o_sint64,json=oSint64" json:"o_sint64,omitempty"`
OFloat *float32 `protobuf:"fixed32,8,opt,name=o_float,json=oFloat" json:"o_float,omitempty"`
ODouble *float64 `protobuf:"fixed64,9,opt,name=o_double,json=oDouble" json:"o_double,omitempty"`
OString *string `protobuf:"bytes,10,opt,name=o_string,json=oString" json:"o_string,omitempty"`
OBytes []byte `protobuf:"bytes,11,opt,name=o_bytes,json=oBytes" json:"o_bytes,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *Simple) Reset() { *m = Simple{} }
func (m *Simple) String() string { return proto.CompactTextString(m) }
func (*Simple) ProtoMessage() {}
func (*Simple) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} }
func (m *Simple) GetOBool() bool {
if m != nil && m.OBool != nil {
return *m.OBool
}
return false
}
func (m *Simple) GetOInt32() int32 {
if m != nil && m.OInt32 != nil {
return *m.OInt32
}
return 0
}
func (m *Simple) GetOInt64() int64 {
if m != nil && m.OInt64 != nil {
return *m.OInt64
}
return 0
}
func (m *Simple) GetOUint32() uint32 {
if m != nil && m.OUint32 != nil {
return *m.OUint32
}
return 0
}
func (m *Simple) GetOUint64() uint64 {
if m != nil && m.OUint64 != nil {
return *m.OUint64
}
return 0
}
func (m *Simple) GetOSint32() int32 {
if m != nil && m.OSint32 != nil {
return *m.OSint32
}
return 0
}
func (m *Simple) GetOSint64() int64 {
if m != nil && m.OSint64 != nil {
return *m.OSint64
}
return 0
}
func (m *Simple) GetOFloat() float32 {
if m != nil && m.OFloat != nil {
return *m.OFloat
}
return 0
}
func (m *Simple) GetODouble() float64 {
if m != nil && m.ODouble != nil {
return *m.ODouble
}
return 0
}
func (m *Simple) GetOString() string {
if m != nil && m.OString != nil {
return *m.OString
}
return ""
}
func (m *Simple) GetOBytes() []byte {
if m != nil {
return m.OBytes
}
return nil
}
// Test message for holding repeated primitives.
type Repeats struct {
RBool []bool `protobuf:"varint,1,rep,name=r_bool,json=rBool" json:"r_bool,omitempty"`
RInt32 []int32 `protobuf:"varint,2,rep,name=r_int32,json=rInt32" json:"r_int32,omitempty"`
RInt64 []int64 `protobuf:"varint,3,rep,name=r_int64,json=rInt64" json:"r_int64,omitempty"`
RUint32 []uint32 `protobuf:"varint,4,rep,name=r_uint32,json=rUint32" json:"r_uint32,omitempty"`
RUint64 []uint64 `protobuf:"varint,5,rep,name=r_uint64,json=rUint64" json:"r_uint64,omitempty"`
RSint32 []int32 `protobuf:"zigzag32,6,rep,name=r_sint32,json=rSint32" json:"r_sint32,omitempty"`
RSint64 []int64 `protobuf:"zigzag64,7,rep,name=r_sint64,json=rSint64" json:"r_sint64,omitempty"`
RFloat []float32 `protobuf:"fixed32,8,rep,name=r_float,json=rFloat" json:"r_float,omitempty"`
RDouble []float64 `protobuf:"fixed64,9,rep,name=r_double,json=rDouble" json:"r_double,omitempty"`
RString []string `protobuf:"bytes,10,rep,name=r_string,json=rString" json:"r_string,omitempty"`
RBytes [][]byte `protobuf:"bytes,11,rep,name=r_bytes,json=rBytes" json:"r_bytes,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *Repeats) Reset() { *m = Repeats{} }
func (m *Repeats) String() string { return proto.CompactTextString(m) }
func (*Repeats) ProtoMessage() {}
func (*Repeats) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{1} }
func (m *Repeats) GetRBool() []bool {
if m != nil {
return m.RBool
}
return nil
}
func (m *Repeats) GetRInt32() []int32 {
if m != nil {
return m.RInt32
}
return nil
}
func (m *Repeats) GetRInt64() []int64 {
if m != nil {
return m.RInt64
}
return nil
}
func (m *Repeats) GetRUint32() []uint32 {
if m != nil {
return m.RUint32
}
return nil
}
func (m *Repeats) GetRUint64() []uint64 {
if m != nil {
return m.RUint64
}
return nil
}
func (m *Repeats) GetRSint32() []int32 {
if m != nil {
return m.RSint32
}
return nil
}
func (m *Repeats) GetRSint64() []int64 {
if m != nil {
return m.RSint64
}
return nil
}
func (m *Repeats) GetRFloat() []float32 {
if m != nil {
return m.RFloat
}
return nil
}
func (m *Repeats) GetRDouble() []float64 {
if m != nil {
return m.RDouble
}
return nil
}
func (m *Repeats) GetRString() []string {
if m != nil {
return m.RString
}
return nil
}
func (m *Repeats) GetRBytes() [][]byte {
if m != nil {
return m.RBytes
}
return nil
}
// Test message for holding enums and nested messages.
type Widget struct {
Color *Widget_Color `protobuf:"varint,1,opt,name=color,enum=jsonpb.Widget_Color" json:"color,omitempty"`
RColor []Widget_Color `protobuf:"varint,2,rep,name=r_color,json=rColor,enum=jsonpb.Widget_Color" json:"r_color,omitempty"`
Simple *Simple `protobuf:"bytes,10,opt,name=simple" json:"simple,omitempty"`
RSimple []*Simple `protobuf:"bytes,11,rep,name=r_simple,json=rSimple" json:"r_simple,omitempty"`
Repeats *Repeats `protobuf:"bytes,20,opt,name=repeats" json:"repeats,omitempty"`
RRepeats []*Repeats `protobuf:"bytes,21,rep,name=r_repeats,json=rRepeats" json:"r_repeats,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *Widget) Reset() { *m = Widget{} }
func (m *Widget) String() string { return proto.CompactTextString(m) }
func (*Widget) ProtoMessage() {}
func (*Widget) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{2} }
func (m *Widget) GetColor() Widget_Color {
if m != nil && m.Color != nil {
return *m.Color
}
return Widget_RED
}
func (m *Widget) GetRColor() []Widget_Color {
if m != nil {
return m.RColor
}
return nil
}
func (m *Widget) GetSimple() *Simple {
if m != nil {
return m.Simple
}
return nil
}
func (m *Widget) GetRSimple() []*Simple {
if m != nil {
return m.RSimple
}
return nil
}
func (m *Widget) GetRepeats() *Repeats {
if m != nil {
return m.Repeats
}
return nil
}
func (m *Widget) GetRRepeats() []*Repeats {
if m != nil {
return m.RRepeats
}
return nil
}
type Maps struct {
MInt64Str map[int64]string `protobuf:"bytes,1,rep,name=m_int64_str,json=mInt64Str" json:"m_int64_str,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
MBoolSimple map[bool]*Simple `protobuf:"bytes,2,rep,name=m_bool_simple,json=mBoolSimple" json:"m_bool_simple,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
XXX_unrecognized []byte `json:"-"`
}
func (m *Maps) Reset() { *m = Maps{} }
func (m *Maps) String() string { return proto.CompactTextString(m) }
func (*Maps) ProtoMessage() {}
func (*Maps) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{3} }
func (m *Maps) GetMInt64Str() map[int64]string {
if m != nil {
return m.MInt64Str
}
return nil
}
func (m *Maps) GetMBoolSimple() map[bool]*Simple {
if m != nil {
return m.MBoolSimple
}
return nil
}
type MsgWithOneof struct {
// Types that are valid to be assigned to Union:
// *MsgWithOneof_Title
// *MsgWithOneof_Salary
// *MsgWithOneof_Country
Union isMsgWithOneof_Union `protobuf_oneof:"union"`
XXX_unrecognized []byte `json:"-"`
}
func (m *MsgWithOneof) Reset() { *m = MsgWithOneof{} }
func (m *MsgWithOneof) String() string { return proto.CompactTextString(m) }
func (*MsgWithOneof) ProtoMessage() {}
func (*MsgWithOneof) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{4} }
type isMsgWithOneof_Union interface {
isMsgWithOneof_Union()
}
type MsgWithOneof_Title struct {
Title string `protobuf:"bytes,1,opt,name=title,oneof"`
}
type MsgWithOneof_Salary struct {
Salary int64 `protobuf:"varint,2,opt,name=salary,oneof"`
}
type MsgWithOneof_Country struct {
Country string `protobuf:"bytes,3,opt,name=Country,json=country,oneof"`
}
func (*MsgWithOneof_Title) isMsgWithOneof_Union() {}
func (*MsgWithOneof_Salary) isMsgWithOneof_Union() {}
func (*MsgWithOneof_Country) isMsgWithOneof_Union() {}
func (m *MsgWithOneof) GetUnion() isMsgWithOneof_Union {
if m != nil {
return m.Union
}
return nil
}
func (m *MsgWithOneof) GetTitle() string {
if x, ok := m.GetUnion().(*MsgWithOneof_Title); ok {
return x.Title
}
return ""
}
func (m *MsgWithOneof) GetSalary() int64 {
if x, ok := m.GetUnion().(*MsgWithOneof_Salary); ok {
return x.Salary
}
return 0
}
func (m *MsgWithOneof) GetCountry() string {
if x, ok := m.GetUnion().(*MsgWithOneof_Country); ok {
return x.Country
}
return ""
}
// XXX_OneofFuncs is for the internal use of the proto package.
func (*MsgWithOneof) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) {
return _MsgWithOneof_OneofMarshaler, _MsgWithOneof_OneofUnmarshaler, _MsgWithOneof_OneofSizer, []interface{}{
(*MsgWithOneof_Title)(nil),
(*MsgWithOneof_Salary)(nil),
(*MsgWithOneof_Country)(nil),
}
}
func _MsgWithOneof_OneofMarshaler(msg proto.Message, b *proto.Buffer) error {
m := msg.(*MsgWithOneof)
// union
switch x := m.Union.(type) {
case *MsgWithOneof_Title:
b.EncodeVarint(1<<3 | proto.WireBytes)
b.EncodeStringBytes(x.Title)
case *MsgWithOneof_Salary:
b.EncodeVarint(2<<3 | proto.WireVarint)
b.EncodeVarint(uint64(x.Salary))
case *MsgWithOneof_Country:
b.EncodeVarint(3<<3 | proto.WireBytes)
b.EncodeStringBytes(x.Country)
case nil:
default:
return fmt.Errorf("MsgWithOneof.Union has unexpected type %T", x)
}
return nil
}
func _MsgWithOneof_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) {
m := msg.(*MsgWithOneof)
switch tag {
case 1: // union.title
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeStringBytes()
m.Union = &MsgWithOneof_Title{x}
return true, err
case 2: // union.salary
if wire != proto.WireVarint {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeVarint()
m.Union = &MsgWithOneof_Salary{int64(x)}
return true, err
case 3: // union.Country
if wire != proto.WireBytes {
return true, proto.ErrInternalBadWireType
}
x, err := b.DecodeStringBytes()
m.Union = &MsgWithOneof_Country{x}
return true, err
default:
return false, nil
}
}
func _MsgWithOneof_OneofSizer(msg proto.Message) (n int) {
m := msg.(*MsgWithOneof)
// union
switch x := m.Union.(type) {
case *MsgWithOneof_Title:
n += proto.SizeVarint(1<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(len(x.Title)))
n += len(x.Title)
case *MsgWithOneof_Salary:
n += proto.SizeVarint(2<<3 | proto.WireVarint)
n += proto.SizeVarint(uint64(x.Salary))
case *MsgWithOneof_Country:
n += proto.SizeVarint(3<<3 | proto.WireBytes)
n += proto.SizeVarint(uint64(len(x.Country)))
n += len(x.Country)
case nil:
default:
panic(fmt.Sprintf("proto: unexpected type %T in oneof", x))
}
return n
}
type Real struct {
Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
proto.XXX_InternalExtensions `json:"-"`
XXX_unrecognized []byte `json:"-"`
}
func (m *Real) Reset() { *m = Real{} }
func (m *Real) String() string { return proto.CompactTextString(m) }
func (*Real) ProtoMessage() {}
func (*Real) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{5} }
var extRange_Real = []proto.ExtensionRange{
{100, 536870911},
}
func (*Real) ExtensionRangeArray() []proto.ExtensionRange {
return extRange_Real
}
func (m *Real) GetValue() float64 {
if m != nil && m.Value != nil {
return *m.Value
}
return 0
}
type Complex struct {
Imaginary *float64 `protobuf:"fixed64,1,opt,name=imaginary" json:"imaginary,omitempty"`
proto.XXX_InternalExtensions `json:"-"`
XXX_unrecognized []byte `json:"-"`
}
func (m *Complex) Reset() { *m = Complex{} }
func (m *Complex) String() string { return proto.CompactTextString(m) }
func (*Complex) ProtoMessage() {}
func (*Complex) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{6} }
var extRange_Complex = []proto.ExtensionRange{
{100, 536870911},
}
func (*Complex) ExtensionRangeArray() []proto.ExtensionRange {
return extRange_Complex
}
func (m *Complex) GetImaginary() float64 {
if m != nil && m.Imaginary != nil {
return *m.Imaginary
}
return 0
}
var E_Complex_RealExtension = &proto.ExtensionDesc{
ExtendedType: (*Real)(nil),
ExtensionType: (*Complex)(nil),
Field: 123,
Name: "jsonpb.Complex.real_extension",
Tag: "bytes,123,opt,name=real_extension,json=realExtension",
}
type KnownTypes struct {
An *google_protobuf.Any `protobuf:"bytes,14,opt,name=an" json:"an,omitempty"`
Dur *google_protobuf1.Duration `protobuf:"bytes,1,opt,name=dur" json:"dur,omitempty"`
St *google_protobuf2.Struct `protobuf:"bytes,12,opt,name=st" json:"st,omitempty"`
Ts *google_protobuf3.Timestamp `protobuf:"bytes,2,opt,name=ts" json:"ts,omitempty"`
Dbl *google_protobuf4.DoubleValue `protobuf:"bytes,3,opt,name=dbl" json:"dbl,omitempty"`
Flt *google_protobuf4.FloatValue `protobuf:"bytes,4,opt,name=flt" json:"flt,omitempty"`
I64 *google_protobuf4.Int64Value `protobuf:"bytes,5,opt,name=i64" json:"i64,omitempty"`
U64 *google_protobuf4.UInt64Value `protobuf:"bytes,6,opt,name=u64" json:"u64,omitempty"`
I32 *google_protobuf4.Int32Value `protobuf:"bytes,7,opt,name=i32" json:"i32,omitempty"`
U32 *google_protobuf4.UInt32Value `protobuf:"bytes,8,opt,name=u32" json:"u32,omitempty"`
Bool *google_protobuf4.BoolValue `protobuf:"bytes,9,opt,name=bool" json:"bool,omitempty"`
Str *google_protobuf4.StringValue `protobuf:"bytes,10,opt,name=str" json:"str,omitempty"`
Bytes *google_protobuf4.BytesValue `protobuf:"bytes,11,opt,name=bytes" json:"bytes,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *KnownTypes) Reset() { *m = KnownTypes{} }
func (m *KnownTypes) String() string { return proto.CompactTextString(m) }
func (*KnownTypes) ProtoMessage() {}
func (*KnownTypes) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{7} }
func (m *KnownTypes) GetAn() *google_protobuf.Any {
if m != nil {
return m.An
}
return nil
}
func (m *KnownTypes) GetDur() *google_protobuf1.Duration {
if m != nil {
return m.Dur
}
return nil
}
func (m *KnownTypes) GetSt() *google_protobuf2.Struct {
if m != nil {
return m.St
}
return nil
}
func (m *KnownTypes) GetTs() *google_protobuf3.Timestamp {
if m != nil {
return m.Ts
}
return nil
}
func (m *KnownTypes) GetDbl() *google_protobuf4.DoubleValue {
if m != nil {
return m.Dbl
}
return nil
}
func (m *KnownTypes) GetFlt() *google_protobuf4.FloatValue {
if m != nil {
return m.Flt
}
return nil
}
func (m *KnownTypes) GetI64() *google_protobuf4.Int64Value {
if m != nil {
return m.I64
}
return nil
}
func (m *KnownTypes) GetU64() *google_protobuf4.UInt64Value {
if m != nil {
return m.U64
}
return nil
}
func (m *KnownTypes) GetI32() *google_protobuf4.Int32Value {
if m != nil {
return m.I32
}
return nil
}
func (m *KnownTypes) GetU32() *google_protobuf4.UInt32Value {
if m != nil {
return m.U32
}
return nil
}
func (m *KnownTypes) GetBool() *google_protobuf4.BoolValue {
if m != nil {
return m.Bool
}
return nil
}
func (m *KnownTypes) GetStr() *google_protobuf4.StringValue {
if m != nil {
return m.Str
}
return nil
}
func (m *KnownTypes) GetBytes() *google_protobuf4.BytesValue {
if m != nil {
return m.Bytes
}
return nil
}
var E_Name = &proto.ExtensionDesc{
ExtendedType: (*Real)(nil),
ExtensionType: (*string)(nil),
Field: 124,
Name: "jsonpb.name",
Tag: "bytes,124,opt,name=name",
}
func init() {
proto.RegisterType((*Simple)(nil), "jsonpb.Simple")
proto.RegisterType((*Repeats)(nil), "jsonpb.Repeats")
proto.RegisterType((*Widget)(nil), "jsonpb.Widget")
proto.RegisterType((*Maps)(nil), "jsonpb.Maps")
proto.RegisterType((*MsgWithOneof)(nil), "jsonpb.MsgWithOneof")
proto.RegisterType((*Real)(nil), "jsonpb.Real")
proto.RegisterType((*Complex)(nil), "jsonpb.Complex")
proto.RegisterType((*KnownTypes)(nil), "jsonpb.KnownTypes")
proto.RegisterEnum("jsonpb.Widget_Color", Widget_Color_name, Widget_Color_value)
proto.RegisterExtension(E_Complex_RealExtension)
proto.RegisterExtension(E_Name)
}
func init() { proto.RegisterFile("test_objects.proto", fileDescriptor1) }
var fileDescriptor1 = []byte{
// 1006 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x55, 0xdd, 0x72, 0xdb, 0x44,
0x14, 0xae, 0xb5, 0x96, 0x65, 0xaf, 0x53, 0x63, 0x76, 0x52, 0xaa, 0x98, 0x00, 0x1d, 0x0f, 0x14,
0x28, 0xe0, 0x0e, 0x6e, 0xa7, 0xc3, 0x14, 0x6e, 0x9a, 0xc6, 0xfc, 0x0c, 0xa4, 0xcc, 0x6c, 0x1a,
0x7a, 0xe9, 0x91, 0x13, 0xc5, 0xa8, 0xc8, 0x5a, 0xcf, 0x6a, 0x45, 0xea, 0x81, 0x0b, 0x1e, 0x82,
0x57, 0x80, 0x47, 0xe0, 0x89, 0x78, 0x10, 0xce, 0x39, 0x2b, 0x69, 0x1d, 0xbb, 0xa6, 0x37, 0xcd,
0xd1, 0xf7, 0xe3, 0xa3, 0x6f, 0x8f, 0xce, 0x72, 0x61, 0xe2, 0xdc, 0x4c, 0xd5, 0xec, 0x65, 0x7c,
0x6e, 0xf2, 0xd1, 0x52, 0x2b, 0xa3, 0x44, 0xeb, 0x65, 0xae, 0xb2, 0xe5, 0x6c, 0x70, 0x30, 0x57,
0x6a, 0x9e, 0xc6, 0xf7, 0xe9, 0xe9, 0xac, 0xb8, 0xbc, 0x1f, 0x65, 0x2b, 0x4b, 0x19, 0xbc, 0xbb,
0x09, 0x5d, 0x14, 0x3a, 0x32, 0x89, 0xca, 0x4a, 0xfc, 0x70, 0x13, 0xcf, 0x8d, 0x2e, 0xce, 0x4d,
0x89, 0xbe, 0xb7, 0x89, 0x9a, 0x64, 0x01, 0x6d, 0x44, 0x8b, 0xe5, 0x2e, 0xfb, 0x2b, 0x1d, 0x2d,
0x97, 0xb1, 0x2e, 0x3b, 0x1c, 0xfe, 0xe5, 0xf1, 0xd6, 0x69, 0xb2, 0x58, 0xa6, 0xb1, 0xb8, 0xc5,
0x5b, 0x6a, 0x3a, 0x53, 0x2a, 0x0d, 0x1b, 0x77, 0x1a, 0x1f, 0xb5, 0xa5, 0xaf, 0x8e, 0xa0, 0x10,
0xb7, 0x79, 0xa0, 0xa6, 0x49, 0x66, 0x1e, 0x8c, 0x43, 0x0f, 0x9e, 0xfb, 0xb2, 0xa5, 0xbe, 0xc3,
0xaa, 0x06, 0x1e, 0x3d, 0x0c, 0x19, 0x00, 0xcc, 0x02, 0x8f, 0x1e, 0x8a, 0x03, 0xde, 0x56, 0xd3,
0xc2, 0x4a, 0x9a, 0x80, 0xdc, 0x94, 0x81, 0x3a, 0xa3, 0xd2, 0x41, 0x20, 0xf2, 0x01, 0x6a, 0x96,
0x50, 0xa5, 0xca, 0xad, 0xaa, 0x05, 0xd0, 0x9b, 0x00, 0x9d, 0xae, 0xa9, 0x72, 0xab, 0x0a, 0x00,
0x12, 0x25, 0x04, 0x2a, 0x6a, 0xe2, 0x32, 0x55, 0x91, 0x09, 0xdb, 0x80, 0x78, 0xd0, 0xc4, 0xd7,
0x58, 0x59, 0xcd, 0x85, 0x2a, 0x66, 0x69, 0x1c, 0x76, 0x00, 0x69, 0x80, 0xe6, 0x98, 0xca, 0xd2,
0xce, 0xe8, 0x24, 0x9b, 0x87, 0x1c, 0xa0, 0x0e, 0xda, 0x51, 0x69, 0xed, 0x66, 0x2b, 0x38, 0xca,
0xb0, 0x0b, 0xc8, 0x1e, 0xd8, 0x1d, 0x61, 0x35, 0xfc, 0xdb, 0xe3, 0x81, 0x8c, 0x97, 0x71, 0x64,
0x72, 0x0c, 0x4a, 0x57, 0x41, 0x31, 0x0c, 0x4a, 0x57, 0x41, 0xe9, 0x3a, 0x28, 0x86, 0x41, 0xe9,
0x3a, 0x28, 0x5d, 0x07, 0xc5, 0x30, 0x28, 0x5d, 0x07, 0xa5, 0x5d, 0x50, 0x0c, 0x83, 0xd2, 0x2e,
0x28, 0xed, 0x82, 0x62, 0x18, 0x94, 0x76, 0x41, 0x69, 0x17, 0x14, 0xc3, 0xa0, 0xf4, 0xe9, 0x9a,
0xaa, 0x0e, 0x8a, 0x61, 0x50, 0xda, 0x05, 0xa5, 0xeb, 0xa0, 0x18, 0x06, 0xa5, 0xeb, 0xa0, 0xb4,
0x0b, 0x8a, 0x61, 0x50, 0xda, 0x05, 0xa5, 0x5d, 0x50, 0x0c, 0x83, 0xd2, 0x2e, 0x28, 0x5d, 0x07,
0xc5, 0x30, 0x28, 0x6d, 0x83, 0xfa, 0x07, 0x06, 0xea, 0x45, 0x72, 0x31, 0x8f, 0x8d, 0xb8, 0xc7,
0xfd, 0x73, 0x95, 0x2a, 0x4d, 0xf3, 0xd4, 0x1b, 0xef, 0x8f, 0xec, 0xd7, 0x30, 0xb2, 0xf0, 0xe8,
0x29, 0x62, 0xd2, 0x52, 0xc4, 0x67, 0xe8, 0x67, 0xd9, 0x18, 0xde, 0x2e, 0x76, 0x4b, 0xd3, 0xff,
0xe2, 0x2e, 0x6f, 0xe5, 0x34, 0xb5, 0x74, 0x80, 0xdd, 0x71, 0xaf, 0x62, 0xdb, 0x59, 0x96, 0x25,
0x2a, 0x3e, 0xb6, 0x81, 0x10, 0x13, 0xfb, 0xdc, 0x66, 0x62, 0x40, 0x25, 0x35, 0xd0, 0xf6, 0x80,
0xc3, 0x7d, 0xf2, 0x7c, 0xa3, 0x62, 0x96, 0xe7, 0x2e, 0x2b, 0x5c, 0x7c, 0xca, 0x3b, 0x7a, 0x5a,
0x91, 0x6f, 0x91, 0xed, 0x16, 0xb9, 0xad, 0xcb, 0xbf, 0x86, 0x1f, 0x70, 0xdf, 0x36, 0x1d, 0x70,
0x26, 0x27, 0xc7, 0xfd, 0x1b, 0xa2, 0xc3, 0xfd, 0x6f, 0xe4, 0x64, 0xf2, 0xac, 0xdf, 0x10, 0x6d,
0xde, 0x3c, 0xfa, 0xe1, 0x6c, 0xd2, 0xf7, 0x86, 0x7f, 0x7a, 0xbc, 0x79, 0x12, 0x2d, 0x73, 0xf1,
0x25, 0xef, 0x2e, 0xec, 0xb8, 0x60, 0xf6, 0x34, 0x63, 0xdd, 0xf1, 0xdb, 0x95, 0x3f, 0x52, 0x46,
0x27, 0x34, 0x3f, 0x70, 0x14, 0x93, 0xcc, 0xe8, 0x95, 0xec, 0x2c, 0xaa, 0x5a, 0x3c, 0xe1, 0x37,
0x17, 0x34, 0x9b, 0xd5, 0x5b, 0x7b, 0x24, 0x7f, 0xe7, 0xba, 0x1c, 0xe7, 0xd5, 0xbe, 0xb6, 0x35,
0xe8, 0x2e, 0xdc, 0x93, 0xc1, 0x57, 0xbc, 0x77, 0xdd, 0x5f, 0xf4, 0x39, 0xfb, 0x25, 0x5e, 0xd1,
0x31, 0x32, 0x89, 0x7f, 0x8a, 0x7d, 0xee, 0xff, 0x1a, 0xa5, 0x45, 0x4c, 0x2b, 0xa1, 0x23, 0x6d,
0xf1, 0xd8, 0xfb, 0xa2, 0x31, 0x78, 0xc6, 0xfb, 0x9b, 0xf6, 0xeb, 0xfa, 0xb6, 0xd5, 0xbf, 0xbf,
0xae, 0xdf, 0x3e, 0x14, 0xe7, 0x37, 0x8c, 0xf9, 0xde, 0x49, 0x3e, 0x7f, 0x91, 0x98, 0x9f, 0x7f,
0xcc, 0x62, 0x75, 0x29, 0xde, 0xe2, 0xbe, 0x49, 0x0c, 0xbc, 0x18, 0xba, 0x75, 0xbe, 0xbd, 0x21,
0x6d, 0x29, 0x42, 0x98, 0x88, 0x28, 0x8d, 0xf4, 0x8a, 0x2c, 0x19, 0x00, 0x65, 0x2d, 0x06, 0x3c,
0x78, 0xaa, 0x0a, 0x6c, 0x84, 0xf6, 0x14, 0x6a, 0x82, 0x73, 0xfb, 0xe0, 0x28, 0xe0, 0x7e, 0x91,
0xc1, 0xb2, 0x1d, 0xde, 0xe5, 0x4d, 0x19, 0x47, 0xa9, 0x7b, 0xb1, 0x06, 0xed, 0x0c, 0x5b, 0xdc,
0x6b, 0xb7, 0x2f, 0xfa, 0x7f, 0xc0, 0x3f, 0x6f, 0x78, 0x85, 0x66, 0xd8, 0xe3, 0x2b, 0x71, 0xc8,
0x3b, 0xc9, 0x22, 0x9a, 0x27, 0x19, 0xfe, 0xa8, 0xa5, 0xbb, 0x07, 0x4e, 0x32, 0x3e, 0xe6, 0x3d,
0x0d, 0xd6, 0xd3, 0xf8, 0x95, 0x89, 0xb3, 0x1c, 0x7e, 0x4c, 0xec, 0xb9, 0x61, 0x89, 0xd2, 0xf0,
0xb7, 0xeb, 0xd3, 0x56, 0xda, 0xcb, 0x9b, 0x28, 0x9a, 0x54, 0x9a, 0xe1, 0xbf, 0x4d, 0xce, 0xbf,
0xcf, 0xd4, 0x55, 0xf6, 0x7c, 0xb5, 0x8c, 0x73, 0x08, 0xd0, 0x8b, 0xb2, 0xb0, 0x47, 0xd2, 0xfd,
0x91, 0x5d, 0xf2, 0xa3, 0x6a, 0xc9, 0x8f, 0x9e, 0x64, 0x2b, 0x09, 0xb8, 0xf8, 0x84, 0x33, 0xb8,
0x4e, 0xa8, 0xb9, 0xee, 0xf8, 0x60, 0x8b, 0x76, 0x5c, 0x5e, 0x35, 0x12, 0x59, 0xe2, 0x43, 0xee,
0xe5, 0x26, 0xdc, 0x23, 0xee, 0xed, 0x2d, 0xee, 0x29, 0x5d, 0x3b, 0x12, 0x28, 0xf0, 0x5d, 0x7b,
0x30, 0xf7, 0xf6, 0xe4, 0x06, 0x5b, 0xc4, 0xe7, 0xd5, 0x0d, 0x24, 0x81, 0x25, 0x46, 0xd0, 0xc1,
0x2c, 0xa5, 0xe0, 0xbb, 0xe3, 0xc3, 0xed, 0x0e, 0x68, 0xd1, 0xfc, 0x84, 0x21, 0x4b, 0x24, 0xc2,
0x1e, 0x60, 0x97, 0xa9, 0xa1, 0x6b, 0x03, 0x87, 0x7e, 0x93, 0x4f, 0x2b, 0xab, 0xa4, 0x03, 0x0f,
0xe9, 0x49, 0x79, 0x95, 0xbc, 0x8e, 0x4e, 0x63, 0x5c, 0xd2, 0x81, 0x87, 0xdd, 0x14, 0x40, 0x6f,
0xed, 0xe8, 0xe6, 0x6c, 0x9d, 0x0f, 0x44, 0xb2, 0x87, 0x2d, 0x1b, 0xec, 0xb6, 0x7f, 0x30, 0xae,
0xec, 0x61, 0xfd, 0xa2, 0x3d, 0xd0, 0xdb, 0xff, 0x63, 0x5f, 0xf3, 0x0b, 0xe2, 0x37, 0xe9, 0x1a,
0xe9, 0xec, 0x88, 0x12, 0xbf, 0x23, 0x4b, 0x27, 0x1e, 0xfa, 0xe3, 0x46, 0xe0, 0x3b, 0xfc, 0xed,
0x6a, 0x2e, 0xfd, 0x81, 0x28, 0x3e, 0xe7, 0xbe, 0xbb, 0xcb, 0x5e, 0xf7, 0x02, 0xb4, 0xb2, 0xad,
0xc0, 0x32, 0x1f, 0xdf, 0xe1, 0xcd, 0x2c, 0x5a, 0xc4, 0x1b, 0x23, 0xfa, 0x3b, 0x7d, 0xe5, 0x84,
0xfc, 0x17, 0x00, 0x00, 0xff, 0xff, 0xca, 0xa2, 0x76, 0x34, 0xe8, 0x08, 0x00, 0x00,
}

View file

@ -0,0 +1,134 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2015 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// 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.
syntax = "proto2";
import "google/protobuf/any.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";
package jsonpb;
// Test message for holding primitive types.
message Simple {
optional bool o_bool = 1;
optional int32 o_int32 = 2;
optional int64 o_int64 = 3;
optional uint32 o_uint32 = 4;
optional uint64 o_uint64 = 5;
optional sint32 o_sint32 = 6;
optional sint64 o_sint64 = 7;
optional float o_float = 8;
optional double o_double = 9;
optional string o_string = 10;
optional bytes o_bytes = 11;
}
// Test message for holding repeated primitives.
message Repeats {
repeated bool r_bool = 1;
repeated int32 r_int32 = 2;
repeated int64 r_int64 = 3;
repeated uint32 r_uint32 = 4;
repeated uint64 r_uint64 = 5;
repeated sint32 r_sint32 = 6;
repeated sint64 r_sint64 = 7;
repeated float r_float = 8;
repeated double r_double = 9;
repeated string r_string = 10;
repeated bytes r_bytes = 11;
}
// Test message for holding enums and nested messages.
message Widget {
enum Color {
RED = 0;
GREEN = 1;
BLUE = 2;
};
optional Color color = 1;
repeated Color r_color = 2;
optional Simple simple = 10;
repeated Simple r_simple = 11;
optional Repeats repeats = 20;
repeated Repeats r_repeats = 21;
}
message Maps {
map<int64, string> m_int64_str = 1;
map<bool, Simple> m_bool_simple = 2;
}
message MsgWithOneof {
oneof union {
string title = 1;
int64 salary = 2;
string Country = 3;
}
}
message Real {
optional double value = 1;
extensions 100 to max;
}
extend Real {
optional string name = 124;
}
message Complex {
extend Real {
optional Complex real_extension = 123;
}
optional double imaginary = 1;
extensions 100 to max;
}
message KnownTypes {
optional google.protobuf.Any an = 14;
optional google.protobuf.Duration dur = 1;
optional google.protobuf.Struct st = 12;
optional google.protobuf.Timestamp ts = 2;
optional google.protobuf.DoubleValue dbl = 3;
optional google.protobuf.FloatValue flt = 4;
optional google.protobuf.Int64Value i64 = 5;
optional google.protobuf.UInt64Value u64 = 6;
optional google.protobuf.Int32Value i32 = 7;
optional google.protobuf.UInt32Value u32 = 8;
optional google.protobuf.BoolValue bool = 9;
optional google.protobuf.StringValue str = 10;
optional google.protobuf.BytesValue bytes = 11;
}

43
vendor/github.com/golang/protobuf/proto/Makefile generated vendored Normal file
View file

@ -0,0 +1,43 @@
# Go support for Protocol Buffers - Google's data interchange format
#
# Copyright 2010 The Go Authors. All rights reserved.
# https://github.com/golang/protobuf
#
# 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.
install:
go install
test: install generate-test-pbs
go test
generate-test-pbs:
make install
make -C testdata
protoc --go_out=Mtestdata/test.proto=github.com/golang/protobuf/proto/testdata,Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any:. proto3_proto/proto3.proto
make

2269
vendor/github.com/golang/protobuf/proto/all_test.go generated vendored Normal file

File diff suppressed because it is too large Load diff

272
vendor/github.com/golang/protobuf/proto/any_test.go generated vendored Normal file
View file

@ -0,0 +1,272 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2016 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// 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.
package proto_test
import (
"strings"
"testing"
"github.com/golang/protobuf/proto"
pb "github.com/golang/protobuf/proto/proto3_proto"
testpb "github.com/golang/protobuf/proto/testdata"
anypb "github.com/golang/protobuf/ptypes/any"
)
var (
expandedMarshaler = proto.TextMarshaler{ExpandAny: true}
expandedCompactMarshaler = proto.TextMarshaler{Compact: true, ExpandAny: true}
)
// anyEqual reports whether two messages which may be google.protobuf.Any or may
// contain google.protobuf.Any fields are equal. We can't use proto.Equal for
// comparison, because semantically equivalent messages may be marshaled to
// binary in different tag order. Instead, trust that TextMarshaler with
// ExpandAny option works and compare the text marshaling results.
func anyEqual(got, want proto.Message) bool {
// if messages are proto.Equal, no need to marshal.
if proto.Equal(got, want) {
return true
}
g := expandedMarshaler.Text(got)
w := expandedMarshaler.Text(want)
return g == w
}
type golden struct {
m proto.Message
t, c string
}
var goldenMessages = makeGolden()
func makeGolden() []golden {
nested := &pb.Nested{Bunny: "Monty"}
nb, err := proto.Marshal(nested)
if err != nil {
panic(err)
}
m1 := &pb.Message{
Name: "David",
ResultCount: 47,
Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb},
}
m2 := &pb.Message{
Name: "David",
ResultCount: 47,
Anything: &anypb.Any{TypeUrl: "http://[::1]/type.googleapis.com/" + proto.MessageName(nested), Value: nb},
}
m3 := &pb.Message{
Name: "David",
ResultCount: 47,
Anything: &anypb.Any{TypeUrl: `type.googleapis.com/"/` + proto.MessageName(nested), Value: nb},
}
m4 := &pb.Message{
Name: "David",
ResultCount: 47,
Anything: &anypb.Any{TypeUrl: "type.googleapis.com/a/path/" + proto.MessageName(nested), Value: nb},
}
m5 := &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb}
any1 := &testpb.MyMessage{Count: proto.Int32(47), Name: proto.String("David")}
proto.SetExtension(any1, testpb.E_Ext_More, &testpb.Ext{Data: proto.String("foo")})
proto.SetExtension(any1, testpb.E_Ext_Text, proto.String("bar"))
any1b, err := proto.Marshal(any1)
if err != nil {
panic(err)
}
any2 := &testpb.MyMessage{Count: proto.Int32(42), Bikeshed: testpb.MyMessage_GREEN.Enum(), RepBytes: [][]byte{[]byte("roboto")}}
proto.SetExtension(any2, testpb.E_Ext_More, &testpb.Ext{Data: proto.String("baz")})
any2b, err := proto.Marshal(any2)
if err != nil {
panic(err)
}
m6 := &pb.Message{
Name: "David",
ResultCount: 47,
Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b},
ManyThings: []*anypb.Any{
&anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any2), Value: any2b},
&anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b},
},
}
const (
m1Golden = `
name: "David"
result_count: 47
anything: <
[type.googleapis.com/proto3_proto.Nested]: <
bunny: "Monty"
>
>
`
m2Golden = `
name: "David"
result_count: 47
anything: <
["http://[::1]/type.googleapis.com/proto3_proto.Nested"]: <
bunny: "Monty"
>
>
`
m3Golden = `
name: "David"
result_count: 47
anything: <
["type.googleapis.com/\"/proto3_proto.Nested"]: <
bunny: "Monty"
>
>
`
m4Golden = `
name: "David"
result_count: 47
anything: <
[type.googleapis.com/a/path/proto3_proto.Nested]: <
bunny: "Monty"
>
>
`
m5Golden = `
[type.googleapis.com/proto3_proto.Nested]: <
bunny: "Monty"
>
`
m6Golden = `
name: "David"
result_count: 47
anything: <
[type.googleapis.com/testdata.MyMessage]: <
count: 47
name: "David"
[testdata.Ext.more]: <
data: "foo"
>
[testdata.Ext.text]: "bar"
>
>
many_things: <
[type.googleapis.com/testdata.MyMessage]: <
count: 42
bikeshed: GREEN
rep_bytes: "roboto"
[testdata.Ext.more]: <
data: "baz"
>
>
>
many_things: <
[type.googleapis.com/testdata.MyMessage]: <
count: 47
name: "David"
[testdata.Ext.more]: <
data: "foo"
>
[testdata.Ext.text]: "bar"
>
>
`
)
return []golden{
{m1, strings.TrimSpace(m1Golden) + "\n", strings.TrimSpace(compact(m1Golden)) + " "},
{m2, strings.TrimSpace(m2Golden) + "\n", strings.TrimSpace(compact(m2Golden)) + " "},
{m3, strings.TrimSpace(m3Golden) + "\n", strings.TrimSpace(compact(m3Golden)) + " "},
{m4, strings.TrimSpace(m4Golden) + "\n", strings.TrimSpace(compact(m4Golden)) + " "},
{m5, strings.TrimSpace(m5Golden) + "\n", strings.TrimSpace(compact(m5Golden)) + " "},
{m6, strings.TrimSpace(m6Golden) + "\n", strings.TrimSpace(compact(m6Golden)) + " "},
}
}
func TestMarshalGolden(t *testing.T) {
for _, tt := range goldenMessages {
if got, want := expandedMarshaler.Text(tt.m), tt.t; got != want {
t.Errorf("message %v: got:\n%s\nwant:\n%s", tt.m, got, want)
}
if got, want := expandedCompactMarshaler.Text(tt.m), tt.c; got != want {
t.Errorf("message %v: got:\n`%s`\nwant:\n`%s`", tt.m, got, want)
}
}
}
func TestUnmarshalGolden(t *testing.T) {
for _, tt := range goldenMessages {
want := tt.m
got := proto.Clone(tt.m)
got.Reset()
if err := proto.UnmarshalText(tt.t, got); err != nil {
t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.t, err)
}
if !anyEqual(got, want) {
t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.t, got, want)
}
got.Reset()
if err := proto.UnmarshalText(tt.c, got); err != nil {
t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.c, err)
}
if !anyEqual(got, want) {
t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.c, got, want)
}
}
}
func TestMarsahlUnknownAny(t *testing.T) {
m := &pb.Message{
Anything: &anypb.Any{
TypeUrl: "foo",
Value: []byte("bar"),
},
}
want := `anything: <
type_url: "foo"
value: "bar"
>
`
got := expandedMarshaler.Text(m)
if got != want {
t.Errorf("got\n`%s`\nwant\n`%s`", got, want)
}
}
func TestAmbiguousAny(t *testing.T) {
pb := &anypb.Any{}
err := proto.UnmarshalText(`
[type.googleapis.com/proto3_proto.Nested]: <
bunny: "Monty"
>
type_url: "ttt/proto3_proto.Nested"
`, pb)
t.Logf("result: %v (error: %v)", expandedMarshaler.Text(pb), err)
if err != nil {
t.Errorf("failed to parse ambiguous Any message: %v", err)
}
}

229
vendor/github.com/golang/protobuf/proto/clone.go generated vendored Normal file
View file

@ -0,0 +1,229 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2011 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// 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.
// Protocol buffer deep copy and merge.
// TODO: RawMessage.
package proto
import (
"log"
"reflect"
"strings"
)
// Clone returns a deep copy of a protocol buffer.
func Clone(pb Message) Message {
in := reflect.ValueOf(pb)
if in.IsNil() {
return pb
}
out := reflect.New(in.Type().Elem())
// out is empty so a merge is a deep copy.
mergeStruct(out.Elem(), in.Elem())
return out.Interface().(Message)
}
// Merge merges src into dst.
// Required and optional fields that are set in src will be set to that value in dst.
// Elements of repeated fields will be appended.
// Merge panics if src and dst are not the same type, or if dst is nil.
func Merge(dst, src Message) {
in := reflect.ValueOf(src)
out := reflect.ValueOf(dst)
if out.IsNil() {
panic("proto: nil destination")
}
if in.Type() != out.Type() {
// Explicit test prior to mergeStruct so that mistyped nils will fail
panic("proto: type mismatch")
}
if in.IsNil() {
// Merging nil into non-nil is a quiet no-op
return
}
mergeStruct(out.Elem(), in.Elem())
}
func mergeStruct(out, in reflect.Value) {
sprop := GetProperties(in.Type())
for i := 0; i < in.NumField(); i++ {
f := in.Type().Field(i)
if strings.HasPrefix(f.Name, "XXX_") {
continue
}
mergeAny(out.Field(i), in.Field(i), false, sprop.Prop[i])
}
if emIn, ok := extendable(in.Addr().Interface()); ok {
emOut, _ := extendable(out.Addr().Interface())
mIn, muIn := emIn.extensionsRead()
if mIn != nil {
mOut := emOut.extensionsWrite()
muIn.Lock()
mergeExtension(mOut, mIn)
muIn.Unlock()
}
}
uf := in.FieldByName("XXX_unrecognized")
if !uf.IsValid() {
return
}
uin := uf.Bytes()
if len(uin) > 0 {
out.FieldByName("XXX_unrecognized").SetBytes(append([]byte(nil), uin...))
}
}
// mergeAny performs a merge between two values of the same type.
// viaPtr indicates whether the values were indirected through a pointer (implying proto2).
// prop is set if this is a struct field (it may be nil).
func mergeAny(out, in reflect.Value, viaPtr bool, prop *Properties) {
if in.Type() == protoMessageType {
if !in.IsNil() {
if out.IsNil() {
out.Set(reflect.ValueOf(Clone(in.Interface().(Message))))
} else {
Merge(out.Interface().(Message), in.Interface().(Message))
}
}
return
}
switch in.Kind() {
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
reflect.String, reflect.Uint32, reflect.Uint64:
if !viaPtr && isProto3Zero(in) {
return
}
out.Set(in)
case reflect.Interface:
// Probably a oneof field; copy non-nil values.
if in.IsNil() {
return
}
// Allocate destination if it is not set, or set to a different type.
// Otherwise we will merge as normal.
if out.IsNil() || out.Elem().Type() != in.Elem().Type() {
out.Set(reflect.New(in.Elem().Elem().Type())) // interface -> *T -> T -> new(T)
}
mergeAny(out.Elem(), in.Elem(), false, nil)
case reflect.Map:
if in.Len() == 0 {
return
}
if out.IsNil() {
out.Set(reflect.MakeMap(in.Type()))
}
// For maps with value types of *T or []byte we need to deep copy each value.
elemKind := in.Type().Elem().Kind()
for _, key := range in.MapKeys() {
var val reflect.Value
switch elemKind {
case reflect.Ptr:
val = reflect.New(in.Type().Elem().Elem())
mergeAny(val, in.MapIndex(key), false, nil)
case reflect.Slice:
val = in.MapIndex(key)
val = reflect.ValueOf(append([]byte{}, val.Bytes()...))
default:
val = in.MapIndex(key)
}
out.SetMapIndex(key, val)
}
case reflect.Ptr:
if in.IsNil() {
return
}
if out.IsNil() {
out.Set(reflect.New(in.Elem().Type()))
}
mergeAny(out.Elem(), in.Elem(), true, nil)
case reflect.Slice:
if in.IsNil() {
return
}
if in.Type().Elem().Kind() == reflect.Uint8 {
// []byte is a scalar bytes field, not a repeated field.
// Edge case: if this is in a proto3 message, a zero length
// bytes field is considered the zero value, and should not
// be merged.
if prop != nil && prop.proto3 && in.Len() == 0 {
return
}
// Make a deep copy.
// Append to []byte{} instead of []byte(nil) so that we never end up
// with a nil result.
out.SetBytes(append([]byte{}, in.Bytes()...))
return
}
n := in.Len()
if out.IsNil() {
out.Set(reflect.MakeSlice(in.Type(), 0, n))
}
switch in.Type().Elem().Kind() {
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
reflect.String, reflect.Uint32, reflect.Uint64:
out.Set(reflect.AppendSlice(out, in))
default:
for i := 0; i < n; i++ {
x := reflect.Indirect(reflect.New(in.Type().Elem()))
mergeAny(x, in.Index(i), false, nil)
out.Set(reflect.Append(out, x))
}
}
case reflect.Struct:
mergeStruct(out, in)
default:
// unknown type, so not a protocol buffer
log.Printf("proto: don't know how to copy %v", in)
}
}
func mergeExtension(out, in map[int32]Extension) {
for extNum, eIn := range in {
eOut := Extension{desc: eIn.desc}
if eIn.value != nil {
v := reflect.New(reflect.TypeOf(eIn.value)).Elem()
mergeAny(v, reflect.ValueOf(eIn.value), false, nil)
eOut.value = v.Interface()
}
if eIn.enc != nil {
eOut.enc = make([]byte, len(eIn.enc))
copy(eOut.enc, eIn.enc)
}
out[extNum] = eOut
}
}

300
vendor/github.com/golang/protobuf/proto/clone_test.go generated vendored Normal file
View file

@ -0,0 +1,300 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2011 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// 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.
package proto_test
import (
"testing"
"github.com/golang/protobuf/proto"
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
pb "github.com/golang/protobuf/proto/testdata"
)
var cloneTestMessage = &pb.MyMessage{
Count: proto.Int32(42),
Name: proto.String("Dave"),
Pet: []string{"bunny", "kitty", "horsey"},
Inner: &pb.InnerMessage{
Host: proto.String("niles"),
Port: proto.Int32(9099),
Connected: proto.Bool(true),
},
Others: []*pb.OtherMessage{
{
Value: []byte("some bytes"),
},
},
Somegroup: &pb.MyMessage_SomeGroup{
GroupField: proto.Int32(6),
},
RepBytes: [][]byte{[]byte("sham"), []byte("wow")},
}
func init() {
ext := &pb.Ext{
Data: proto.String("extension"),
}
if err := proto.SetExtension(cloneTestMessage, pb.E_Ext_More, ext); err != nil {
panic("SetExtension: " + err.Error())
}
}
func TestClone(t *testing.T) {
m := proto.Clone(cloneTestMessage).(*pb.MyMessage)
if !proto.Equal(m, cloneTestMessage) {
t.Errorf("Clone(%v) = %v", cloneTestMessage, m)
}
// Verify it was a deep copy.
*m.Inner.Port++
if proto.Equal(m, cloneTestMessage) {
t.Error("Mutating clone changed the original")
}
// Byte fields and repeated fields should be copied.
if &m.Pet[0] == &cloneTestMessage.Pet[0] {
t.Error("Pet: repeated field not copied")
}
if &m.Others[0] == &cloneTestMessage.Others[0] {
t.Error("Others: repeated field not copied")
}
if &m.Others[0].Value[0] == &cloneTestMessage.Others[0].Value[0] {
t.Error("Others[0].Value: bytes field not copied")
}
if &m.RepBytes[0] == &cloneTestMessage.RepBytes[0] {
t.Error("RepBytes: repeated field not copied")
}
if &m.RepBytes[0][0] == &cloneTestMessage.RepBytes[0][0] {
t.Error("RepBytes[0]: bytes field not copied")
}
}
func TestCloneNil(t *testing.T) {
var m *pb.MyMessage
if c := proto.Clone(m); !proto.Equal(m, c) {
t.Errorf("Clone(%v) = %v", m, c)
}
}
var mergeTests = []struct {
src, dst, want proto.Message
}{
{
src: &pb.MyMessage{
Count: proto.Int32(42),
},
dst: &pb.MyMessage{
Name: proto.String("Dave"),
},
want: &pb.MyMessage{
Count: proto.Int32(42),
Name: proto.String("Dave"),
},
},
{
src: &pb.MyMessage{
Inner: &pb.InnerMessage{
Host: proto.String("hey"),
Connected: proto.Bool(true),
},
Pet: []string{"horsey"},
Others: []*pb.OtherMessage{
{
Value: []byte("some bytes"),
},
},
},
dst: &pb.MyMessage{
Inner: &pb.InnerMessage{
Host: proto.String("niles"),
Port: proto.Int32(9099),
},
Pet: []string{"bunny", "kitty"},
Others: []*pb.OtherMessage{
{
Key: proto.Int64(31415926535),
},
{
// Explicitly test a src=nil field
Inner: nil,
},
},
},
want: &pb.MyMessage{
Inner: &pb.InnerMessage{
Host: proto.String("hey"),
Connected: proto.Bool(true),
Port: proto.Int32(9099),
},
Pet: []string{"bunny", "kitty", "horsey"},
Others: []*pb.OtherMessage{
{
Key: proto.Int64(31415926535),
},
{},
{
Value: []byte("some bytes"),
},
},
},
},
{
src: &pb.MyMessage{
RepBytes: [][]byte{[]byte("wow")},
},
dst: &pb.MyMessage{
Somegroup: &pb.MyMessage_SomeGroup{
GroupField: proto.Int32(6),
},
RepBytes: [][]byte{[]byte("sham")},
},
want: &pb.MyMessage{
Somegroup: &pb.MyMessage_SomeGroup{
GroupField: proto.Int32(6),
},
RepBytes: [][]byte{[]byte("sham"), []byte("wow")},
},
},
// Check that a scalar bytes field replaces rather than appends.
{
src: &pb.OtherMessage{Value: []byte("foo")},
dst: &pb.OtherMessage{Value: []byte("bar")},
want: &pb.OtherMessage{Value: []byte("foo")},
},
{
src: &pb.MessageWithMap{
NameMapping: map[int32]string{6: "Nigel"},
MsgMapping: map[int64]*pb.FloatingPoint{
0x4001: &pb.FloatingPoint{F: proto.Float64(2.0)},
0x4002: &pb.FloatingPoint{
F: proto.Float64(2.0),
},
},
ByteMapping: map[bool][]byte{true: []byte("wowsa")},
},
dst: &pb.MessageWithMap{
NameMapping: map[int32]string{
6: "Bruce", // should be overwritten
7: "Andrew",
},
MsgMapping: map[int64]*pb.FloatingPoint{
0x4002: &pb.FloatingPoint{
F: proto.Float64(3.0),
Exact: proto.Bool(true),
}, // the entire message should be overwritten
},
},
want: &pb.MessageWithMap{
NameMapping: map[int32]string{
6: "Nigel",
7: "Andrew",
},
MsgMapping: map[int64]*pb.FloatingPoint{
0x4001: &pb.FloatingPoint{F: proto.Float64(2.0)},
0x4002: &pb.FloatingPoint{
F: proto.Float64(2.0),
},
},
ByteMapping: map[bool][]byte{true: []byte("wowsa")},
},
},
// proto3 shouldn't merge zero values,
// in the same way that proto2 shouldn't merge nils.
{
src: &proto3pb.Message{
Name: "Aaron",
Data: []byte(""), // zero value, but not nil
},
dst: &proto3pb.Message{
HeightInCm: 176,
Data: []byte("texas!"),
},
want: &proto3pb.Message{
Name: "Aaron",
HeightInCm: 176,
Data: []byte("texas!"),
},
},
// Oneof fields should merge by assignment.
{
src: &pb.Communique{
Union: &pb.Communique_Number{41},
},
dst: &pb.Communique{
Union: &pb.Communique_Name{"Bobby Tables"},
},
want: &pb.Communique{
Union: &pb.Communique_Number{41},
},
},
// Oneof nil is the same as not set.
{
src: &pb.Communique{},
dst: &pb.Communique{
Union: &pb.Communique_Name{"Bobby Tables"},
},
want: &pb.Communique{
Union: &pb.Communique_Name{"Bobby Tables"},
},
},
{
src: &proto3pb.Message{
Terrain: map[string]*proto3pb.Nested{
"kay_a": &proto3pb.Nested{Cute: true}, // replace
"kay_b": &proto3pb.Nested{Bunny: "rabbit"}, // insert
},
},
dst: &proto3pb.Message{
Terrain: map[string]*proto3pb.Nested{
"kay_a": &proto3pb.Nested{Bunny: "lost"}, // replaced
"kay_c": &proto3pb.Nested{Bunny: "bunny"}, // keep
},
},
want: &proto3pb.Message{
Terrain: map[string]*proto3pb.Nested{
"kay_a": &proto3pb.Nested{Cute: true},
"kay_b": &proto3pb.Nested{Bunny: "rabbit"},
"kay_c": &proto3pb.Nested{Bunny: "bunny"},
},
},
},
}
func TestMerge(t *testing.T) {
for _, m := range mergeTests {
got := proto.Clone(m.dst)
proto.Merge(got, m.src)
if !proto.Equal(got, m.want) {
t.Errorf("Merge(%v, %v)\n got %v\nwant %v\n", m.dst, m.src, got, m.want)
}
}
}

874
vendor/github.com/golang/protobuf/proto/decode.go generated vendored Normal file
View file

@ -0,0 +1,874 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2010 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// 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.
package proto
/*
* Routines for decoding protocol buffer data to construct in-memory representations.
*/
import (
"errors"
"fmt"
"io"
"os"
"reflect"
)
// errOverflow is returned when an integer is too large to be represented.
var errOverflow = errors.New("proto: integer overflow")
// ErrInternalBadWireType is returned by generated code when an incorrect
// wire type is encountered. It does not get returned to user code.
var ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof")
// The fundamental decoders that interpret bytes on the wire.
// Those that take integer types all return uint64 and are
// therefore of type valueDecoder.
// DecodeVarint reads a varint-encoded integer from the slice.
// It returns the integer and the number of bytes consumed, or
// zero if there is not enough.
// This is the format for the
// int32, int64, uint32, uint64, bool, and enum
// protocol buffer types.
func DecodeVarint(buf []byte) (x uint64, n int) {
// x, n already 0
for shift := uint(0); shift < 64; shift += 7 {
if n >= len(buf) {
return 0, 0
}
b := uint64(buf[n])
n++
x |= (b & 0x7F) << shift
if (b & 0x80) == 0 {
return x, n
}
}
// The number is too large to represent in a 64-bit value.
return 0, 0
}
// DecodeVarint reads a varint-encoded integer from the Buffer.
// This is the format for the
// int32, int64, uint32, uint64, bool, and enum
// protocol buffer types.
func (p *Buffer) DecodeVarint() (x uint64, err error) {
// x, err already 0
i := p.index
l := len(p.buf)
for shift := uint(0); shift < 64; shift += 7 {
if i >= l {
err = io.ErrUnexpectedEOF
return
}
b := p.buf[i]
i++
x |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
p.index = i
return
}
}
// The number is too large to represent in a 64-bit value.
err = errOverflow
return
}
// DecodeFixed64 reads a 64-bit integer from the Buffer.
// This is the format for the
// fixed64, sfixed64, and double protocol buffer types.
func (p *Buffer) DecodeFixed64() (x uint64, err error) {
// x, err already 0
i := p.index + 8
if i < 0 || i > len(p.buf) {
err = io.ErrUnexpectedEOF
return
}
p.index = i
x = uint64(p.buf[i-8])
x |= uint64(p.buf[i-7]) << 8
x |= uint64(p.buf[i-6]) << 16
x |= uint64(p.buf[i-5]) << 24
x |= uint64(p.buf[i-4]) << 32
x |= uint64(p.buf[i-3]) << 40
x |= uint64(p.buf[i-2]) << 48
x |= uint64(p.buf[i-1]) << 56
return
}
// DecodeFixed32 reads a 32-bit integer from the Buffer.
// This is the format for the
// fixed32, sfixed32, and float protocol buffer types.
func (p *Buffer) DecodeFixed32() (x uint64, err error) {
// x, err already 0
i := p.index + 4
if i < 0 || i > len(p.buf) {
err = io.ErrUnexpectedEOF
return
}
p.index = i
x = uint64(p.buf[i-4])
x |= uint64(p.buf[i-3]) << 8
x |= uint64(p.buf[i-2]) << 16
x |= uint64(p.buf[i-1]) << 24
return
}
// DecodeZigzag64 reads a zigzag-encoded 64-bit integer
// from the Buffer.
// This is the format used for the sint64 protocol buffer type.
func (p *Buffer) DecodeZigzag64() (x uint64, err error) {
x, err = p.DecodeVarint()
if err != nil {
return
}
x = (x >> 1) ^ uint64((int64(x&1)<<63)>>63)
return
}
// DecodeZigzag32 reads a zigzag-encoded 32-bit integer
// from the Buffer.
// This is the format used for the sint32 protocol buffer type.
func (p *Buffer) DecodeZigzag32() (x uint64, err error) {
x, err = p.DecodeVarint()
if err != nil {
return
}
x = uint64((uint32(x) >> 1) ^ uint32((int32(x&1)<<31)>>31))
return
}
// These are not ValueDecoders: they produce an array of bytes or a string.
// bytes, embedded messages
// DecodeRawBytes reads a count-delimited byte buffer from the Buffer.
// This is the format used for the bytes protocol buffer
// type and for embedded messages.
func (p *Buffer) DecodeRawBytes(alloc bool) (buf []byte, err error) {
n, err := p.DecodeVarint()
if err != nil {
return nil, err
}
nb := int(n)
if nb < 0 {
return nil, fmt.Errorf("proto: bad byte length %d", nb)
}
end := p.index + nb
if end < p.index || end > len(p.buf) {
return nil, io.ErrUnexpectedEOF
}
if !alloc {
// todo: check if can get more uses of alloc=false
buf = p.buf[p.index:end]
p.index += nb
return
}
buf = make([]byte, nb)
copy(buf, p.buf[p.index:])
p.index += nb
return
}
// DecodeStringBytes reads an encoded string from the Buffer.
// This is the format used for the proto2 string type.
func (p *Buffer) DecodeStringBytes() (s string, err error) {
buf, err := p.DecodeRawBytes(false)
if err != nil {
return
}
return string(buf), nil
}
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
// If the protocol buffer has extensions, and the field matches, add it as an extension.
// Otherwise, if the XXX_unrecognized field exists, append the skipped data there.
func (o *Buffer) skipAndSave(t reflect.Type, tag, wire int, base structPointer, unrecField field) error {
oi := o.index
err := o.skip(t, tag, wire)
if err != nil {
return err
}
if !unrecField.IsValid() {
return nil
}
ptr := structPointer_Bytes(base, unrecField)
// Add the skipped field to struct field
obuf := o.buf
o.buf = *ptr
o.EncodeVarint(uint64(tag<<3 | wire))
*ptr = append(o.buf, obuf[oi:o.index]...)
o.buf = obuf
return nil
}
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
func (o *Buffer) skip(t reflect.Type, tag, wire int) error {
var u uint64
var err error
switch wire {
case WireVarint:
_, err = o.DecodeVarint()
case WireFixed64:
_, err = o.DecodeFixed64()
case WireBytes:
_, err = o.DecodeRawBytes(false)
case WireFixed32:
_, err = o.DecodeFixed32()
case WireStartGroup:
for {
u, err = o.DecodeVarint()
if err != nil {
break
}
fwire := int(u & 0x7)
if fwire == WireEndGroup {
break
}
ftag := int(u >> 3)
err = o.skip(t, ftag, fwire)
if err != nil {
break
}
}
default:
err = fmt.Errorf("proto: can't skip unknown wire type %d for %s", wire, t)
}
return err
}
// Unmarshaler is the interface representing objects that can
// unmarshal themselves. The method should reset the receiver before
// decoding starts. The argument points to data that may be
// overwritten, so implementations should not keep references to the
// buffer.
type Unmarshaler interface {
Unmarshal([]byte) error
}
// Unmarshal parses the protocol buffer representation in buf and places the
// decoded result in pb. If the struct underlying pb does not match
// the data in buf, the results can be unpredictable.
//
// Unmarshal resets pb before starting to unmarshal, so any
// existing data in pb is always removed. Use UnmarshalMerge
// to preserve and append to existing data.
func Unmarshal(buf []byte, pb Message) error {
pb.Reset()
return UnmarshalMerge(buf, pb)
}
// UnmarshalMerge parses the protocol buffer representation in buf and
// writes the decoded result to pb. If the struct underlying pb does not match
// the data in buf, the results can be unpredictable.
//
// UnmarshalMerge merges into existing data in pb.
// Most code should use Unmarshal instead.
func UnmarshalMerge(buf []byte, pb Message) error {
// If the object can unmarshal itself, let it.
if u, ok := pb.(Unmarshaler); ok {
return u.Unmarshal(buf)
}
return NewBuffer(buf).Unmarshal(pb)
}
// DecodeMessage reads a count-delimited message from the Buffer.
func (p *Buffer) DecodeMessage(pb Message) error {
enc, err := p.DecodeRawBytes(false)
if err != nil {
return err
}
return NewBuffer(enc).Unmarshal(pb)
}
// DecodeGroup reads a tag-delimited group from the Buffer.
func (p *Buffer) DecodeGroup(pb Message) error {
typ, base, err := getbase(pb)
if err != nil {
return err
}
return p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), true, base)
}
// Unmarshal parses the protocol buffer representation in the
// Buffer and places the decoded result in pb. If the struct
// underlying pb does not match the data in the buffer, the results can be
// unpredictable.
func (p *Buffer) Unmarshal(pb Message) error {
// If the object can unmarshal itself, let it.
if u, ok := pb.(Unmarshaler); ok {
err := u.Unmarshal(p.buf[p.index:])
p.index = len(p.buf)
return err
}
typ, base, err := getbase(pb)
if err != nil {
return err
}
err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base)
if collectStats {
stats.Decode++
}
return err
}
// unmarshalType does the work of unmarshaling a structure.
func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group bool, base structPointer) error {
var state errorState
required, reqFields := prop.reqCount, uint64(0)
var err error
for err == nil && o.index < len(o.buf) {
oi := o.index
var u uint64
u, err = o.DecodeVarint()
if err != nil {
break
}
wire := int(u & 0x7)
if wire == WireEndGroup {
if is_group {
if required > 0 {
// Not enough information to determine the exact field.
// (See below.)
return &RequiredNotSetError{"{Unknown}"}
}
return nil // input is satisfied
}
return fmt.Errorf("proto: %s: wiretype end group for non-group", st)
}
tag := int(u >> 3)
if tag <= 0 {
return fmt.Errorf("proto: %s: illegal tag %d (wire type %d)", st, tag, wire)
}
fieldnum, ok := prop.decoderTags.get(tag)
if !ok {
// Maybe it's an extension?
if prop.extendable {
if e, _ := extendable(structPointer_Interface(base, st)); isExtensionField(e, int32(tag)) {
if err = o.skip(st, tag, wire); err == nil {
extmap := e.extensionsWrite()
ext := extmap[int32(tag)] // may be missing
ext.enc = append(ext.enc, o.buf[oi:o.index]...)
extmap[int32(tag)] = ext
}
continue
}
}
// Maybe it's a oneof?
if prop.oneofUnmarshaler != nil {
m := structPointer_Interface(base, st).(Message)
// First return value indicates whether tag is a oneof field.
ok, err = prop.oneofUnmarshaler(m, tag, wire, o)
if err == ErrInternalBadWireType {
// Map the error to something more descriptive.
// Do the formatting here to save generated code space.
err = fmt.Errorf("bad wiretype for oneof field in %T", m)
}
if ok {
continue
}
}
err = o.skipAndSave(st, tag, wire, base, prop.unrecField)
continue
}
p := prop.Prop[fieldnum]
if p.dec == nil {
fmt.Fprintf(os.Stderr, "proto: no protobuf decoder for %s.%s\n", st, st.Field(fieldnum).Name)
continue
}
dec := p.dec
if wire != WireStartGroup && wire != p.WireType {
if wire == WireBytes && p.packedDec != nil {
// a packable field
dec = p.packedDec
} else {
err = fmt.Errorf("proto: bad wiretype for field %s.%s: got wiretype %d, want %d", st, st.Field(fieldnum).Name, wire, p.WireType)
continue
}
}
decErr := dec(o, p, base)
if decErr != nil && !state.shouldContinue(decErr, p) {
err = decErr
}
if err == nil && p.Required {
// Successfully decoded a required field.
if tag <= 64 {
// use bitmap for fields 1-64 to catch field reuse.
var mask uint64 = 1 << uint64(tag-1)
if reqFields&mask == 0 {
// new required field
reqFields |= mask
required--
}
} else {
// This is imprecise. It can be fooled by a required field
// with a tag > 64 that is encoded twice; that's very rare.
// A fully correct implementation would require allocating
// a data structure, which we would like to avoid.
required--
}
}
}
if err == nil {
if is_group {
return io.ErrUnexpectedEOF
}
if state.err != nil {
return state.err
}
if required > 0 {
// Not enough information to determine the exact field. If we use extra
// CPU, we could determine the field only if the missing required field
// has a tag <= 64 and we check reqFields.
return &RequiredNotSetError{"{Unknown}"}
}
}
return err
}
// Individual type decoders
// For each,
// u is the decoded value,
// v is a pointer to the field (pointer) in the struct
// Sizes of the pools to allocate inside the Buffer.
// The goal is modest amortization and allocation
// on at least 16-byte boundaries.
const (
boolPoolSize = 16
uint32PoolSize = 8
uint64PoolSize = 4
)
// Decode a bool.
func (o *Buffer) dec_bool(p *Properties, base structPointer) error {
u, err := p.valDec(o)
if err != nil {
return err
}
if len(o.bools) == 0 {
o.bools = make([]bool, boolPoolSize)
}
o.bools[0] = u != 0
*structPointer_Bool(base, p.field) = &o.bools[0]
o.bools = o.bools[1:]
return nil
}
func (o *Buffer) dec_proto3_bool(p *Properties, base structPointer) error {
u, err := p.valDec(o)
if err != nil {
return err
}
*structPointer_BoolVal(base, p.field) = u != 0
return nil
}
// Decode an int32.
func (o *Buffer) dec_int32(p *Properties, base structPointer) error {
u, err := p.valDec(o)
if err != nil {
return err
}
word32_Set(structPointer_Word32(base, p.field), o, uint32(u))
return nil
}
func (o *Buffer) dec_proto3_int32(p *Properties, base structPointer) error {
u, err := p.valDec(o)
if err != nil {
return err
}
word32Val_Set(structPointer_Word32Val(base, p.field), uint32(u))
return nil
}
// Decode an int64.
func (o *Buffer) dec_int64(p *Properties, base structPointer) error {
u, err := p.valDec(o)
if err != nil {
return err
}
word64_Set(structPointer_Word64(base, p.field), o, u)
return nil
}
func (o *Buffer) dec_proto3_int64(p *Properties, base structPointer) error {
u, err := p.valDec(o)
if err != nil {
return err
}
word64Val_Set(structPointer_Word64Val(base, p.field), o, u)
return nil
}
// Decode a string.
func (o *Buffer) dec_string(p *Properties, base structPointer) error {
s, err := o.DecodeStringBytes()
if err != nil {
return err
}
*structPointer_String(base, p.field) = &s
return nil
}
func (o *Buffer) dec_proto3_string(p *Properties, base structPointer) error {
s, err := o.DecodeStringBytes()
if err != nil {
return err
}
*structPointer_StringVal(base, p.field) = s
return nil
}
// Decode a slice of bytes ([]byte).
func (o *Buffer) dec_slice_byte(p *Properties, base structPointer) error {
b, err := o.DecodeRawBytes(true)
if err != nil {
return err
}
*structPointer_Bytes(base, p.field) = b
return nil
}
// Decode a slice of bools ([]bool).
func (o *Buffer) dec_slice_bool(p *Properties, base structPointer) error {
u, err := p.valDec(o)
if err != nil {
return err
}
v := structPointer_BoolSlice(base, p.field)
*v = append(*v, u != 0)
return nil
}
// Decode a slice of bools ([]bool) in packed format.
func (o *Buffer) dec_slice_packed_bool(p *Properties, base structPointer) error {
v := structPointer_BoolSlice(base, p.field)
nn, err := o.DecodeVarint()
if err != nil {
return err
}
nb := int(nn) // number of bytes of encoded bools
fin := o.index + nb
if fin < o.index {
return errOverflow
}
y := *v
for o.index < fin {
u, err := p.valDec(o)
if err != nil {
return err
}
y = append(y, u != 0)
}
*v = y
return nil
}
// Decode a slice of int32s ([]int32).
func (o *Buffer) dec_slice_int32(p *Properties, base structPointer) error {
u, err := p.valDec(o)
if err != nil {
return err
}
structPointer_Word32Slice(base, p.field).Append(uint32(u))
return nil
}
// Decode a slice of int32s ([]int32) in packed format.
func (o *Buffer) dec_slice_packed_int32(p *Properties, base structPointer) error {
v := structPointer_Word32Slice(base, p.field)
nn, err := o.DecodeVarint()
if err != nil {
return err
}
nb := int(nn) // number of bytes of encoded int32s
fin := o.index + nb
if fin < o.index {
return errOverflow
}
for o.index < fin {
u, err := p.valDec(o)
if err != nil {
return err
}
v.Append(uint32(u))
}
return nil
}
// Decode a slice of int64s ([]int64).
func (o *Buffer) dec_slice_int64(p *Properties, base structPointer) error {
u, err := p.valDec(o)
if err != nil {
return err
}
structPointer_Word64Slice(base, p.field).Append(u)
return nil
}
// Decode a slice of int64s ([]int64) in packed format.
func (o *Buffer) dec_slice_packed_int64(p *Properties, base structPointer) error {
v := structPointer_Word64Slice(base, p.field)
nn, err := o.DecodeVarint()
if err != nil {
return err
}
nb := int(nn) // number of bytes of encoded int64s
fin := o.index + nb
if fin < o.index {
return errOverflow
}
for o.index < fin {
u, err := p.valDec(o)
if err != nil {
return err
}
v.Append(u)
}
return nil
}
// Decode a slice of strings ([]string).
func (o *Buffer) dec_slice_string(p *Properties, base structPointer) error {
s, err := o.DecodeStringBytes()
if err != nil {
return err
}
v := structPointer_StringSlice(base, p.field)
*v = append(*v, s)
return nil
}
// Decode a slice of slice of bytes ([][]byte).
func (o *Buffer) dec_slice_slice_byte(p *Properties, base structPointer) error {
b, err := o.DecodeRawBytes(true)
if err != nil {
return err
}
v := structPointer_BytesSlice(base, p.field)
*v = append(*v, b)
return nil
}
// Decode a map field.
func (o *Buffer) dec_new_map(p *Properties, base structPointer) error {
raw, err := o.DecodeRawBytes(false)
if err != nil {
return err
}
oi := o.index // index at the end of this map entry
o.index -= len(raw) // move buffer back to start of map entry
mptr := structPointer_NewAt(base, p.field, p.mtype) // *map[K]V
if mptr.Elem().IsNil() {
mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem()))
}
v := mptr.Elem() // map[K]V
// Prepare addressable doubly-indirect placeholders for the key and value types.
// See enc_new_map for why.
keyptr := reflect.New(reflect.PtrTo(p.mtype.Key())).Elem() // addressable *K
keybase := toStructPointer(keyptr.Addr()) // **K
var valbase structPointer
var valptr reflect.Value
switch p.mtype.Elem().Kind() {
case reflect.Slice:
// []byte
var dummy []byte
valptr = reflect.ValueOf(&dummy) // *[]byte
valbase = toStructPointer(valptr) // *[]byte
case reflect.Ptr:
// message; valptr is **Msg; need to allocate the intermediate pointer
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
valptr.Set(reflect.New(valptr.Type().Elem()))
valbase = toStructPointer(valptr)
default:
// everything else
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
valbase = toStructPointer(valptr.Addr()) // **V
}
// Decode.
// This parses a restricted wire format, namely the encoding of a message
// with two fields. See enc_new_map for the format.
for o.index < oi {
// tagcode for key and value properties are always a single byte
// because they have tags 1 and 2.
tagcode := o.buf[o.index]
o.index++
switch tagcode {
case p.mkeyprop.tagcode[0]:
if err := p.mkeyprop.dec(o, p.mkeyprop, keybase); err != nil {
return err
}
case p.mvalprop.tagcode[0]:
if err := p.mvalprop.dec(o, p.mvalprop, valbase); err != nil {
return err
}
default:
// TODO: Should we silently skip this instead?
return fmt.Errorf("proto: bad map data tag %d", raw[0])
}
}
keyelem, valelem := keyptr.Elem(), valptr.Elem()
if !keyelem.IsValid() {
keyelem = reflect.Zero(p.mtype.Key())
}
if !valelem.IsValid() {
valelem = reflect.Zero(p.mtype.Elem())
}
v.SetMapIndex(keyelem, valelem)
return nil
}
// Decode a group.
func (o *Buffer) dec_struct_group(p *Properties, base structPointer) error {
bas := structPointer_GetStructPointer(base, p.field)
if structPointer_IsNil(bas) {
// allocate new nested message
bas = toStructPointer(reflect.New(p.stype))
structPointer_SetStructPointer(base, p.field, bas)
}
return o.unmarshalType(p.stype, p.sprop, true, bas)
}
// Decode an embedded message.
func (o *Buffer) dec_struct_message(p *Properties, base structPointer) (err error) {
raw, e := o.DecodeRawBytes(false)
if e != nil {
return e
}
bas := structPointer_GetStructPointer(base, p.field)
if structPointer_IsNil(bas) {
// allocate new nested message
bas = toStructPointer(reflect.New(p.stype))
structPointer_SetStructPointer(base, p.field, bas)
}
// If the object can unmarshal itself, let it.
if p.isUnmarshaler {
iv := structPointer_Interface(bas, p.stype)
return iv.(Unmarshaler).Unmarshal(raw)
}
obuf := o.buf
oi := o.index
o.buf = raw
o.index = 0
err = o.unmarshalType(p.stype, p.sprop, false, bas)
o.buf = obuf
o.index = oi
return err
}
// Decode a slice of embedded messages.
func (o *Buffer) dec_slice_struct_message(p *Properties, base structPointer) error {
return o.dec_slice_struct(p, false, base)
}
// Decode a slice of embedded groups.
func (o *Buffer) dec_slice_struct_group(p *Properties, base structPointer) error {
return o.dec_slice_struct(p, true, base)
}
// Decode a slice of structs ([]*struct).
func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base structPointer) error {
v := reflect.New(p.stype)
bas := toStructPointer(v)
structPointer_StructPointerSlice(base, p.field).Append(bas)
if is_group {
err := o.unmarshalType(p.stype, p.sprop, is_group, bas)
return err
}
raw, err := o.DecodeRawBytes(false)
if err != nil {
return err
}
// If the object can unmarshal itself, let it.
if p.isUnmarshaler {
iv := v.Interface()
return iv.(Unmarshaler).Unmarshal(raw)
}
obuf := o.buf
oi := o.index
o.buf = raw
o.index = 0
err = o.unmarshalType(p.stype, p.sprop, is_group, bas)
o.buf = obuf
o.index = oi
return err
}

1363
vendor/github.com/golang/protobuf/proto/encode.go generated vendored Normal file

File diff suppressed because it is too large Load diff

296
vendor/github.com/golang/protobuf/proto/equal.go generated vendored Normal file
View file

@ -0,0 +1,296 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2011 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// 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.
// Protocol buffer comparison.
package proto
import (
"bytes"
"log"
"reflect"
"strings"
)
/*
Equal returns true iff protocol buffers a and b are equal.
The arguments must both be pointers to protocol buffer structs.
Equality is defined in this way:
- Two messages are equal iff they are the same type,
corresponding fields are equal, unknown field sets
are equal, and extensions sets are equal.
- Two set scalar fields are equal iff their values are equal.
If the fields are of a floating-point type, remember that
NaN != x for all x, including NaN. If the message is defined
in a proto3 .proto file, fields are not "set"; specifically,
zero length proto3 "bytes" fields are equal (nil == {}).
- Two repeated fields are equal iff their lengths are the same,
and their corresponding elements are equal (a "bytes" field,
although represented by []byte, is not a repeated field)
- Two unset fields are equal.
- Two unknown field sets are equal if their current
encoded state is equal.
- Two extension sets are equal iff they have corresponding
elements that are pairwise equal.
- Every other combination of things are not equal.
The return value is undefined if a and b are not protocol buffers.
*/
func Equal(a, b Message) bool {
if a == nil || b == nil {
return a == b
}
v1, v2 := reflect.ValueOf(a), reflect.ValueOf(b)
if v1.Type() != v2.Type() {
return false
}
if v1.Kind() == reflect.Ptr {
if v1.IsNil() {
return v2.IsNil()
}
if v2.IsNil() {
return false
}
v1, v2 = v1.Elem(), v2.Elem()
}
if v1.Kind() != reflect.Struct {
return false
}
return equalStruct(v1, v2)
}
// v1 and v2 are known to have the same type.
func equalStruct(v1, v2 reflect.Value) bool {
sprop := GetProperties(v1.Type())
for i := 0; i < v1.NumField(); i++ {
f := v1.Type().Field(i)
if strings.HasPrefix(f.Name, "XXX_") {
continue
}
f1, f2 := v1.Field(i), v2.Field(i)
if f.Type.Kind() == reflect.Ptr {
if n1, n2 := f1.IsNil(), f2.IsNil(); n1 && n2 {
// both unset
continue
} else if n1 != n2 {
// set/unset mismatch
return false
}
b1, ok := f1.Interface().(raw)
if ok {
b2 := f2.Interface().(raw)
// RawMessage
if !bytes.Equal(b1.Bytes(), b2.Bytes()) {
return false
}
continue
}
f1, f2 = f1.Elem(), f2.Elem()
}
if !equalAny(f1, f2, sprop.Prop[i]) {
return false
}
}
if em1 := v1.FieldByName("XXX_InternalExtensions"); em1.IsValid() {
em2 := v2.FieldByName("XXX_InternalExtensions")
if !equalExtensions(v1.Type(), em1.Interface().(XXX_InternalExtensions), em2.Interface().(XXX_InternalExtensions)) {
return false
}
}
if em1 := v1.FieldByName("XXX_extensions"); em1.IsValid() {
em2 := v2.FieldByName("XXX_extensions")
if !equalExtMap(v1.Type(), em1.Interface().(map[int32]Extension), em2.Interface().(map[int32]Extension)) {
return false
}
}
uf := v1.FieldByName("XXX_unrecognized")
if !uf.IsValid() {
return true
}
u1 := uf.Bytes()
u2 := v2.FieldByName("XXX_unrecognized").Bytes()
if !bytes.Equal(u1, u2) {
return false
}
return true
}
// v1 and v2 are known to have the same type.
// prop may be nil.
func equalAny(v1, v2 reflect.Value, prop *Properties) bool {
if v1.Type() == protoMessageType {
m1, _ := v1.Interface().(Message)
m2, _ := v2.Interface().(Message)
return Equal(m1, m2)
}
switch v1.Kind() {
case reflect.Bool:
return v1.Bool() == v2.Bool()
case reflect.Float32, reflect.Float64:
return v1.Float() == v2.Float()
case reflect.Int32, reflect.Int64:
return v1.Int() == v2.Int()
case reflect.Interface:
// Probably a oneof field; compare the inner values.
n1, n2 := v1.IsNil(), v2.IsNil()
if n1 || n2 {
return n1 == n2
}
e1, e2 := v1.Elem(), v2.Elem()
if e1.Type() != e2.Type() {
return false
}
return equalAny(e1, e2, nil)
case reflect.Map:
if v1.Len() != v2.Len() {
return false
}
for _, key := range v1.MapKeys() {
val2 := v2.MapIndex(key)
if !val2.IsValid() {
// This key was not found in the second map.
return false
}
if !equalAny(v1.MapIndex(key), val2, nil) {
return false
}
}
return true
case reflect.Ptr:
// Maps may have nil values in them, so check for nil.
if v1.IsNil() && v2.IsNil() {
return true
}
if v1.IsNil() != v2.IsNil() {
return false
}
return equalAny(v1.Elem(), v2.Elem(), prop)
case reflect.Slice:
if v1.Type().Elem().Kind() == reflect.Uint8 {
// short circuit: []byte
// Edge case: if this is in a proto3 message, a zero length
// bytes field is considered the zero value.
if prop != nil && prop.proto3 && v1.Len() == 0 && v2.Len() == 0 {
return true
}
if v1.IsNil() != v2.IsNil() {
return false
}
return bytes.Equal(v1.Interface().([]byte), v2.Interface().([]byte))
}
if v1.Len() != v2.Len() {
return false
}
for i := 0; i < v1.Len(); i++ {
if !equalAny(v1.Index(i), v2.Index(i), prop) {
return false
}
}
return true
case reflect.String:
return v1.Interface().(string) == v2.Interface().(string)
case reflect.Struct:
return equalStruct(v1, v2)
case reflect.Uint32, reflect.Uint64:
return v1.Uint() == v2.Uint()
}
// unknown type, so not a protocol buffer
log.Printf("proto: don't know how to compare %v", v1)
return false
}
// base is the struct type that the extensions are based on.
// x1 and x2 are InternalExtensions.
func equalExtensions(base reflect.Type, x1, x2 XXX_InternalExtensions) bool {
em1, _ := x1.extensionsRead()
em2, _ := x2.extensionsRead()
return equalExtMap(base, em1, em2)
}
func equalExtMap(base reflect.Type, em1, em2 map[int32]Extension) bool {
if len(em1) != len(em2) {
return false
}
for extNum, e1 := range em1 {
e2, ok := em2[extNum]
if !ok {
return false
}
m1, m2 := e1.value, e2.value
if m1 != nil && m2 != nil {
// Both are unencoded.
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
return false
}
continue
}
// At least one is encoded. To do a semantically correct comparison
// we need to unmarshal them first.
var desc *ExtensionDesc
if m := extensionMaps[base]; m != nil {
desc = m[extNum]
}
if desc == nil {
log.Printf("proto: don't know how to compare extension %d of %v", extNum, base)
continue
}
var err error
if m1 == nil {
m1, err = decodeExtension(e1.enc, desc)
}
if m2 == nil && err == nil {
m2, err = decodeExtension(e2.enc, desc)
}
if err != nil {
// The encoded form is invalid.
log.Printf("proto: badly encoded extension %d of %v: %v", extNum, base, err)
return false
}
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
return false
}
}
return true
}

212
vendor/github.com/golang/protobuf/proto/equal_test.go generated vendored Normal file
View file

@ -0,0 +1,212 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2011 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// 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.
package proto_test
import (
"testing"
. "github.com/golang/protobuf/proto"
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
pb "github.com/golang/protobuf/proto/testdata"
)
// Four identical base messages.
// The init function adds extensions to some of them.
var messageWithoutExtension = &pb.MyMessage{Count: Int32(7)}
var messageWithExtension1a = &pb.MyMessage{Count: Int32(7)}
var messageWithExtension1b = &pb.MyMessage{Count: Int32(7)}
var messageWithExtension2 = &pb.MyMessage{Count: Int32(7)}
// Two messages with non-message extensions.
var messageWithInt32Extension1 = &pb.MyMessage{Count: Int32(8)}
var messageWithInt32Extension2 = &pb.MyMessage{Count: Int32(8)}
func init() {
ext1 := &pb.Ext{Data: String("Kirk")}
ext2 := &pb.Ext{Data: String("Picard")}
// messageWithExtension1a has ext1, but never marshals it.
if err := SetExtension(messageWithExtension1a, pb.E_Ext_More, ext1); err != nil {
panic("SetExtension on 1a failed: " + err.Error())
}
// messageWithExtension1b is the unmarshaled form of messageWithExtension1a.
if err := SetExtension(messageWithExtension1b, pb.E_Ext_More, ext1); err != nil {
panic("SetExtension on 1b failed: " + err.Error())
}
buf, err := Marshal(messageWithExtension1b)
if err != nil {
panic("Marshal of 1b failed: " + err.Error())
}
messageWithExtension1b.Reset()
if err := Unmarshal(buf, messageWithExtension1b); err != nil {
panic("Unmarshal of 1b failed: " + err.Error())
}
// messageWithExtension2 has ext2.
if err := SetExtension(messageWithExtension2, pb.E_Ext_More, ext2); err != nil {
panic("SetExtension on 2 failed: " + err.Error())
}
if err := SetExtension(messageWithInt32Extension1, pb.E_Ext_Number, Int32(23)); err != nil {
panic("SetExtension on Int32-1 failed: " + err.Error())
}
if err := SetExtension(messageWithInt32Extension1, pb.E_Ext_Number, Int32(24)); err != nil {
panic("SetExtension on Int32-2 failed: " + err.Error())
}
}
var EqualTests = []struct {
desc string
a, b Message
exp bool
}{
{"different types", &pb.GoEnum{}, &pb.GoTestField{}, false},
{"equal empty", &pb.GoEnum{}, &pb.GoEnum{}, true},
{"nil vs nil", nil, nil, true},
{"typed nil vs typed nil", (*pb.GoEnum)(nil), (*pb.GoEnum)(nil), true},
{"typed nil vs empty", (*pb.GoEnum)(nil), &pb.GoEnum{}, false},
{"different typed nil", (*pb.GoEnum)(nil), (*pb.GoTestField)(nil), false},
{"one set field, one unset field", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{}, false},
{"one set field zero, one unset field", &pb.GoTest{Param: Int32(0)}, &pb.GoTest{}, false},
{"different set fields", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{Label: String("bar")}, false},
{"equal set", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{Label: String("foo")}, true},
{"repeated, one set", &pb.GoTest{F_Int32Repeated: []int32{2, 3}}, &pb.GoTest{}, false},
{"repeated, different length", &pb.GoTest{F_Int32Repeated: []int32{2, 3}}, &pb.GoTest{F_Int32Repeated: []int32{2}}, false},
{"repeated, different value", &pb.GoTest{F_Int32Repeated: []int32{2}}, &pb.GoTest{F_Int32Repeated: []int32{3}}, false},
{"repeated, equal", &pb.GoTest{F_Int32Repeated: []int32{2, 4}}, &pb.GoTest{F_Int32Repeated: []int32{2, 4}}, true},
{"repeated, nil equal nil", &pb.GoTest{F_Int32Repeated: nil}, &pb.GoTest{F_Int32Repeated: nil}, true},
{"repeated, nil equal empty", &pb.GoTest{F_Int32Repeated: nil}, &pb.GoTest{F_Int32Repeated: []int32{}}, true},
{"repeated, empty equal nil", &pb.GoTest{F_Int32Repeated: []int32{}}, &pb.GoTest{F_Int32Repeated: nil}, true},
{
"nested, different",
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("foo")}},
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("bar")}},
false,
},
{
"nested, equal",
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("wow")}},
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("wow")}},
true,
},
{"bytes", &pb.OtherMessage{Value: []byte("foo")}, &pb.OtherMessage{Value: []byte("foo")}, true},
{"bytes, empty", &pb.OtherMessage{Value: []byte{}}, &pb.OtherMessage{Value: []byte{}}, true},
{"bytes, empty vs nil", &pb.OtherMessage{Value: []byte{}}, &pb.OtherMessage{Value: nil}, false},
{
"repeated bytes",
&pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}},
&pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}},
true,
},
// In proto3, []byte{} and []byte(nil) are equal.
{"proto3 bytes, empty vs nil", &proto3pb.Message{Data: []byte{}}, &proto3pb.Message{Data: nil}, true},
{"extension vs. no extension", messageWithoutExtension, messageWithExtension1a, false},
{"extension vs. same extension", messageWithExtension1a, messageWithExtension1b, true},
{"extension vs. different extension", messageWithExtension1a, messageWithExtension2, false},
{"int32 extension vs. itself", messageWithInt32Extension1, messageWithInt32Extension1, true},
{"int32 extension vs. a different int32", messageWithInt32Extension1, messageWithInt32Extension2, false},
{
"message with group",
&pb.MyMessage{
Count: Int32(1),
Somegroup: &pb.MyMessage_SomeGroup{
GroupField: Int32(5),
},
},
&pb.MyMessage{
Count: Int32(1),
Somegroup: &pb.MyMessage_SomeGroup{
GroupField: Int32(5),
},
},
true,
},
{
"map same",
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
true,
},
{
"map different entry",
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
&pb.MessageWithMap{NameMapping: map[int32]string{2: "Rob"}},
false,
},
{
"map different key only",
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
&pb.MessageWithMap{NameMapping: map[int32]string{2: "Ken"}},
false,
},
{
"map different value only",
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Rob"}},
false,
},
{
"oneof same",
&pb.Communique{Union: &pb.Communique_Number{41}},
&pb.Communique{Union: &pb.Communique_Number{41}},
true,
},
{
"oneof one nil",
&pb.Communique{Union: &pb.Communique_Number{41}},
&pb.Communique{},
false,
},
{
"oneof different",
&pb.Communique{Union: &pb.Communique_Number{41}},
&pb.Communique{Union: &pb.Communique_Name{"Bobby Tables"}},
false,
},
}
func TestEqual(t *testing.T) {
for _, tc := range EqualTests {
if res := Equal(tc.a, tc.b); res != tc.exp {
t.Errorf("%v: Equal(%v, %v) = %v, want %v", tc.desc, tc.a, tc.b, res, tc.exp)
}
}
}

583
vendor/github.com/golang/protobuf/proto/extensions.go generated vendored Normal file
View file

@ -0,0 +1,583 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2010 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// 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.
package proto
/*
* Types and routines for supporting protocol buffer extensions.
*/
import (
"errors"
"fmt"
"reflect"
"strconv"
"sync"
)
// ErrMissingExtension is the error returned by GetExtension if the named extension is not in the message.
var ErrMissingExtension = errors.New("proto: missing extension")
// ExtensionRange represents a range of message extensions for a protocol buffer.
// Used in code generated by the protocol compiler.
type ExtensionRange struct {
Start, End int32 // both inclusive
}
// extendableProto is an interface implemented by any protocol buffer generated by the current
// proto compiler that may be extended.
type extendableProto interface {
Message
ExtensionRangeArray() []ExtensionRange
extensionsWrite() map[int32]Extension
extensionsRead() (map[int32]Extension, sync.Locker)
}
// extendableProtoV1 is an interface implemented by a protocol buffer generated by the previous
// version of the proto compiler that may be extended.
type extendableProtoV1 interface {
Message
ExtensionRangeArray() []ExtensionRange
ExtensionMap() map[int32]Extension
}
// extensionAdapter is a wrapper around extendableProtoV1 that implements extendableProto.
type extensionAdapter struct {
extendableProtoV1
}
func (e extensionAdapter) extensionsWrite() map[int32]Extension {
return e.ExtensionMap()
}
func (e extensionAdapter) extensionsRead() (map[int32]Extension, sync.Locker) {
return e.ExtensionMap(), notLocker{}
}
// notLocker is a sync.Locker whose Lock and Unlock methods are nops.
type notLocker struct{}
func (n notLocker) Lock() {}
func (n notLocker) Unlock() {}
// extendable returns the extendableProto interface for the given generated proto message.
// If the proto message has the old extension format, it returns a wrapper that implements
// the extendableProto interface.
func extendable(p interface{}) (extendableProto, bool) {
if ep, ok := p.(extendableProto); ok {
return ep, ok
}
if ep, ok := p.(extendableProtoV1); ok {
return extensionAdapter{ep}, ok
}
return nil, false
}
// XXX_InternalExtensions is an internal representation of proto extensions.
//
// Each generated message struct type embeds an anonymous XXX_InternalExtensions field,
// thus gaining the unexported 'extensions' method, which can be called only from the proto package.
//
// The methods of XXX_InternalExtensions are not concurrency safe in general,
// but calls to logically read-only methods such as has and get may be executed concurrently.
type XXX_InternalExtensions struct {
// The struct must be indirect so that if a user inadvertently copies a
// generated message and its embedded XXX_InternalExtensions, they
// avoid the mayhem of a copied mutex.
//
// The mutex serializes all logically read-only operations to p.extensionMap.
// It is up to the client to ensure that write operations to p.extensionMap are
// mutually exclusive with other accesses.
p *struct {
mu sync.Mutex
extensionMap map[int32]Extension
}
}
// extensionsWrite returns the extension map, creating it on first use.
func (e *XXX_InternalExtensions) extensionsWrite() map[int32]Extension {
if e.p == nil {
e.p = new(struct {
mu sync.Mutex
extensionMap map[int32]Extension
})
e.p.extensionMap = make(map[int32]Extension)
}
return e.p.extensionMap
}
// extensionsRead returns the extensions map for read-only use. It may be nil.
// The caller must hold the returned mutex's lock when accessing Elements within the map.
func (e *XXX_InternalExtensions) extensionsRead() (map[int32]Extension, sync.Locker) {
if e.p == nil {
return nil, nil
}
return e.p.extensionMap, &e.p.mu
}
var extendableProtoType = reflect.TypeOf((*extendableProto)(nil)).Elem()
var extendableProtoV1Type = reflect.TypeOf((*extendableProtoV1)(nil)).Elem()
// ExtensionDesc represents an extension specification.
// Used in generated code from the protocol compiler.
type ExtensionDesc struct {
ExtendedType Message // nil pointer to the type that is being extended
ExtensionType interface{} // nil pointer to the extension type
Field int32 // field number
Name string // fully-qualified name of extension, for text formatting
Tag string // protobuf tag style
}
func (ed *ExtensionDesc) repeated() bool {
t := reflect.TypeOf(ed.ExtensionType)
return t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
}
// Extension represents an extension in a message.
type Extension struct {
// When an extension is stored in a message using SetExtension
// only desc and value are set. When the message is marshaled
// enc will be set to the encoded form of the message.
//
// When a message is unmarshaled and contains extensions, each
// extension will have only enc set. When such an extension is
// accessed using GetExtension (or GetExtensions) desc and value
// will be set.
desc *ExtensionDesc
value interface{}
enc []byte
}
// SetRawExtension is for testing only.
func SetRawExtension(base Message, id int32, b []byte) {
epb, ok := extendable(base)
if !ok {
return
}
extmap := epb.extensionsWrite()
extmap[id] = Extension{enc: b}
}
// isExtensionField returns true iff the given field number is in an extension range.
func isExtensionField(pb extendableProto, field int32) bool {
for _, er := range pb.ExtensionRangeArray() {
if er.Start <= field && field <= er.End {
return true
}
}
return false
}
// checkExtensionTypes checks that the given extension is valid for pb.
func checkExtensionTypes(pb extendableProto, extension *ExtensionDesc) error {
var pbi interface{} = pb
// Check the extended type.
if ea, ok := pbi.(extensionAdapter); ok {
pbi = ea.extendableProtoV1
}
if a, b := reflect.TypeOf(pbi), reflect.TypeOf(extension.ExtendedType); a != b {
return errors.New("proto: bad extended type; " + b.String() + " does not extend " + a.String())
}
// Check the range.
if !isExtensionField(pb, extension.Field) {
return errors.New("proto: bad extension number; not in declared ranges")
}
return nil
}
// extPropKey is sufficient to uniquely identify an extension.
type extPropKey struct {
base reflect.Type
field int32
}
var extProp = struct {
sync.RWMutex
m map[extPropKey]*Properties
}{
m: make(map[extPropKey]*Properties),
}
func extensionProperties(ed *ExtensionDesc) *Properties {
key := extPropKey{base: reflect.TypeOf(ed.ExtendedType), field: ed.Field}
extProp.RLock()
if prop, ok := extProp.m[key]; ok {
extProp.RUnlock()
return prop
}
extProp.RUnlock()
extProp.Lock()
defer extProp.Unlock()
// Check again.
if prop, ok := extProp.m[key]; ok {
return prop
}
prop := new(Properties)
prop.Init(reflect.TypeOf(ed.ExtensionType), "unknown_name", ed.Tag, nil)
extProp.m[key] = prop
return prop
}
// encode encodes any unmarshaled (unencoded) extensions in e.
func encodeExtensions(e *XXX_InternalExtensions) error {
m, mu := e.extensionsRead()
if m == nil {
return nil // fast path
}
mu.Lock()
defer mu.Unlock()
return encodeExtensionsMap(m)
}
// encode encodes any unmarshaled (unencoded) extensions in e.
func encodeExtensionsMap(m map[int32]Extension) error {
for k, e := range m {
if e.value == nil || e.desc == nil {
// Extension is only in its encoded form.
continue
}
// We don't skip extensions that have an encoded form set,
// because the extension value may have been mutated after
// the last time this function was called.
et := reflect.TypeOf(e.desc.ExtensionType)
props := extensionProperties(e.desc)
p := NewBuffer(nil)
// If e.value has type T, the encoder expects a *struct{ X T }.
// Pass a *T with a zero field and hope it all works out.
x := reflect.New(et)
x.Elem().Set(reflect.ValueOf(e.value))
if err := props.enc(p, props, toStructPointer(x)); err != nil {
return err
}
e.enc = p.buf
m[k] = e
}
return nil
}
func extensionsSize(e *XXX_InternalExtensions) (n int) {
m, mu := e.extensionsRead()
if m == nil {
return 0
}
mu.Lock()
defer mu.Unlock()
return extensionsMapSize(m)
}
func extensionsMapSize(m map[int32]Extension) (n int) {
for _, e := range m {
if e.value == nil || e.desc == nil {
// Extension is only in its encoded form.
n += len(e.enc)
continue
}
// We don't skip extensions that have an encoded form set,
// because the extension value may have been mutated after
// the last time this function was called.
et := reflect.TypeOf(e.desc.ExtensionType)
props := extensionProperties(e.desc)
// If e.value has type T, the encoder expects a *struct{ X T }.
// Pass a *T with a zero field and hope it all works out.
x := reflect.New(et)
x.Elem().Set(reflect.ValueOf(e.value))
n += props.size(props, toStructPointer(x))
}
return
}
// HasExtension returns whether the given extension is present in pb.
func HasExtension(pb Message, extension *ExtensionDesc) bool {
// TODO: Check types, field numbers, etc.?
epb, ok := extendable(pb)
if !ok {
return false
}
extmap, mu := epb.extensionsRead()
if extmap == nil {
return false
}
mu.Lock()
_, ok = extmap[extension.Field]
mu.Unlock()
return ok
}
// ClearExtension removes the given extension from pb.
func ClearExtension(pb Message, extension *ExtensionDesc) {
epb, ok := extendable(pb)
if !ok {
return
}
// TODO: Check types, field numbers, etc.?
extmap := epb.extensionsWrite()
delete(extmap, extension.Field)
}
// GetExtension parses and returns the given extension of pb.
// If the extension is not present and has no default value it returns ErrMissingExtension.
func GetExtension(pb Message, extension *ExtensionDesc) (interface{}, error) {
epb, ok := extendable(pb)
if !ok {
return nil, errors.New("proto: not an extendable proto")
}
if err := checkExtensionTypes(epb, extension); err != nil {
return nil, err
}
emap, mu := epb.extensionsRead()
if emap == nil {
return defaultExtensionValue(extension)
}
mu.Lock()
defer mu.Unlock()
e, ok := emap[extension.Field]
if !ok {
// defaultExtensionValue returns the default value or
// ErrMissingExtension if there is no default.
return defaultExtensionValue(extension)
}
if e.value != nil {
// Already decoded. Check the descriptor, though.
if e.desc != extension {
// This shouldn't happen. If it does, it means that
// GetExtension was called twice with two different
// descriptors with the same field number.
return nil, errors.New("proto: descriptor conflict")
}
return e.value, nil
}
v, err := decodeExtension(e.enc, extension)
if err != nil {
return nil, err
}
// Remember the decoded version and drop the encoded version.
// That way it is safe to mutate what we return.
e.value = v
e.desc = extension
e.enc = nil
emap[extension.Field] = e
return e.value, nil
}
// defaultExtensionValue returns the default value for extension.
// If no default for an extension is defined ErrMissingExtension is returned.
func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) {
t := reflect.TypeOf(extension.ExtensionType)
props := extensionProperties(extension)
sf, _, err := fieldDefault(t, props)
if err != nil {
return nil, err
}
if sf == nil || sf.value == nil {
// There is no default value.
return nil, ErrMissingExtension
}
if t.Kind() != reflect.Ptr {
// We do not need to return a Ptr, we can directly return sf.value.
return sf.value, nil
}
// We need to return an interface{} that is a pointer to sf.value.
value := reflect.New(t).Elem()
value.Set(reflect.New(value.Type().Elem()))
if sf.kind == reflect.Int32 {
// We may have an int32 or an enum, but the underlying data is int32.
// Since we can't set an int32 into a non int32 reflect.value directly
// set it as a int32.
value.Elem().SetInt(int64(sf.value.(int32)))
} else {
value.Elem().Set(reflect.ValueOf(sf.value))
}
return value.Interface(), nil
}
// decodeExtension decodes an extension encoded in b.
func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) {
o := NewBuffer(b)
t := reflect.TypeOf(extension.ExtensionType)
props := extensionProperties(extension)
// t is a pointer to a struct, pointer to basic type or a slice.
// Allocate a "field" to store the pointer/slice itself; the
// pointer/slice will be stored here. We pass
// the address of this field to props.dec.
// This passes a zero field and a *t and lets props.dec
// interpret it as a *struct{ x t }.
value := reflect.New(t).Elem()
for {
// Discard wire type and field number varint. It isn't needed.
if _, err := o.DecodeVarint(); err != nil {
return nil, err
}
if err := props.dec(o, props, toStructPointer(value.Addr())); err != nil {
return nil, err
}
if o.index >= len(o.buf) {
break
}
}
return value.Interface(), nil
}
// GetExtensions returns a slice of the extensions present in pb that are also listed in es.
// The returned slice has the same length as es; missing extensions will appear as nil elements.
func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, err error) {
epb, ok := extendable(pb)
if !ok {
return nil, errors.New("proto: not an extendable proto")
}
extensions = make([]interface{}, len(es))
for i, e := range es {
extensions[i], err = GetExtension(epb, e)
if err == ErrMissingExtension {
err = nil
}
if err != nil {
return
}
}
return
}
// ExtensionDescs returns a new slice containing pb's extension descriptors, in undefined order.
// For non-registered extensions, ExtensionDescs returns an incomplete descriptor containing
// just the Field field, which defines the extension's field number.
func ExtensionDescs(pb Message) ([]*ExtensionDesc, error) {
epb, ok := extendable(pb)
if !ok {
return nil, fmt.Errorf("proto: %T is not an extendable proto.Message", pb)
}
registeredExtensions := RegisteredExtensions(pb)
emap, mu := epb.extensionsRead()
mu.Lock()
defer mu.Unlock()
extensions := make([]*ExtensionDesc, 0, len(emap))
for extid, e := range emap {
desc := e.desc
if desc == nil {
desc = registeredExtensions[extid]
if desc == nil {
desc = &ExtensionDesc{Field: extid}
}
}
extensions = append(extensions, desc)
}
return extensions, nil
}
// SetExtension sets the specified extension of pb to the specified value.
func SetExtension(pb Message, extension *ExtensionDesc, value interface{}) error {
epb, ok := extendable(pb)
if !ok {
return errors.New("proto: not an extendable proto")
}
if err := checkExtensionTypes(epb, extension); err != nil {
return err
}
typ := reflect.TypeOf(extension.ExtensionType)
if typ != reflect.TypeOf(value) {
return errors.New("proto: bad extension value type")
}
// nil extension values need to be caught early, because the
// encoder can't distinguish an ErrNil due to a nil extension
// from an ErrNil due to a missing field. Extensions are
// always optional, so the encoder would just swallow the error
// and drop all the extensions from the encoded message.
if reflect.ValueOf(value).IsNil() {
return fmt.Errorf("proto: SetExtension called with nil value of type %T", value)
}
extmap := epb.extensionsWrite()
extmap[extension.Field] = Extension{desc: extension, value: value}
return nil
}
// ClearAllExtensions clears all extensions from pb.
func ClearAllExtensions(pb Message) {
epb, ok := extendable(pb)
if !ok {
return
}
m := epb.extensionsWrite()
for k := range m {
delete(m, k)
}
}
// A global registry of extensions.
// The generated code will register the generated descriptors by calling RegisterExtension.
var extensionMaps = make(map[reflect.Type]map[int32]*ExtensionDesc)
// RegisterExtension is called from the generated code.
func RegisterExtension(desc *ExtensionDesc) {
st := reflect.TypeOf(desc.ExtendedType).Elem()
m := extensionMaps[st]
if m == nil {
m = make(map[int32]*ExtensionDesc)
extensionMaps[st] = m
}
if _, ok := m[desc.Field]; ok {
panic("proto: duplicate extension registered: " + st.String() + " " + strconv.Itoa(int(desc.Field)))
}
m[desc.Field] = desc
}
// RegisteredExtensions returns a map of the registered extensions of a
// protocol buffer struct, indexed by the extension number.
// The argument pb should be a nil pointer to the struct type.
func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc {
return extensionMaps[reflect.TypeOf(pb).Elem()]
}

View file

@ -0,0 +1,504 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2014 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// 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.
package proto_test
import (
"bytes"
"fmt"
"reflect"
"sort"
"testing"
"github.com/golang/protobuf/proto"
pb "github.com/golang/protobuf/proto/testdata"
)
func TestGetExtensionsWithMissingExtensions(t *testing.T) {
msg := &pb.MyMessage{}
ext1 := &pb.Ext{}
if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil {
t.Fatalf("Could not set ext1: %s", err)
}
exts, err := proto.GetExtensions(msg, []*proto.ExtensionDesc{
pb.E_Ext_More,
pb.E_Ext_Text,
})
if err != nil {
t.Fatalf("GetExtensions() failed: %s", err)
}
if exts[0] != ext1 {
t.Errorf("ext1 not in returned extensions: %T %v", exts[0], exts[0])
}
if exts[1] != nil {
t.Errorf("ext2 in returned extensions: %T %v", exts[1], exts[1])
}
}
func TestExtensionDescsWithMissingExtensions(t *testing.T) {
msg := &pb.MyMessage{Count: proto.Int32(0)}
extdesc1 := pb.E_Ext_More
ext1 := &pb.Ext{}
if err := proto.SetExtension(msg, extdesc1, ext1); err != nil {
t.Fatalf("Could not set ext1: %s", err)
}
extdesc2 := &proto.ExtensionDesc{
ExtendedType: (*pb.MyMessage)(nil),
ExtensionType: (*bool)(nil),
Field: 123456789,
Name: "a.b",
Tag: "varint,123456789,opt",
}
ext2 := proto.Bool(false)
if err := proto.SetExtension(msg, extdesc2, ext2); err != nil {
t.Fatalf("Could not set ext2: %s", err)
}
b, err := proto.Marshal(msg)
if err != nil {
t.Fatalf("Could not marshal msg: %v", err)
}
if err := proto.Unmarshal(b, msg); err != nil {
t.Fatalf("Could not unmarshal into msg: %v", err)
}
descs, err := proto.ExtensionDescs(msg)
if err != nil {
t.Fatalf("proto.ExtensionDescs: got error %v", err)
}
sortExtDescs(descs)
wantDescs := []*proto.ExtensionDesc{extdesc1, &proto.ExtensionDesc{Field: extdesc2.Field}}
if !reflect.DeepEqual(descs, wantDescs) {
t.Errorf("proto.ExtensionDescs(msg) sorted extension ids: got %+v, want %+v", descs, wantDescs)
}
}
type ExtensionDescSlice []*proto.ExtensionDesc
func (s ExtensionDescSlice) Len() int { return len(s) }
func (s ExtensionDescSlice) Less(i, j int) bool { return s[i].Field < s[j].Field }
func (s ExtensionDescSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func sortExtDescs(s []*proto.ExtensionDesc) {
sort.Sort(ExtensionDescSlice(s))
}
func TestGetExtensionStability(t *testing.T) {
check := func(m *pb.MyMessage) bool {
ext1, err := proto.GetExtension(m, pb.E_Ext_More)
if err != nil {
t.Fatalf("GetExtension() failed: %s", err)
}
ext2, err := proto.GetExtension(m, pb.E_Ext_More)
if err != nil {
t.Fatalf("GetExtension() failed: %s", err)
}
return ext1 == ext2
}
msg := &pb.MyMessage{Count: proto.Int32(4)}
ext0 := &pb.Ext{}
if err := proto.SetExtension(msg, pb.E_Ext_More, ext0); err != nil {
t.Fatalf("Could not set ext1: %s", ext0)
}
if !check(msg) {
t.Errorf("GetExtension() not stable before marshaling")
}
bb, err := proto.Marshal(msg)
if err != nil {
t.Fatalf("Marshal() failed: %s", err)
}
msg1 := &pb.MyMessage{}
err = proto.Unmarshal(bb, msg1)
if err != nil {
t.Fatalf("Unmarshal() failed: %s", err)
}
if !check(msg1) {
t.Errorf("GetExtension() not stable after unmarshaling")
}
}
func TestGetExtensionDefaults(t *testing.T) {
var setFloat64 float64 = 1
var setFloat32 float32 = 2
var setInt32 int32 = 3
var setInt64 int64 = 4
var setUint32 uint32 = 5
var setUint64 uint64 = 6
var setBool = true
var setBool2 = false
var setString = "Goodnight string"
var setBytes = []byte("Goodnight bytes")
var setEnum = pb.DefaultsMessage_TWO
type testcase struct {
ext *proto.ExtensionDesc // Extension we are testing.
want interface{} // Expected value of extension, or nil (meaning that GetExtension will fail).
def interface{} // Expected value of extension after ClearExtension().
}
tests := []testcase{
{pb.E_NoDefaultDouble, setFloat64, nil},
{pb.E_NoDefaultFloat, setFloat32, nil},
{pb.E_NoDefaultInt32, setInt32, nil},
{pb.E_NoDefaultInt64, setInt64, nil},
{pb.E_NoDefaultUint32, setUint32, nil},
{pb.E_NoDefaultUint64, setUint64, nil},
{pb.E_NoDefaultSint32, setInt32, nil},
{pb.E_NoDefaultSint64, setInt64, nil},
{pb.E_NoDefaultFixed32, setUint32, nil},
{pb.E_NoDefaultFixed64, setUint64, nil},
{pb.E_NoDefaultSfixed32, setInt32, nil},
{pb.E_NoDefaultSfixed64, setInt64, nil},
{pb.E_NoDefaultBool, setBool, nil},
{pb.E_NoDefaultBool, setBool2, nil},
{pb.E_NoDefaultString, setString, nil},
{pb.E_NoDefaultBytes, setBytes, nil},
{pb.E_NoDefaultEnum, setEnum, nil},
{pb.E_DefaultDouble, setFloat64, float64(3.1415)},
{pb.E_DefaultFloat, setFloat32, float32(3.14)},
{pb.E_DefaultInt32, setInt32, int32(42)},
{pb.E_DefaultInt64, setInt64, int64(43)},
{pb.E_DefaultUint32, setUint32, uint32(44)},
{pb.E_DefaultUint64, setUint64, uint64(45)},
{pb.E_DefaultSint32, setInt32, int32(46)},
{pb.E_DefaultSint64, setInt64, int64(47)},
{pb.E_DefaultFixed32, setUint32, uint32(48)},
{pb.E_DefaultFixed64, setUint64, uint64(49)},
{pb.E_DefaultSfixed32, setInt32, int32(50)},
{pb.E_DefaultSfixed64, setInt64, int64(51)},
{pb.E_DefaultBool, setBool, true},
{pb.E_DefaultBool, setBool2, true},
{pb.E_DefaultString, setString, "Hello, string"},
{pb.E_DefaultBytes, setBytes, []byte("Hello, bytes")},
{pb.E_DefaultEnum, setEnum, pb.DefaultsMessage_ONE},
}
checkVal := func(test testcase, msg *pb.DefaultsMessage, valWant interface{}) error {
val, err := proto.GetExtension(msg, test.ext)
if err != nil {
if valWant != nil {
return fmt.Errorf("GetExtension(): %s", err)
}
if want := proto.ErrMissingExtension; err != want {
return fmt.Errorf("Unexpected error: got %v, want %v", err, want)
}
return nil
}
// All proto2 extension values are either a pointer to a value or a slice of values.
ty := reflect.TypeOf(val)
tyWant := reflect.TypeOf(test.ext.ExtensionType)
if got, want := ty, tyWant; got != want {
return fmt.Errorf("unexpected reflect.TypeOf(): got %v want %v", got, want)
}
tye := ty.Elem()
tyeWant := tyWant.Elem()
if got, want := tye, tyeWant; got != want {
return fmt.Errorf("unexpected reflect.TypeOf().Elem(): got %v want %v", got, want)
}
// Check the name of the type of the value.
// If it is an enum it will be type int32 with the name of the enum.
if got, want := tye.Name(), tye.Name(); got != want {
return fmt.Errorf("unexpected reflect.TypeOf().Elem().Name(): got %v want %v", got, want)
}
// Check that value is what we expect.
// If we have a pointer in val, get the value it points to.
valExp := val
if ty.Kind() == reflect.Ptr {
valExp = reflect.ValueOf(val).Elem().Interface()
}
if got, want := valExp, valWant; !reflect.DeepEqual(got, want) {
return fmt.Errorf("unexpected reflect.DeepEqual(): got %v want %v", got, want)
}
return nil
}
setTo := func(test testcase) interface{} {
setTo := reflect.ValueOf(test.want)
if typ := reflect.TypeOf(test.ext.ExtensionType); typ.Kind() == reflect.Ptr {
setTo = reflect.New(typ).Elem()
setTo.Set(reflect.New(setTo.Type().Elem()))
setTo.Elem().Set(reflect.ValueOf(test.want))
}
return setTo.Interface()
}
for _, test := range tests {
msg := &pb.DefaultsMessage{}
name := test.ext.Name
// Check the initial value.
if err := checkVal(test, msg, test.def); err != nil {
t.Errorf("%s: %v", name, err)
}
// Set the per-type value and check value.
name = fmt.Sprintf("%s (set to %T %v)", name, test.want, test.want)
if err := proto.SetExtension(msg, test.ext, setTo(test)); err != nil {
t.Errorf("%s: SetExtension(): %v", name, err)
continue
}
if err := checkVal(test, msg, test.want); err != nil {
t.Errorf("%s: %v", name, err)
continue
}
// Set and check the value.
name += " (cleared)"
proto.ClearExtension(msg, test.ext)
if err := checkVal(test, msg, test.def); err != nil {
t.Errorf("%s: %v", name, err)
}
}
}
func TestExtensionsRoundTrip(t *testing.T) {
msg := &pb.MyMessage{}
ext1 := &pb.Ext{
Data: proto.String("hi"),
}
ext2 := &pb.Ext{
Data: proto.String("there"),
}
exists := proto.HasExtension(msg, pb.E_Ext_More)
if exists {
t.Error("Extension More present unexpectedly")
}
if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil {
t.Error(err)
}
if err := proto.SetExtension(msg, pb.E_Ext_More, ext2); err != nil {
t.Error(err)
}
e, err := proto.GetExtension(msg, pb.E_Ext_More)
if err != nil {
t.Error(err)
}
x, ok := e.(*pb.Ext)
if !ok {
t.Errorf("e has type %T, expected testdata.Ext", e)
} else if *x.Data != "there" {
t.Errorf("SetExtension failed to overwrite, got %+v, not 'there'", x)
}
proto.ClearExtension(msg, pb.E_Ext_More)
if _, err = proto.GetExtension(msg, pb.E_Ext_More); err != proto.ErrMissingExtension {
t.Errorf("got %v, expected ErrMissingExtension", e)
}
if _, err := proto.GetExtension(msg, pb.E_X215); err == nil {
t.Error("expected bad extension error, got nil")
}
if err := proto.SetExtension(msg, pb.E_X215, 12); err == nil {
t.Error("expected extension err")
}
if err := proto.SetExtension(msg, pb.E_Ext_More, 12); err == nil {
t.Error("expected some sort of type mismatch error, got nil")
}
}
func TestNilExtension(t *testing.T) {
msg := &pb.MyMessage{
Count: proto.Int32(1),
}
if err := proto.SetExtension(msg, pb.E_Ext_Text, proto.String("hello")); err != nil {
t.Fatal(err)
}
if err := proto.SetExtension(msg, pb.E_Ext_More, (*pb.Ext)(nil)); err == nil {
t.Error("expected SetExtension to fail due to a nil extension")
} else if want := "proto: SetExtension called with nil value of type *testdata.Ext"; err.Error() != want {
t.Errorf("expected error %v, got %v", want, err)
}
// Note: if the behavior of Marshal is ever changed to ignore nil extensions, update
// this test to verify that E_Ext_Text is properly propagated through marshal->unmarshal.
}
func TestMarshalUnmarshalRepeatedExtension(t *testing.T) {
// Add a repeated extension to the result.
tests := []struct {
name string
ext []*pb.ComplexExtension
}{
{
"two fields",
[]*pb.ComplexExtension{
{First: proto.Int32(7)},
{Second: proto.Int32(11)},
},
},
{
"repeated field",
[]*pb.ComplexExtension{
{Third: []int32{1000}},
{Third: []int32{2000}},
},
},
{
"two fields and repeated field",
[]*pb.ComplexExtension{
{Third: []int32{1000}},
{First: proto.Int32(9)},
{Second: proto.Int32(21)},
{Third: []int32{2000}},
},
},
}
for _, test := range tests {
// Marshal message with a repeated extension.
msg1 := new(pb.OtherMessage)
err := proto.SetExtension(msg1, pb.E_RComplex, test.ext)
if err != nil {
t.Fatalf("[%s] Error setting extension: %v", test.name, err)
}
b, err := proto.Marshal(msg1)
if err != nil {
t.Fatalf("[%s] Error marshaling message: %v", test.name, err)
}
// Unmarshal and read the merged proto.
msg2 := new(pb.OtherMessage)
err = proto.Unmarshal(b, msg2)
if err != nil {
t.Fatalf("[%s] Error unmarshaling message: %v", test.name, err)
}
e, err := proto.GetExtension(msg2, pb.E_RComplex)
if err != nil {
t.Fatalf("[%s] Error getting extension: %v", test.name, err)
}
ext := e.([]*pb.ComplexExtension)
if ext == nil {
t.Fatalf("[%s] Invalid extension", test.name)
}
if !reflect.DeepEqual(ext, test.ext) {
t.Errorf("[%s] Wrong value for ComplexExtension: got: %v want: %v\n", test.name, ext, test.ext)
}
}
}
func TestUnmarshalRepeatingNonRepeatedExtension(t *testing.T) {
// We may see multiple instances of the same extension in the wire
// format. For example, the proto compiler may encode custom options in
// this way. Here, we verify that we merge the extensions together.
tests := []struct {
name string
ext []*pb.ComplexExtension
}{
{
"two fields",
[]*pb.ComplexExtension{
{First: proto.Int32(7)},
{Second: proto.Int32(11)},
},
},
{
"repeated field",
[]*pb.ComplexExtension{
{Third: []int32{1000}},
{Third: []int32{2000}},
},
},
{
"two fields and repeated field",
[]*pb.ComplexExtension{
{Third: []int32{1000}},
{First: proto.Int32(9)},
{Second: proto.Int32(21)},
{Third: []int32{2000}},
},
},
}
for _, test := range tests {
var buf bytes.Buffer
var want pb.ComplexExtension
// Generate a serialized representation of a repeated extension
// by catenating bytes together.
for i, e := range test.ext {
// Merge to create the wanted proto.
proto.Merge(&want, e)
// serialize the message
msg := new(pb.OtherMessage)
err := proto.SetExtension(msg, pb.E_Complex, e)
if err != nil {
t.Fatalf("[%s] Error setting extension %d: %v", test.name, i, err)
}
b, err := proto.Marshal(msg)
if err != nil {
t.Fatalf("[%s] Error marshaling message %d: %v", test.name, i, err)
}
buf.Write(b)
}
// Unmarshal and read the merged proto.
msg2 := new(pb.OtherMessage)
err := proto.Unmarshal(buf.Bytes(), msg2)
if err != nil {
t.Fatalf("[%s] Error unmarshaling message: %v", test.name, err)
}
e, err := proto.GetExtension(msg2, pb.E_Complex)
if err != nil {
t.Fatalf("[%s] Error getting extension: %v", test.name, err)
}
ext := e.(*pb.ComplexExtension)
if ext == nil {
t.Fatalf("[%s] Invalid extension", test.name)
}
if !reflect.DeepEqual(*ext, want) {
t.Errorf("[%s] Wrong value for ComplexExtension: got: %s want: %s\n", test.name, ext, want)
}
}
}
func TestClearAllExtensions(t *testing.T) {
// unregistered extension
desc := &proto.ExtensionDesc{
ExtendedType: (*pb.MyMessage)(nil),
ExtensionType: (*bool)(nil),
Field: 101010100,
Name: "emptyextension",
Tag: "varint,0,opt",
}
m := &pb.MyMessage{}
if proto.HasExtension(m, desc) {
t.Errorf("proto.HasExtension(%s): got true, want false", proto.MarshalTextString(m))
}
if err := proto.SetExtension(m, desc, proto.Bool(true)); err != nil {
t.Errorf("proto.SetExtension(m, desc, true): got error %q, want nil", err)
}
if !proto.HasExtension(m, desc) {
t.Errorf("proto.HasExtension(%s): got false, want true", proto.MarshalTextString(m))
}
proto.ClearAllExtensions(m)
if proto.HasExtension(m, desc) {
t.Errorf("proto.HasExtension(%s): got true, want false", proto.MarshalTextString(m))
}
}

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