initial commit
This commit is contained in:
commit
cab271f304
1438 changed files with 335968 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
bin
|
||||||
|
dist
|
55
Makefile
Normal file
55
Makefile
Normal 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
126
cmd/poke/config.go
Normal 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
1
cmd/poke/init.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package main
|
28
cmd/poke/poke.go
Normal file
28
cmd/poke/poke.go
Normal 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
111
cmd/poke/serve.go
Normal 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
19
cmd/poke/version.go
Normal 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
24
cmd/pokectl/pokectl.go
Normal 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
32
connector/connector.go
Normal 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
177
connector/github/github.go
Normal 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
59
connector/ldap/ldap.go
Normal 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
|
||||||
|
}
|
53
connector/mock/connectortest.go
Normal file
53
connector/mock/connectortest.go
Normal 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
2
connector/oidc/oidc.go
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package oidc implements logging in through OpenID Connect providers.
|
||||||
|
package oidc
|
9
example/client.yaml
Normal file
9
example/client.yaml
Normal 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
20
example/config.yaml
Normal 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
|
48
example/thirdpartyresources.yaml
Normal file
48
example/thirdpartyresources.yaml
Normal 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
67
glide.lock
generated
Normal 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
63
glide.yaml
Normal 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
112
glide_test.go
Normal 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
2
server/doc.go
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package server implements an OpenID Connect server with federated logins.
|
||||||
|
package server
|
556
server/handlers.go
Normal file
556
server/handlers.go
Normal 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
1
server/handlers_test.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package server
|
339
server/oauth2.go
Normal file
339
server/oauth2.go
Normal 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
1
server/oauth2_test.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package server
|
165
server/rotation.go
Normal file
165
server/rotation.go
Normal 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
1
server/rotation_test.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package server
|
141
server/server.go
Normal file
141
server/server.go
Normal 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
221
server/server_test.go
Normal 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
101
server/templates.go
Normal 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
1
server/templates_test.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package server
|
2
storage/doc.go
Normal file
2
storage/doc.go
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package storage defines the storage interface and types used by the server.
|
||||||
|
package storage
|
363
storage/kubernetes/client.go
Normal file
363
storage/kubernetes/client.go
Normal 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
|
||||||
|
}
|
2
storage/kubernetes/doc.go
Normal file
2
storage/kubernetes/doc.go
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package kubernetes provides a storage interface using Kubernetes third party APIs.
|
||||||
|
package kubernetes
|
29
storage/kubernetes/garbage_collection.go
Normal file
29
storage/kubernetes/garbage_collection.go
Normal 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
|
||||||
|
}
|
140
storage/kubernetes/k8sapi/client.go
Normal file
140
storage/kubernetes/k8sapi/client.go
Normal 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"`
|
||||||
|
}
|
2
storage/kubernetes/k8sapi/doc.go
Normal file
2
storage/kubernetes/k8sapi/doc.go
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Package k8sapi holds vendored Kubernetes types.
|
||||||
|
package k8sapi
|
49
storage/kubernetes/k8sapi/extensions.go
Normal file
49
storage/kubernetes/k8sapi/extensions.go
Normal 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"`
|
||||||
|
}
|
138
storage/kubernetes/k8sapi/time.go
Normal file
138
storage/kubernetes/k8sapi/time.go
Normal 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
|
||||||
|
}
|
52
storage/kubernetes/k8sapi/unversioned.go
Normal file
52
storage/kubernetes/k8sapi/unversioned.go
Normal 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"`
|
||||||
|
}
|
162
storage/kubernetes/k8sapi/v1.go
Normal file
162
storage/kubernetes/k8sapi/v1.go
Normal 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"`
|
||||||
|
}
|
234
storage/kubernetes/storage.go
Normal file
234
storage/kubernetes/storage.go
Normal 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)
|
||||||
|
}
|
78
storage/kubernetes/storage_test.go
Normal file
78
storage/kubernetes/storage_test.go
Normal 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
309
storage/kubernetes/types.go
Normal 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
244
storage/memory/memory.go
Normal 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
|
||||||
|
}
|
12
storage/memory/memory_test.go
Normal file
12
storage/memory/memory_test.go
Normal 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
263
storage/storage.go
Normal 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()
|
||||||
|
}
|
84
storage/storagetest/storagetest.go
Normal file
84
storage/storagetest/storagetest.go
Normal 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
202
vendor/github.com/ericchiang/oidc/LICENSE
generated
vendored
Normal 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
99
vendor/github.com/ericchiang/oidc/README.md
generated
vendored
Normal 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
145
vendor/github.com/ericchiang/oidc/doc.go
generated
vendored
Normal 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
15
vendor/github.com/ericchiang/oidc/examples/README.md
generated
vendored
Normal 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"
|
86
vendor/github.com/ericchiang/oidc/examples/idtoken/app.go
generated
vendored
Normal file
86
vendor/github.com/ericchiang/oidc/examples/idtoken/app.go
generated
vendored
Normal 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))
|
||||||
|
}
|
99
vendor/github.com/ericchiang/oidc/examples/nonce/app.go
generated
vendored
Normal file
99
vendor/github.com/ericchiang/oidc/examples/nonce/app.go
generated
vendored
Normal 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))
|
||||||
|
}
|
76
vendor/github.com/ericchiang/oidc/examples/userinfo/app.go
generated
vendored
Normal file
76
vendor/github.com/ericchiang/oidc/examples/userinfo/app.go
generated
vendored
Normal 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
7
vendor/github.com/ericchiang/oidc/internal/oidc.go
generated
vendored
Normal 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
188
vendor/github.com/ericchiang/oidc/jwks.go
generated
vendored
Normal 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
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
43
vendor/github.com/ericchiang/oidc/nonce.go
generated
vendored
Normal 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
236
vendor/github.com/ericchiang/oidc/oidc.go
generated
vendored
Normal 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
54
vendor/github.com/ericchiang/oidc/oidc_test.go
generated
vendored
Normal 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
283
vendor/github.com/ericchiang/oidc/oidcproxy/main.go
generated
vendored
Normal 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
72
vendor/github.com/ericchiang/oidc/oidcproxy/nonce.go
generated
vendored
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_1.pem
generated
vendored
Normal file
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_1.pem
generated
vendored
Normal 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-----
|
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_2.pem
generated
vendored
Normal file
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_2.pem
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MIHcAgEBBEIAtlsqZlQW4bWtSmDgLjbhcCgmmWEVwtzHMTZRoQnUv4rdTxCeKLY3
|
||||||
|
hjxzd5hPCmtfP68GyJhKQgKofKYD/DgQLc2gBwYFK4EEACOhgYkDgYYABAABrcbo
|
||||||
|
t8KEfgzslg4Bb7t0khCgFrT2hX5htSWnwwHiScs1yO9egRcftZg/WAoIo/QDID+i
|
||||||
|
OB4f5Flg5PygZpm/SwE4B1E8dGpyLRmBg3cC0/PfuRkGZ2E5POKZqsiRU5TkvC2D
|
||||||
|
AkwVr6UPpXPheStrp2qh6ptBUtZRzn8Q4lVFKoKe9g==
|
||||||
|
-----END EC PRIVATE KEY-----
|
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_3.pem
generated
vendored
Normal file
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_3.pem
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MIHcAgEBBEIAzUq62J5hiC8B5xw9M/e9KlSeO66uq9PHRSGcFY4d9MFgFKILKU0u
|
||||||
|
cfUBCwbhOnDWkdUTp1DkLWeNhE0UUvN2FgegBwYFK4EEACOhgYkDgYYABAHH7ZyR
|
||||||
|
YXJ9oDJ/KohiQFFXqkvspk3ljvBAFUFRyfL4Q40TtgKGt5YBNmU3SHNHn3fJdjQy
|
||||||
|
xe2OZcmKYxzwCjx6mgACI0IoiIdgZN0RBy8UMhgn810C/iDg+nOZScl7P0t3DFcv
|
||||||
|
H3K5+tkVWe8lLBIUOkqEyHmmfHGYcn6Kc5jHEnAebA==
|
||||||
|
-----END EC PRIVATE KEY-----
|
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_4.pem
generated
vendored
Normal file
7
vendor/github.com/ericchiang/oidc/testdata/ecdsa_521_4.pem
generated
vendored
Normal 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
15
vendor/github.com/ericchiang/oidc/testdata/gen.sh
generated
vendored
Executable 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
|
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_1.pem
generated
vendored
Normal file
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_1.pem
generated
vendored
Normal 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-----
|
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_2.pem
generated
vendored
Normal file
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_2.pem
generated
vendored
Normal 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-----
|
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_3.pem
generated
vendored
Normal file
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_3.pem
generated
vendored
Normal 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-----
|
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_4.pem
generated
vendored
Normal file
27
vendor/github.com/ericchiang/oidc/testdata/rsa_2048_4.pem
generated
vendored
Normal 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-----
|
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_1.pem
generated
vendored
Normal file
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_1.pem
generated
vendored
Normal 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-----
|
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_2.pem
generated
vendored
Normal file
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_2.pem
generated
vendored
Normal 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-----
|
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_3.pem
generated
vendored
Normal file
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_3.pem
generated
vendored
Normal 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-----
|
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_4.pem
generated
vendored
Normal file
51
vendor/github.com/ericchiang/oidc/testdata/rsa_4096_4.pem
generated
vendored
Normal 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
15
vendor/github.com/golang/protobuf/.gitignore
generated
vendored
Normal 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
3
vendor/github.com/golang/protobuf/AUTHORS
generated
vendored
Normal 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
3
vendor/github.com/golang/protobuf/CONTRIBUTORS
generated
vendored
Normal 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
31
vendor/github.com/golang/protobuf/LICENSE
generated
vendored
Normal 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
40
vendor/github.com/golang/protobuf/Make.protobuf
generated
vendored
Normal 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
54
vendor/github.com/golang/protobuf/Makefile
generated
vendored
Normal 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
199
vendor/github.com/golang/protobuf/README.md
generated
vendored
Normal 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
832
vendor/github.com/golang/protobuf/jsonpb/jsonpb.go
generated
vendored
Normal 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
557
vendor/github.com/golang/protobuf/jsonpb/jsonpb_test.go
generated
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/Makefile
generated
vendored
Normal file
33
vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/Makefile
generated
vendored
Normal 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
|
200
vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/more_test_objects.pb.go
generated
vendored
Normal file
200
vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/more_test_objects.pb.go
generated
vendored
Normal 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,
|
||||||
|
}
|
57
vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/more_test_objects.proto
generated
vendored
Normal file
57
vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/more_test_objects.proto
generated
vendored
Normal 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;
|
||||||
|
}
|
739
vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/test_objects.pb.go
generated
vendored
Normal file
739
vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/test_objects.pb.go
generated
vendored
Normal 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,
|
||||||
|
}
|
134
vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/test_objects.proto
generated
vendored
Normal file
134
vendor/github.com/golang/protobuf/jsonpb/jsonpb_test_proto/test_objects.proto
generated
vendored
Normal 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
43
vendor/github.com/golang/protobuf/proto/Makefile
generated
vendored
Normal 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
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
272
vendor/github.com/golang/protobuf/proto/any_test.go
generated
vendored
Normal 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
229
vendor/github.com/golang/protobuf/proto/clone.go
generated
vendored
Normal 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
300
vendor/github.com/golang/protobuf/proto/clone_test.go
generated
vendored
Normal 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
874
vendor/github.com/golang/protobuf/proto/decode.go
generated
vendored
Normal 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
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
296
vendor/github.com/golang/protobuf/proto/equal.go
generated
vendored
Normal 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
212
vendor/github.com/golang/protobuf/proto/equal_test.go
generated
vendored
Normal 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
583
vendor/github.com/golang/protobuf/proto/extensions.go
generated
vendored
Normal 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()]
|
||||||
|
}
|
504
vendor/github.com/golang/protobuf/proto/extensions_test.go
generated
vendored
Normal file
504
vendor/github.com/golang/protobuf/proto/extensions_test.go
generated
vendored
Normal 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
Reference in a new issue