Merge pull request #728 from ericchiang/document-managing-dependencies

Document managing dependencies
This commit is contained in:
rithu leena john 2016-12-07 14:04:22 -08:00 committed by GitHub
commit 9e6d8689c3
1133 changed files with 62 additions and 249966 deletions

View file

@ -0,0 +1,36 @@
# Managing dependencies
Dex uses [glide][glide] and [glide-vc][glide-vc] to manage its [`vendor` directory][go-vendor]. A recent version of these are preferred but dex doesn't require any bleeding edge features. Either install these tools using `go get` or take an opportunity to update to a more recent version.
```
go get -u github.com/Masterminds/glide
go get -u github.com/sgotti/glide-vc
```
To add a new dependency to dex or update an existing one:
* Make changes to dex's source code importing the new dependency.
* Edit `glide.yaml` to include the new dependency at a given commit SHA or change a SHA.
* Add all transitive dependencies of the package to prevent unpinned packages.
Tests will fail if transitive dependencies aren't included.
Once `glide.yaml` describes the desired state use glide and glide-vc to update `glide.lock` and `vendor`.
```
glide up -v
glide-vc
```
When composing commits make sure that updates to `vendor` are in a separate commit from the main changes. GitHub's UI makes commits with a large number of changes unreviewable.
Commit histories should look like the following:
```
connector/ldap: add a LDAP connector
vendor: revendor
```
[glide]: https://github.com/Masterminds/glide
[glide-vc]: https://github.com/sgotti/glide-vc
[go-vendor]: https://golang.org/cmd/go/#hdr-Vendor_Directories

4
glide.lock generated
View file

@ -1,5 +1,5 @@
hash: 773c45cb2136423f907496cc1ba67e0c58b35e237b15b0d5f212dce598265442
updated: 2016-12-01T13:12:54.401738528-08:00
hash: 2d3cc27484f672337349fffc8d7839462df7b1fbedc9200ae0a87a84afeed896
updated: 2016-12-07T13:06:21.940111139-08:00
imports:
- name: github.com/cockroachdb/cockroach-go
version: 31611c0501c812f437d4861d87d117053967c955

View file

@ -1,5 +1,11 @@
# NOTE(ericchiang): Create a separate block for each dependency so it's clear
# which dependencies require which transitive dependencies.
# For detailed docs on how to add new dependencies or
# update dependencies see Documentation/dev-dependencies.md
#
# The TL;DR is:
# * Use glide and glide-vc.
# * List transitive dependencies.
# * Create a separate block for each dependency group.
# * Keep updates to "vendor/" in a seperate commit from actual changes.
package: github.com/coreos/dex
import:
@ -11,17 +17,20 @@ import:
- package: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
# LDAP dependencies.
- package: gopkg.in/ldap.v2
version: 0e7db8eb77695b5a952f0e5d78df9ab160050c73
- package: gopkg.in/asn1-ber.v1
version: 4e86f4367175e39f69d9358a5f17b4dda270378d
# Used for JOSE functionality (JWKs, JWTs, etc.).
- package: gopkg.in/square/go-jose.v2
version: v2.0.0
subpackages:
- cipher
- json
# Imported directly and by several third party packages.
- package: golang.org/x/net
version: 6a513affb38dc9788b449d59ffed099b8de18fa0
subpackages:
@ -33,16 +42,21 @@ import:
- lex/httplex
- trace
# Used for parsing configs.
- package: github.com/ghodss/yaml
version: bea76d6a4713e18b7f5321a2b020738552def3ea
- package: gopkg.in/yaml.v2
version: a83829b6f1293c91addabc89d0571c246397bbf4
# Router used by the server.
- package: github.com/gorilla/mux
version: 9fa818a44c2bf1396a17f9d5a3c0f6dd39d2ff8e
- package: github.com/gorilla/context
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296
# Package with a bunch of sane crypto defaults. Consider just
# copy the code (as recommended by the repo itself) instead
# of importing.
- package: github.com/gtank/cryptopasta
version: e7e23673cac3f529f49e22f94e4af6d12bb49dba
- package: golang.org/x/crypto
@ -50,6 +64,7 @@ import:
subpackages:
- bcrypt
# Used for server integration tests and OpenID Connect connector.
- package: github.com/coreos/go-oidc
version: dedb650fb29c39c2f21aa88c1e4cec66da8754d1
- package: github.com/pquerna/cachecontrol
@ -71,13 +86,8 @@ import:
- internal/datastore
- internal/log
- internal/remote_api
# Go's protobuf generator is also a direct dependency of this repo.
- package: github.com/golang/protobuf
version: 874264fbbb43f4d91e999fecb4b40143ed611400
subpackages:
- proto
- protoc-gen-go
# Testing conveniences.
- package: github.com/kylelemons/godebug
subpackages:
- diff
@ -96,7 +106,7 @@ import:
subpackages:
- crdb
# gRPC also imports protobuf and the x/net/http2 stack.
# gRPC and protobuf are use for the API. Also import x/net/http2 stack.
- package: google.golang.org/grpc
version: b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21
subpackages:
@ -108,3 +118,8 @@ import:
- naming
- peer
- transport
- package: github.com/golang/protobuf
version: 874264fbbb43f4d91e999fecb4b40143ed611400
subpackages:
- proto
- protoc-gen-go

View file

@ -1,3 +0,0 @@
*~
.#*
*.test

View file

@ -1,55 +0,0 @@
# Copyright 2016 The Cockroach 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. See the AUTHORS file
# for names of contributors.
#
# Author: Spencer Kimball (spencer.kimball@gmail.com)
#
# Cockroach build rules.
GO ?= go
# Allow setting of go build flags from the command line.
GOFLAGS :=
.PHONY: all
all: test check
.PHONY: test
test:
$(GO) test -v -i ./...
$(GO) test -v ./...
.PHONY: deps
deps:
$(GO) get -d -t ./...
.PHONY: check
check:
@echo "checking for \"path\" imports"
@! git grep -F '"path"' -- '*.go'
@echo "errcheck"
@errcheck ./...
@echo "vet"
@! go tool vet . 2>&1 | \
grep -vE '^vet: cannot process directory .git'
@echo "vet --shadow"
@! go tool vet --shadow . 2>&1 | \
grep -vE '(declaration of err shadows|^vet: cannot process directory \.git)'
@echo "golint"
@! golint ./... | grep -vE '(\.pb\.go)'
@echo "varcheck"
@varcheck -e ./...
@echo "gofmt (simplify)"
@! gofmt -s -d -l . 2>&1 | grep -vE '^\.git/'
@echo "goimports"
@! goimports -l . | grep -vF 'No Exceptions'

View file

@ -1,2 +0,0 @@
# testing
Testing helpers for cockroach clients.

View file

@ -1,18 +0,0 @@
machine:
environment:
GOROOT: ${HOME}/go
PATH: ${PATH}:${HOME}/go/bin
post:
- sudo rm -rf /usr/local/go
- if [ ! -e go1.6.linux-amd64.tar.gz ]; then curl -O https://storage.googleapis.com/golang/go1.6.linux-amd64.tar.gz; fi
- tar -C ${HOME} -xzf go1.6.linux-amd64.tar.gz
dependencies:
override:
- make deps
cache_directories:
- ~/go1.6.linux-amd64.tar.gz
test:
override:
- make test

View file

@ -1,119 +0,0 @@
package testserver
import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"time"
)
const (
awsBaseURL = "https://s3.amazonaws.com/cockroach/cockroach"
latestSuffix = "LATEST"
localBinaryPath = "/var/tmp"
finishedFileMode = 0555
)
func binaryName() string {
return fmt.Sprintf("cockroach.%s-%s", runtime.GOOS, runtime.GOARCH)
}
func binaryNameWithSha(sha string) string {
return fmt.Sprintf("%s.%s", binaryName(), sha)
}
func binaryPath(sha string) string {
return filepath.Join(localBinaryPath, binaryNameWithSha(sha))
}
func latestMarkerURL() string {
return fmt.Sprintf("%s/%s.%s", awsBaseURL, binaryName(), latestSuffix)
}
func binaryURL(sha string) string {
return fmt.Sprintf("%s/%s.%s", awsBaseURL, binaryName(), sha)
}
func findLatestSha() (string, error) {
markerURL := latestMarkerURL()
marker, err := http.Get(markerURL)
if err != nil {
return "", fmt.Errorf("could not download %s: %s", markerURL)
}
if marker.StatusCode == 404 {
return "", fmt.Errorf("for 404 from GET %s: make sure OS and ARCH are supported",
markerURL)
} else if marker.StatusCode != 200 {
return "", fmt.Errorf("bad response got GET %s: %d (%s)",
markerURL, marker.StatusCode, marker.Status)
}
defer marker.Body.Close()
body, err := ioutil.ReadAll(marker.Body)
if err != nil {
return "", err
}
return strings.TrimSpace(string(body)), nil
}
func downloadFile(url, filePath string) error {
output, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0200)
if err != nil {
return fmt.Errorf("error creating %s: %s", filePath, "-", err)
}
defer output.Close()
log.Printf("downloading %s to %s, this may take some time", url, filePath)
response, err := http.Get(url)
if err != nil {
return fmt.Errorf("error downloading %s: %s", url, err)
}
defer response.Body.Close()
if response.StatusCode != 200 {
return fmt.Errorf("error downloading %s: %d (%s)", url, response.StatusCode, response.Status)
}
_, err = io.Copy(output, response.Body)
if err != nil {
return fmt.Errorf("problem downloading %s to %s: %s", url, filePath, err)
}
// Download was successful, add the rw bits.
return os.Chmod(filePath, finishedFileMode)
}
func downloadLatestBinary() (string, error) {
sha, err := findLatestSha()
if err != nil {
return "", err
}
localFile := binaryPath(sha)
for {
finfo, err := os.Stat(localFile)
if err != nil {
// File does not exist: download it.
break
}
// File already present: check mode.
if finfo.Mode().Perm() == finishedFileMode {
return localFile, nil
}
time.Sleep(time.Millisecond * 10)
}
err = downloadFile(binaryURL(sha), localFile)
if err != nil {
_ = os.Remove(localFile)
return "", err
}
return localFile, nil
}

View file

@ -1,415 +0,0 @@
// Copyright 2016 The Cockroach 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.
//
// Author: Marc Berhault (marc@cockroachlabs.com)
// Package testserver provides helpers to run a cockroach binary within tests.
// It automatically downloads the latest cockroach binary for your platform
// (Linux-amd64 and Darwin-amd64 only for now), or attempts to run "cockroach"
// from your PATH.
//
// A normal invocation is (check err every time):
// ts, err := testserver.NewTestServer()
// err = ts.Start()
// defer ts.Stop()
// url := ts.PGURL()
//
// To use, run as follows:
// import "github.com/cockroachdb/cockroach-go/testserver"
// import "testing"
// import "time"
//
// func TestRunServer(t *testing.T) {
// ts, err := testserver.NewTestServer()
// if err != nil {
// t.Fatal(err)
// }
// err := ts.Start()
// if err != nil {
// t.Fatal(err)
// }
// defer ts.Stop()
//
// url := ts.PGURL()
// if url != nil {
// t.FatalF("url not found")
// }
// t.Logf("URL: %s", url.String())
//
// db, err := sql.Open("postgres", url.String())
// if err != nil {
// t.Fatal(err)
// }
// }
package testserver
import (
"database/sql"
"errors"
"fmt"
"io/ioutil"
"log"
"net/url"
"os"
"os/exec"
"os/user"
"path/filepath"
"regexp"
"strings"
"sync"
"syscall"
"testing"
"time"
)
var sqlURLRegexp = regexp.MustCompile("sql:\\s+(postgresql:.+)\n")
const (
stateNew = iota
stateRunning = iota
stateStopped = iota
stateFailed = iota
socketPort = 26257
socketFileBase = ".s.PGSQL"
)
// TestServer is a helper to run a real cockroach node.
type TestServer struct {
mu sync.RWMutex
state int
baseDir string
pgURL *url.URL
cmd *exec.Cmd
args []string
stdout string
stderr string
stdoutBuf logWriter
stderrBuf logWriter
}
// NewDBForTest creates a new CockroachDB TestServer instance and
// opens a SQL database connection to it. Returns a sql *DB instance a
// shutdown function. The caller is responsible for executing the
// returned shutdown function on exit.
func NewDBForTest(t *testing.T) (*sql.DB, func()) {
return NewDBForTestWithDatabase(t, "")
}
// NewDBForTestWithDatabase creates a new CockroachDB TestServer
// instance and opens a SQL database connection to it. If database is
// specified, the returned connection will explicitly connect to
// it. Returns a sql *DB instance a shutdown function. The caller is
// responsible for executing the returned shutdown function on exit.
func NewDBForTestWithDatabase(t *testing.T, database string) (*sql.DB, func()) {
ts, err := NewTestServer()
if err != nil {
t.Fatal(err)
}
err = ts.Start()
if err != nil {
t.Fatal(err)
}
url := ts.PGURL()
if url == nil {
t.Fatalf("url not found")
}
if len(database) > 0 {
url.Path = database
}
db, err := sql.Open("postgres", url.String())
if err != nil {
t.Fatal(err)
}
ts.WaitForInit(db)
return db, func() {
_ = db.Close()
ts.Stop()
}
}
// NewTestServer creates a new TestServer, but does not start it.
// The cockroach binary for your OS and ARCH is downloaded automatically.
// If the download fails, we attempt just call "cockroach", hoping it is
// found in your path.
func NewTestServer() (*TestServer, error) {
cockroachBinary, err := downloadLatestBinary()
if err == nil {
log.Printf("Using automatically-downloaded binary: %s", cockroachBinary)
} else {
log.Printf("Attempting to use cockroach binary from your PATH")
cockroachBinary = "cockroach"
}
// Force "/tmp/" so avoid OSX's really long temp directory names
// which get us over the socket filename length limit.
baseDir, err := ioutil.TempDir("/tmp", "cockroach-testserver")
if err != nil {
return nil, fmt.Errorf("could not create temp directory: %s", err)
}
logDir := filepath.Join(baseDir, "logs")
if err := os.MkdirAll(logDir, 0755); err != nil {
return nil, fmt.Errorf("could not create logs directory: %s: %s", logDir, err)
}
options := url.Values{
"host": []string{baseDir},
}
pgurl := &url.URL{
Scheme: "postgres",
User: url.User("root"),
Host: fmt.Sprintf(":%d", socketPort),
RawQuery: options.Encode(),
}
socketPath := filepath.Join(baseDir, fmt.Sprintf("%s.%d", socketFileBase, socketPort))
args := []string{
cockroachBinary,
"start",
"--logtostderr",
"--insecure",
"--port=0",
"--http-port=0",
"--socket=" + socketPath,
"--store=" + baseDir,
}
ts := &TestServer{
baseDir: baseDir,
pgURL: pgurl,
args: args,
stdout: filepath.Join(logDir, "cockroach.stdout"),
stderr: filepath.Join(logDir, "cockroach.stderr"),
}
return ts, nil
}
// Stdout returns the entire contents of the process' stdout.
func (ts *TestServer) Stdout() string {
return ts.stdoutBuf.String()
}
// Stderr returns the entire contents of the process' stderr.
func (ts *TestServer) Stderr() string {
return ts.stderrBuf.String()
}
// PGURL returns the postgres connection URL to reach the started
// cockroach node.
// It loops until the expected unix socket file exists.
// This does not timeout, relying instead on test timeouts.
func (ts *TestServer) PGURL() *url.URL {
socketPath := filepath.Join(ts.baseDir, fmt.Sprintf("%s.%d", socketFileBase, socketPort))
for {
if _, err := os.Stat(socketPath); err == nil {
return ts.pgURL
}
time.Sleep(time.Millisecond * 10)
}
return nil
}
// WaitForInit repeatedly looks up the list of databases until
// the "system" database exists. It ignores all errors as we are
// waiting for the process to start and complete initialization.
// This does not timeout, relying instead on test timeouts.
func (ts *TestServer) WaitForInit(db *sql.DB) {
for {
// We issue a query that fails both on connection errors and on the
// system database not existing.
if _, err := db.Query("SHOW DATABASES"); err == nil {
return
}
time.Sleep(time.Millisecond * 10)
}
}
// Start runs the process, returning an error on any problems,
// including being unable to start, but not unexpected failure.
// It should only be called once in the lifetime of a TestServer object.
func (ts *TestServer) Start() error {
ts.mu.Lock()
if ts.state != stateNew {
ts.mu.Unlock()
return errors.New("Start() can only be called once")
}
ts.state = stateRunning
ts.mu.Unlock()
ts.cmd = exec.Command(ts.args[0], ts.args[1:]...)
ts.cmd.Env = []string{"COCKROACH_MAX_OFFSET=1ns"}
if len(ts.stdout) > 0 {
wr, err := newFileLogWriter(ts.stdout)
if err != nil {
return fmt.Errorf("unable to open file %s: %s", ts.stdout, err)
}
ts.stdoutBuf = wr
}
ts.cmd.Stdout = ts.stdoutBuf
if len(ts.stderr) > 0 {
wr, err := newFileLogWriter(ts.stderr)
if err != nil {
return fmt.Errorf("unable to open file %s: %s", ts.stderr, err)
}
ts.stderrBuf = wr
}
ts.cmd.Stderr = ts.stderrBuf
for k, v := range defaultEnv() {
ts.cmd.Env = append(ts.cmd.Env, k+"="+v)
}
err := ts.cmd.Start()
if ts.cmd.Process != nil {
log.Printf("process %d started: %s", ts.cmd.Process.Pid, strings.Join(ts.args, " "))
}
if err != nil {
log.Printf(err.Error())
ts.stdoutBuf.Close()
ts.stderrBuf.Close()
ts.mu.Lock()
ts.state = stateFailed
ts.mu.Unlock()
return fmt.Errorf("failure starting process: %s", err)
}
go func() {
ts.cmd.Wait()
ts.stdoutBuf.Close()
ts.stderrBuf.Close()
ps := ts.cmd.ProcessState
sy := ps.Sys().(syscall.WaitStatus)
log.Printf("Process %d exited with status %d", ps.Pid(), sy.ExitStatus())
log.Printf(ps.String())
ts.mu.Lock()
if sy.ExitStatus() == 0 {
ts.state = stateStopped
} else {
ts.state = stateFailed
}
ts.mu.Unlock()
}()
return nil
}
// Stop kills the process if it is still running and cleans its directory.
// It should only be called once in the lifetime of a TestServer object.
// Logs fatal if the process has already failed.
func (ts *TestServer) Stop() {
ts.mu.RLock()
defer ts.mu.RUnlock()
if ts.state == stateNew {
log.Fatal("Stop() called, but Start() was never called")
}
if ts.state == stateFailed {
log.Fatalf("Stop() called, but process exited unexpectedly. Stdout:\n%s\nStderr:\n%s\n",
ts.Stdout(), ts.Stderr())
return
}
if ts.state != stateStopped {
// Only call kill if not running. It could have exited properly.
ts.cmd.Process.Kill()
}
// Only cleanup on intentional stops.
_ = os.RemoveAll(ts.baseDir)
}
type logWriter interface {
Write(p []byte) (n int, err error)
String() string
Len() int64
Close()
}
type fileLogWriter struct {
filename string
file *os.File
}
func newFileLogWriter(file string) (*fileLogWriter, error) {
f, err := os.Create(file)
if err != nil {
return nil, err
}
return &fileLogWriter{
filename: file,
file: f,
}, nil
}
func (w fileLogWriter) Close() {
w.file.Close()
}
func (w fileLogWriter) Write(p []byte) (n int, err error) {
return w.file.Write(p)
}
func (w fileLogWriter) String() string {
b, err := ioutil.ReadFile(w.filename)
if err == nil {
return string(b)
}
return ""
}
func (w fileLogWriter) Len() int64 {
s, err := os.Stat(w.filename)
if err == nil {
return s.Size()
}
return 0
}
func defaultEnv() map[string]string {
vars := map[string]string{}
u, err := user.Current()
if err == nil {
if _, ok := vars["USER"]; !ok {
vars["USER"] = u.Username
}
if _, ok := vars["UID"]; !ok {
vars["UID"] = u.Uid
}
if _, ok := vars["GID"]; !ok {
vars["GID"] = u.Gid
}
if _, ok := vars["HOME"]; !ok {
vars["HOME"] = u.HomeDir
}
}
if _, ok := vars["PATH"]; !ok {
vars["PATH"] = os.Getenv("PATH")
}
return vars
}

View file

@ -1,35 +0,0 @@
// Copyright 2016 The Cockroach 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.
//
// Author: Marc Berhault (marc@cockroachlabs.com)
package testserver_test
import (
"testing"
// Needed for postgres driver test.
"github.com/cockroachdb/cockroach-go/testserver"
_ "github.com/lib/pq"
)
func TestRunServer(t *testing.T) {
db, stop := testserver.NewDBForTest(t)
defer stop()
_, err := db.Exec("SELECT 1")
if err != nil {
t.Fatal(err)
}
}

View file

@ -1,21 +0,0 @@
# 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. To create one:
1. Visit your [Google Developer Console][google-developer-console].
2. Click "Credentials" on the left column.
3. Click the "Create credentials" button followed by "OAuth client ID".
4. Select "Web application" and add "http://127.0.0.1:5556/auth/google/callback" as an authorized redirect URI.
5. Click create and add the printed client ID and secret to your environment using the following variables:
```
GOOGLE_OAUTH2_CLIENT_ID
GOOGLE_OAUTH2_CLIENT_SECRET
```
Finally run the examples using the Go tool and navigate to http://127.0.0.1:5556.
```
go run ./examples/idtoken/app.go
```
[google-developer-console]: https://console.developers.google.com/apis/dashboard

View file

@ -1,89 +0,0 @@
/*
This is an example application to demonstrate parsing an ID Token.
*/
package main
import (
"encoding/json"
"log"
"net/http"
"os"
oidc "github.com/coreos/go-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.Verifier()
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
}
idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
oauth2Token.AccessToken = "*REDACTED*"
resp := struct {
OAuth2Token *oauth2.Token
IDTokenClaims *json.RawMessage // ID Token payload is just JSON.
}{oauth2Token, new(json.RawMessage)}
if err := idToken.Claims(&resp.IDTokenClaims); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data, err := json.MarshalIndent(resp, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(data)
})
log.Printf("listening on http://%s/", "127.0.0.1:5556")
log.Fatal(http.ListenAndServe("127.0.0.1:5556", nil))
}

View file

@ -1,104 +0,0 @@
/*
This is an example application to demonstrate verifying an ID Token with a nonce.
*/
package main
import (
"encoding/json"
"errors"
"log"
"net/http"
"os"
oidc "github.com/coreos/go-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.Verifier(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.
idToken, err := nonceEnabledVerifier.Verify(ctx, rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
resp := struct {
OAuth2Token *oauth2.Token
IDTokenClaims *json.RawMessage // ID Token payload is just JSON.
}{oauth2Token, new(json.RawMessage)}
if err := idToken.Claims(&resp.IDTokenClaims); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data, err := json.MarshalIndent(resp, "", " ")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(data)
})
log.Printf("listening on http://%s/", "127.0.0.1:5556")
log.Fatal(http.ListenAndServe("127.0.0.1:5556", nil))
}

View file

@ -1,76 +0,0 @@
/*
This is an example application to demonstrate querying the user info endpoint.
*/
package main
import (
"encoding/json"
"log"
"net/http"
"os"
oidc "github.com/coreos/go-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))
}

View file

@ -1,7 +0,0 @@
package http
import "net/http"
type Client interface {
Do(*http.Request) (*http.Response, error)
}

View file

@ -1,2 +0,0 @@
// Package http is DEPRECATED. Use net/http instead.
package http

View file

@ -1,156 +0,0 @@
package http
import (
"encoding/base64"
"encoding/json"
"errors"
"log"
"net/http"
"net/url"
"path"
"strconv"
"strings"
"time"
)
func WriteError(w http.ResponseWriter, code int, msg string) {
e := struct {
Error string `json:"error"`
}{
Error: msg,
}
b, err := json.Marshal(e)
if err != nil {
log.Printf("go-oidc: failed to marshal %#v: %v", e, err)
code = http.StatusInternalServerError
b = []byte(`{"error":"server_error"}`)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(b)
}
// BasicAuth parses a username and password from the request's
// Authorization header. This was pulled from golang master:
// https://codereview.appspot.com/76540043
func BasicAuth(r *http.Request) (username, password string, ok bool) {
auth := r.Header.Get("Authorization")
if auth == "" {
return
}
if !strings.HasPrefix(auth, "Basic ") {
return
}
c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic "))
if err != nil {
return
}
cs := string(c)
s := strings.IndexByte(cs, ':')
if s < 0 {
return
}
return cs[:s], cs[s+1:], true
}
func cacheControlMaxAge(hdr string) (time.Duration, bool, error) {
for _, field := range strings.Split(hdr, ",") {
parts := strings.SplitN(strings.TrimSpace(field), "=", 2)
k := strings.ToLower(strings.TrimSpace(parts[0]))
if k != "max-age" {
continue
}
if len(parts) == 1 {
return 0, false, errors.New("max-age has no value")
}
v := strings.TrimSpace(parts[1])
if v == "" {
return 0, false, errors.New("max-age has empty value")
}
age, err := strconv.Atoi(v)
if err != nil {
return 0, false, err
}
if age <= 0 {
return 0, false, nil
}
return time.Duration(age) * time.Second, true, nil
}
return 0, false, nil
}
func expires(date, expires string) (time.Duration, bool, error) {
if date == "" || expires == "" {
return 0, false, nil
}
te, err := time.Parse(time.RFC1123, expires)
if err != nil {
return 0, false, err
}
td, err := time.Parse(time.RFC1123, date)
if err != nil {
return 0, false, err
}
ttl := te.Sub(td)
// headers indicate data already expired, caller should not
// have to care about this case
if ttl <= 0 {
return 0, false, nil
}
return ttl, true, nil
}
func Cacheable(hdr http.Header) (time.Duration, bool, error) {
ttl, ok, err := cacheControlMaxAge(hdr.Get("Cache-Control"))
if err != nil || ok {
return ttl, ok, err
}
return expires(hdr.Get("Date"), hdr.Get("Expires"))
}
// MergeQuery appends additional query values to an existing URL.
func MergeQuery(u url.URL, q url.Values) url.URL {
uv := u.Query()
for k, vs := range q {
for _, v := range vs {
uv.Add(k, v)
}
}
u.RawQuery = uv.Encode()
return u
}
// NewResourceLocation appends a resource id to the end of the requested URL path.
func NewResourceLocation(reqURL *url.URL, id string) string {
var u url.URL
u = *reqURL
u.Path = path.Join(u.Path, id)
u.RawQuery = ""
u.Fragment = ""
return u.String()
}
// CopyRequest returns a clone of the provided *http.Request.
// The returned object is a shallow copy of the struct and a
// deep copy of its Header field.
func CopyRequest(r *http.Request) *http.Request {
r2 := *r
r2.Header = make(http.Header)
for k, s := range r.Header {
r2.Header[k] = s
}
return &r2
}

View file

@ -1,380 +0,0 @@
package http
import (
"net/http"
"net/url"
"reflect"
"strings"
"testing"
"time"
)
func TestCacheControlMaxAgeSuccess(t *testing.T) {
tests := []struct {
hdr string
wantAge time.Duration
wantOK bool
}{
{"max-age=12", 12 * time.Second, true},
{"max-age=-12", 0, false},
{"max-age=0", 0, false},
{"public, max-age=12", 12 * time.Second, true},
{"public, max-age=40192, must-revalidate", 40192 * time.Second, true},
{"public, not-max-age=12, must-revalidate", time.Duration(0), false},
}
for i, tt := range tests {
maxAge, ok, err := cacheControlMaxAge(tt.hdr)
if err != nil {
t.Errorf("case %d: err=%v", i, err)
}
if tt.wantAge != maxAge {
t.Errorf("case %d: want=%d got=%d", i, tt.wantAge, maxAge)
}
if tt.wantOK != ok {
t.Errorf("case %d: incorrect ok value: want=%t got=%t", i, tt.wantOK, ok)
}
}
}
func TestCacheControlMaxAgeFail(t *testing.T) {
tests := []string{
"max-age=aasdf",
"max-age=",
"max-age",
}
for i, tt := range tests {
_, ok, err := cacheControlMaxAge(tt)
if ok {
t.Errorf("case %d: want ok=false, got true", i)
}
if err == nil {
t.Errorf("case %d: want non-nil err", i)
}
}
}
func TestMergeQuery(t *testing.T) {
tests := []struct {
u string
q url.Values
w string
}{
// No values
{
u: "http://example.com",
q: nil,
w: "http://example.com",
},
// No additional values
{
u: "http://example.com?foo=bar",
q: nil,
w: "http://example.com?foo=bar",
},
// Simple addition
{
u: "http://example.com",
q: url.Values{
"foo": []string{"bar"},
},
w: "http://example.com?foo=bar",
},
// Addition with existing values
{
u: "http://example.com?dog=boo",
q: url.Values{
"foo": []string{"bar"},
},
w: "http://example.com?dog=boo&foo=bar",
},
// Merge
{
u: "http://example.com?dog=boo",
q: url.Values{
"dog": []string{"elroy"},
},
w: "http://example.com?dog=boo&dog=elroy",
},
// Add and merge
{
u: "http://example.com?dog=boo",
q: url.Values{
"dog": []string{"elroy"},
"foo": []string{"bar"},
},
w: "http://example.com?dog=boo&dog=elroy&foo=bar",
},
// Multivalue merge
{
u: "http://example.com?dog=boo",
q: url.Values{
"dog": []string{"elroy", "penny"},
},
w: "http://example.com?dog=boo&dog=elroy&dog=penny",
},
}
for i, tt := range tests {
ur, err := url.Parse(tt.u)
if err != nil {
t.Errorf("case %d: failed parsing test url: %v, error: %v", i, tt.u, err)
}
got := MergeQuery(*ur, tt.q)
want, err := url.Parse(tt.w)
if err != nil {
t.Errorf("case %d: failed parsing want url: %v, error: %v", i, tt.w, err)
}
if !reflect.DeepEqual(*want, got) {
t.Errorf("case %d: want: %v, got: %v", i, *want, got)
}
}
}
func TestExpiresPass(t *testing.T) {
tests := []struct {
date string
exp string
wantTTL time.Duration
wantOK bool
}{
// Expires and Date properly set
{
date: "Thu, 01 Dec 1983 22:00:00 GMT",
exp: "Fri, 02 Dec 1983 01:00:00 GMT",
wantTTL: 10800 * time.Second,
wantOK: true,
},
// empty headers
{
date: "",
exp: "",
wantOK: false,
},
// lack of Expirs short-ciruits Date parsing
{
date: "foo",
exp: "",
wantOK: false,
},
// lack of Date short-ciruits Expires parsing
{
date: "",
exp: "foo",
wantOK: false,
},
// no Date
{
exp: "Thu, 01 Dec 1983 22:00:00 GMT",
wantTTL: 0,
wantOK: false,
},
// no Expires
{
date: "Thu, 01 Dec 1983 22:00:00 GMT",
wantTTL: 0,
wantOK: false,
},
// Expires < Date
{
date: "Fri, 02 Dec 1983 01:00:00 GMT",
exp: "Thu, 01 Dec 1983 22:00:00 GMT",
wantTTL: 0,
wantOK: false,
},
}
for i, tt := range tests {
ttl, ok, err := expires(tt.date, tt.exp)
if err != nil {
t.Errorf("case %d: err=%v", i, err)
}
if tt.wantTTL != ttl {
t.Errorf("case %d: want=%d got=%d", i, tt.wantTTL, ttl)
}
if tt.wantOK != ok {
t.Errorf("case %d: incorrect ok value: want=%t got=%t", i, tt.wantOK, ok)
}
}
}
func TestExpiresFail(t *testing.T) {
tests := []struct {
date string
exp string
}{
// malformed Date header
{
date: "foo",
exp: "Fri, 02 Dec 1983 01:00:00 GMT",
},
// malformed exp header
{
date: "Fri, 02 Dec 1983 01:00:00 GMT",
exp: "bar",
},
}
for i, tt := range tests {
_, _, err := expires(tt.date, tt.exp)
if err == nil {
t.Errorf("case %d: expected non-nil error", i)
}
}
}
func TestCacheablePass(t *testing.T) {
tests := []struct {
headers http.Header
wantTTL time.Duration
wantOK bool
}{
// valid Cache-Control
{
headers: http.Header{
"Cache-Control": []string{"max-age=100"},
},
wantTTL: 100 * time.Second,
wantOK: true,
},
// valid Date/Expires
{
headers: http.Header{
"Date": []string{"Thu, 01 Dec 1983 22:00:00 GMT"},
"Expires": []string{"Fri, 02 Dec 1983 01:00:00 GMT"},
},
wantTTL: 10800 * time.Second,
wantOK: true,
},
// Cache-Control supersedes Date/Expires
{
headers: http.Header{
"Cache-Control": []string{"max-age=100"},
"Date": []string{"Thu, 01 Dec 1983 22:00:00 GMT"},
"Expires": []string{"Fri, 02 Dec 1983 01:00:00 GMT"},
},
wantTTL: 100 * time.Second,
wantOK: true,
},
// no caching headers
{
headers: http.Header{},
wantOK: false,
},
}
for i, tt := range tests {
ttl, ok, err := Cacheable(tt.headers)
if err != nil {
t.Errorf("case %d: err=%v", i, err)
continue
}
if tt.wantTTL != ttl {
t.Errorf("case %d: want=%d got=%d", i, tt.wantTTL, ttl)
}
if tt.wantOK != ok {
t.Errorf("case %d: incorrect ok value: want=%t got=%t", i, tt.wantOK, ok)
}
}
}
func TestCacheableFail(t *testing.T) {
tests := []http.Header{
// invalid Cache-Control short-circuits
http.Header{
"Cache-Control": []string{"max-age"},
"Date": []string{"Thu, 01 Dec 1983 22:00:00 GMT"},
"Expires": []string{"Fri, 02 Dec 1983 01:00:00 GMT"},
},
// no Cache-Control, invalid Expires
http.Header{
"Date": []string{"Thu, 01 Dec 1983 22:00:00 GMT"},
"Expires": []string{"boo"},
},
}
for i, tt := range tests {
_, _, err := Cacheable(tt)
if err == nil {
t.Errorf("case %d: want non-nil err", i)
}
}
}
func TestNewResourceLocation(t *testing.T) {
tests := []struct {
ru *url.URL
id string
want string
}{
{
ru: &url.URL{
Scheme: "http",
Host: "example.com",
},
id: "foo",
want: "http://example.com/foo",
},
// https
{
ru: &url.URL{
Scheme: "https",
Host: "example.com",
},
id: "foo",
want: "https://example.com/foo",
},
// with path
{
ru: &url.URL{
Scheme: "http",
Host: "example.com",
Path: "one/two/three",
},
id: "foo",
want: "http://example.com/one/two/three/foo",
},
// with fragment
{
ru: &url.URL{
Scheme: "http",
Host: "example.com",
Fragment: "frag",
},
id: "foo",
want: "http://example.com/foo",
},
// with query
{
ru: &url.URL{
Scheme: "http",
Host: "example.com",
RawQuery: "dog=elroy",
},
id: "foo",
want: "http://example.com/foo",
},
}
for i, tt := range tests {
got := NewResourceLocation(tt.ru, tt.id)
if tt.want != got {
t.Errorf("case %d: want=%s, got=%s", i, tt.want, got)
}
}
}
func TestCopyRequest(t *testing.T) {
r1, err := http.NewRequest("GET", "http://example.com", strings.NewReader("foo"))
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
r2 := CopyRequest(r1)
if !reflect.DeepEqual(r1, r2) {
t.Fatalf("Result of CopyRequest incorrect: %#v != %#v", r1, r2)
}
}

View file

@ -1,29 +0,0 @@
package http
import (
"errors"
"net/url"
)
// ParseNonEmptyURL checks that a string is a parsable URL which is also not empty
// since `url.Parse("")` does not return an error. Must contian a scheme and a host.
func ParseNonEmptyURL(u string) (*url.URL, error) {
if u == "" {
return nil, errors.New("url is empty")
}
ur, err := url.Parse(u)
if err != nil {
return nil, err
}
if ur.Scheme == "" {
return nil, errors.New("url scheme is empty")
}
if ur.Host == "" {
return nil, errors.New("url host is empty")
}
return ur, nil
}

View file

@ -1,49 +0,0 @@
package http
import (
"net/url"
"testing"
)
func TestParseNonEmptyURL(t *testing.T) {
tests := []struct {
u string
ok bool
}{
{"", false},
{"http://", false},
{"example.com", false},
{"example", false},
{"http://example", true},
{"http://example:1234", true},
{"http://example.com", true},
{"http://example.com:1234", true},
}
for i, tt := range tests {
u, err := ParseNonEmptyURL(tt.u)
if err != nil {
t.Logf("err: %v", err)
if tt.ok {
t.Errorf("case %d: unexpected error: %v", i, err)
} else {
continue
}
}
if !tt.ok {
t.Errorf("case %d: expected error but got none", i)
continue
}
uu, err := url.Parse(tt.u)
if err != nil {
t.Errorf("case %d: unexpected error: %v", i, err)
continue
}
if uu.String() != u.String() {
t.Errorf("case %d: incorrect url value, want: %q, got: %q", i, uu.String(), u.String())
}
}
}

View file

@ -1,126 +0,0 @@
package jose
import (
"encoding/json"
"fmt"
"math"
"time"
)
type Claims map[string]interface{}
func (c Claims) Add(name string, value interface{}) {
c[name] = value
}
func (c Claims) StringClaim(name string) (string, bool, error) {
cl, ok := c[name]
if !ok {
return "", false, nil
}
v, ok := cl.(string)
if !ok {
return "", false, fmt.Errorf("unable to parse claim as string: %v", name)
}
return v, true, nil
}
func (c Claims) StringsClaim(name string) ([]string, bool, error) {
cl, ok := c[name]
if !ok {
return nil, false, nil
}
if v, ok := cl.([]string); ok {
return v, true, nil
}
// When unmarshaled, []string will become []interface{}.
if v, ok := cl.([]interface{}); ok {
var ret []string
for _, vv := range v {
str, ok := vv.(string)
if !ok {
return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
}
ret = append(ret, str)
}
return ret, true, nil
}
return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
}
func (c Claims) Int64Claim(name string) (int64, bool, error) {
cl, ok := c[name]
if !ok {
return 0, false, nil
}
v, ok := cl.(int64)
if !ok {
vf, ok := cl.(float64)
if !ok {
return 0, false, fmt.Errorf("unable to parse claim as int64: %v", name)
}
v = int64(vf)
}
return v, true, nil
}
func (c Claims) Float64Claim(name string) (float64, bool, error) {
cl, ok := c[name]
if !ok {
return 0, false, nil
}
v, ok := cl.(float64)
if !ok {
vi, ok := cl.(int64)
if !ok {
return 0, false, fmt.Errorf("unable to parse claim as float64: %v", name)
}
v = float64(vi)
}
return v, true, nil
}
func (c Claims) TimeClaim(name string) (time.Time, bool, error) {
v, ok, err := c.Float64Claim(name)
if !ok || err != nil {
return time.Time{}, ok, err
}
s := math.Trunc(v)
ns := (v - s) * math.Pow(10, 9)
return time.Unix(int64(s), int64(ns)).UTC(), true, nil
}
func decodeClaims(payload []byte) (Claims, error) {
var c Claims
if err := json.Unmarshal(payload, &c); err != nil {
return nil, fmt.Errorf("malformed JWT claims, unable to decode: %v", err)
}
return c, nil
}
func marshalClaims(c Claims) ([]byte, error) {
b, err := json.Marshal(c)
if err != nil {
return nil, err
}
return b, nil
}
func encodeClaims(c Claims) (string, error) {
b, err := marshalClaims(c)
if err != nil {
return "", err
}
return encodeSegment(b), nil
}

View file

@ -1,328 +0,0 @@
package jose
import (
"reflect"
"testing"
"time"
)
func TestString(t *testing.T) {
tests := []struct {
cl Claims
key string
ok bool
err bool
val string
}{
// ok, no err, claim exists
{
cl: Claims{
"foo": "bar",
},
key: "foo",
val: "bar",
ok: true,
err: false,
},
// no claims
{
cl: Claims{},
key: "foo",
val: "",
ok: false,
err: false,
},
// missing claim
{
cl: Claims{
"foo": "bar",
},
key: "xxx",
val: "",
ok: false,
err: false,
},
// unparsable: type
{
cl: Claims{
"foo": struct{}{},
},
key: "foo",
val: "",
ok: false,
err: true,
},
// unparsable: nil value
{
cl: Claims{
"foo": nil,
},
key: "foo",
val: "",
ok: false,
err: true,
},
}
for i, tt := range tests {
val, ok, err := tt.cl.StringClaim(tt.key)
if tt.err && err == nil {
t.Errorf("case %d: want err=non-nil, got err=nil", i)
} else if !tt.err && err != nil {
t.Errorf("case %d: want err=nil, got err=%v", i, err)
}
if tt.ok != ok {
t.Errorf("case %d: want ok=%v, got ok=%v", i, tt.ok, ok)
}
if tt.val != val {
t.Errorf("case %d: want val=%v, got val=%v", i, tt.val, val)
}
}
}
func TestInt64(t *testing.T) {
tests := []struct {
cl Claims
key string
ok bool
err bool
val int64
}{
// ok, no err, claim exists
{
cl: Claims{
"foo": int64(100),
},
key: "foo",
val: int64(100),
ok: true,
err: false,
},
// no claims
{
cl: Claims{},
key: "foo",
val: 0,
ok: false,
err: false,
},
// missing claim
{
cl: Claims{
"foo": "bar",
},
key: "xxx",
val: 0,
ok: false,
err: false,
},
// unparsable: type
{
cl: Claims{
"foo": struct{}{},
},
key: "foo",
val: 0,
ok: false,
err: true,
},
// unparsable: nil value
{
cl: Claims{
"foo": nil,
},
key: "foo",
val: 0,
ok: false,
err: true,
},
}
for i, tt := range tests {
val, ok, err := tt.cl.Int64Claim(tt.key)
if tt.err && err == nil {
t.Errorf("case %d: want err=non-nil, got err=nil", i)
} else if !tt.err && err != nil {
t.Errorf("case %d: want err=nil, got err=%v", i, err)
}
if tt.ok != ok {
t.Errorf("case %d: want ok=%v, got ok=%v", i, tt.ok, ok)
}
if tt.val != val {
t.Errorf("case %d: want val=%v, got val=%v", i, tt.val, val)
}
}
}
func TestTime(t *testing.T) {
now := time.Now().UTC()
unixNow := now.Unix()
tests := []struct {
cl Claims
key string
ok bool
err bool
val time.Time
}{
// ok, no err, claim exists
{
cl: Claims{
"foo": unixNow,
},
key: "foo",
val: time.Unix(now.Unix(), 0).UTC(),
ok: true,
err: false,
},
// no claims
{
cl: Claims{},
key: "foo",
val: time.Time{},
ok: false,
err: false,
},
// missing claim
{
cl: Claims{
"foo": "bar",
},
key: "xxx",
val: time.Time{},
ok: false,
err: false,
},
// unparsable: type
{
cl: Claims{
"foo": struct{}{},
},
key: "foo",
val: time.Time{},
ok: false,
err: true,
},
// unparsable: nil value
{
cl: Claims{
"foo": nil,
},
key: "foo",
val: time.Time{},
ok: false,
err: true,
},
}
for i, tt := range tests {
val, ok, err := tt.cl.TimeClaim(tt.key)
if tt.err && err == nil {
t.Errorf("case %d: want err=non-nil, got err=nil", i)
} else if !tt.err && err != nil {
t.Errorf("case %d: want err=nil, got err=%v", i, err)
}
if tt.ok != ok {
t.Errorf("case %d: want ok=%v, got ok=%v", i, tt.ok, ok)
}
if tt.val != val {
t.Errorf("case %d: want val=%v, got val=%v", i, tt.val, val)
}
}
}
func TestStringArray(t *testing.T) {
tests := []struct {
cl Claims
key string
ok bool
err bool
val []string
}{
// ok, no err, claim exists
{
cl: Claims{
"foo": []string{"bar", "faf"},
},
key: "foo",
val: []string{"bar", "faf"},
ok: true,
err: false,
},
// ok, no err, []interface{}
{
cl: Claims{
"foo": []interface{}{"bar", "faf"},
},
key: "foo",
val: []string{"bar", "faf"},
ok: true,
err: false,
},
// no claims
{
cl: Claims{},
key: "foo",
val: nil,
ok: false,
err: false,
},
// missing claim
{
cl: Claims{
"foo": "bar",
},
key: "xxx",
val: nil,
ok: false,
err: false,
},
// unparsable: type
{
cl: Claims{
"foo": struct{}{},
},
key: "foo",
val: nil,
ok: false,
err: true,
},
// unparsable: nil value
{
cl: Claims{
"foo": nil,
},
key: "foo",
val: nil,
ok: false,
err: true,
},
}
for i, tt := range tests {
val, ok, err := tt.cl.StringsClaim(tt.key)
if tt.err && err == nil {
t.Errorf("case %d: want err=non-nil, got err=nil", i)
} else if !tt.err && err != nil {
t.Errorf("case %d: want err=nil, got err=%v", i, err)
}
if tt.ok != ok {
t.Errorf("case %d: want ok=%v, got ok=%v", i, tt.ok, ok)
}
if !reflect.DeepEqual(tt.val, val) {
t.Errorf("case %d: want val=%v, got val=%v", i, tt.val, val)
}
}
}

View file

@ -1,2 +0,0 @@
// Package jose is DEPRECATED. Use gopkg.in/square/go-jose.v2 instead.
package jose

View file

@ -1,112 +0,0 @@
package jose
import (
"encoding/base64"
"encoding/json"
"fmt"
"strings"
)
const (
HeaderMediaType = "typ"
HeaderKeyAlgorithm = "alg"
HeaderKeyID = "kid"
)
const (
// Encryption Algorithm Header Parameter Values for JWS
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-6
AlgHS256 = "HS256"
AlgHS384 = "HS384"
AlgHS512 = "HS512"
AlgRS256 = "RS256"
AlgRS384 = "RS384"
AlgRS512 = "RS512"
AlgES256 = "ES256"
AlgES384 = "ES384"
AlgES512 = "ES512"
AlgPS256 = "PS256"
AlgPS384 = "PS384"
AlgPS512 = "PS512"
AlgNone = "none"
)
const (
// Algorithm Header Parameter Values for JWE
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#section-4.1
AlgRSA15 = "RSA1_5"
AlgRSAOAEP = "RSA-OAEP"
AlgRSAOAEP256 = "RSA-OAEP-256"
AlgA128KW = "A128KW"
AlgA192KW = "A192KW"
AlgA256KW = "A256KW"
AlgDir = "dir"
AlgECDHES = "ECDH-ES"
AlgECDHESA128KW = "ECDH-ES+A128KW"
AlgECDHESA192KW = "ECDH-ES+A192KW"
AlgECDHESA256KW = "ECDH-ES+A256KW"
AlgA128GCMKW = "A128GCMKW"
AlgA192GCMKW = "A192GCMKW"
AlgA256GCMKW = "A256GCMKW"
AlgPBES2HS256A128KW = "PBES2-HS256+A128KW"
AlgPBES2HS384A192KW = "PBES2-HS384+A192KW"
AlgPBES2HS512A256KW = "PBES2-HS512+A256KW"
)
const (
// Encryption Algorithm Header Parameter Values for JWE
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-22
EncA128CBCHS256 = "A128CBC-HS256"
EncA128CBCHS384 = "A128CBC-HS384"
EncA256CBCHS512 = "A256CBC-HS512"
EncA128GCM = "A128GCM"
EncA192GCM = "A192GCM"
EncA256GCM = "A256GCM"
)
type JOSEHeader map[string]string
func (j JOSEHeader) Validate() error {
if _, exists := j[HeaderKeyAlgorithm]; !exists {
return fmt.Errorf("header missing %q parameter", HeaderKeyAlgorithm)
}
return nil
}
func decodeHeader(seg string) (JOSEHeader, error) {
b, err := decodeSegment(seg)
if err != nil {
return nil, err
}
var h JOSEHeader
err = json.Unmarshal(b, &h)
if err != nil {
return nil, err
}
return h, nil
}
func encodeHeader(h JOSEHeader) (string, error) {
b, err := json.Marshal(h)
if err != nil {
return "", err
}
return encodeSegment(b), nil
}
// Decode JWT specific base64url encoding with padding stripped
func decodeSegment(seg string) ([]byte, error) {
if l := len(seg) % 4; l != 0 {
seg += strings.Repeat("=", 4-l)
}
return base64.URLEncoding.DecodeString(seg)
}
// Encode JWT specific base64url encoding with padding stripped
func encodeSegment(seg []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
}

View file

@ -1,135 +0,0 @@
package jose
import (
"bytes"
"encoding/base64"
"encoding/binary"
"encoding/json"
"math/big"
"strings"
)
// JSON Web Key
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-36#page-5
type JWK struct {
ID string
Type string
Alg string
Use string
Exponent int
Modulus *big.Int
Secret []byte
}
type jwkJSON struct {
ID string `json:"kid"`
Type string `json:"kty"`
Alg string `json:"alg"`
Use string `json:"use"`
Exponent string `json:"e"`
Modulus string `json:"n"`
}
func (j *JWK) MarshalJSON() ([]byte, error) {
t := jwkJSON{
ID: j.ID,
Type: j.Type,
Alg: j.Alg,
Use: j.Use,
Exponent: encodeExponent(j.Exponent),
Modulus: encodeModulus(j.Modulus),
}
return json.Marshal(&t)
}
func (j *JWK) UnmarshalJSON(data []byte) error {
var t jwkJSON
err := json.Unmarshal(data, &t)
if err != nil {
return err
}
e, err := decodeExponent(t.Exponent)
if err != nil {
return err
}
n, err := decodeModulus(t.Modulus)
if err != nil {
return err
}
j.ID = t.ID
j.Type = t.Type
j.Alg = t.Alg
j.Use = t.Use
j.Exponent = e
j.Modulus = n
return nil
}
type JWKSet struct {
Keys []JWK `json:"keys"`
}
func decodeExponent(e string) (int, error) {
decE, err := decodeBase64URLPaddingOptional(e)
if err != nil {
return 0, err
}
var eBytes []byte
if len(decE) < 8 {
eBytes = make([]byte, 8-len(decE), 8)
eBytes = append(eBytes, decE...)
} else {
eBytes = decE
}
eReader := bytes.NewReader(eBytes)
var E uint64
err = binary.Read(eReader, binary.BigEndian, &E)
if err != nil {
return 0, err
}
return int(E), nil
}
func encodeExponent(e int) string {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(e))
var idx int
for ; idx < 8; idx++ {
if b[idx] != 0x0 {
break
}
}
return base64.RawURLEncoding.EncodeToString(b[idx:])
}
// Turns a URL encoded modulus of a key into a big int.
func decodeModulus(n string) (*big.Int, error) {
decN, err := decodeBase64URLPaddingOptional(n)
if err != nil {
return nil, err
}
N := big.NewInt(0)
N.SetBytes(decN)
return N, nil
}
func encodeModulus(n *big.Int) string {
return base64.RawURLEncoding.EncodeToString(n.Bytes())
}
// decodeBase64URLPaddingOptional decodes Base64 whether there is padding or not.
// The stdlib version currently doesn't handle this.
// We can get rid of this is if this bug:
// https://github.com/golang/go/issues/4237
// ever closes.
func decodeBase64URLPaddingOptional(e string) ([]byte, error) {
if m := len(e) % 4; m != 0 {
e += strings.Repeat("=", 4-m)
}
return base64.URLEncoding.DecodeString(e)
}

View file

@ -1,64 +0,0 @@
package jose
import (
"testing"
)
func TestDecodeBase64URLPaddingOptional(t *testing.T) {
tests := []struct {
encoded string
decoded string
err bool
}{
{
// With padding
encoded: "VGVjdG9uaWM=",
decoded: "Tectonic",
},
{
// Without padding
encoded: "VGVjdG9uaWM",
decoded: "Tectonic",
},
{
// Even More padding
encoded: "VGVjdG9uaQ==",
decoded: "Tectoni",
},
{
// And take it away!
encoded: "VGVjdG9uaQ",
decoded: "Tectoni",
},
{
// Too much padding.
encoded: "VGVjdG9uaWNh=",
decoded: "",
err: true,
},
{
// Too much padding.
encoded: "VGVjdG9uaWNh=",
decoded: "",
err: true,
},
}
for i, tt := range tests {
got, err := decodeBase64URLPaddingOptional(tt.encoded)
if tt.err {
if err == nil {
t.Errorf("case %d: expected non-nil err", i)
}
continue
}
if err != nil {
t.Errorf("case %d: want nil err, got: %v", i, err)
}
if string(got) != tt.decoded {
t.Errorf("case %d: want=%q, got=%q", i, tt.decoded, got)
}
}
}

View file

@ -1,51 +0,0 @@
package jose
import (
"fmt"
"strings"
)
type JWS struct {
RawHeader string
Header JOSEHeader
RawPayload string
Payload []byte
Signature []byte
}
// Given a raw encoded JWS token parses it and verifies the structure.
func ParseJWS(raw string) (JWS, error) {
parts := strings.Split(raw, ".")
if len(parts) != 3 {
return JWS{}, fmt.Errorf("malformed JWS, only %d segments", len(parts))
}
rawSig := parts[2]
jws := JWS{
RawHeader: parts[0],
RawPayload: parts[1],
}
header, err := decodeHeader(jws.RawHeader)
if err != nil {
return JWS{}, fmt.Errorf("malformed JWS, unable to decode header, %s", err)
}
if err = header.Validate(); err != nil {
return JWS{}, fmt.Errorf("malformed JWS, %s", err)
}
jws.Header = header
payload, err := decodeSegment(jws.RawPayload)
if err != nil {
return JWS{}, fmt.Errorf("malformed JWS, unable to decode payload: %s", err)
}
jws.Payload = payload
sig, err := decodeSegment(rawSig)
if err != nil {
return JWS{}, fmt.Errorf("malformed JWS, unable to decode signature: %s", err)
}
jws.Signature = sig
return jws, nil
}

View file

@ -1,74 +0,0 @@
package jose
import (
"strings"
"testing"
)
type testCase struct{ t string }
var validInput []testCase
var invalidInput []testCase
func init() {
validInput = []testCase{
{
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
},
}
invalidInput = []testCase{
// empty
{
"",
},
// undecodeable
{
"aaa.bbb.ccc",
},
// missing parts
{
"aaa",
},
// missing parts
{
"aaa.bbb",
},
// too many parts
{
"aaa.bbb.ccc.ddd",
},
// invalid header
// EncodeHeader(map[string]string{"foo": "bar"})
{
"eyJmb28iOiJiYXIifQ.bbb.ccc",
},
}
}
func TestParseJWS(t *testing.T) {
for i, tt := range validInput {
jws, err := ParseJWS(tt.t)
if err != nil {
t.Errorf("test: %d. expected: valid, actual: invalid", i)
}
expectedHeader := strings.Split(tt.t, ".")[0]
if jws.RawHeader != expectedHeader {
t.Errorf("test: %d. expected: %s, actual: %s", i, expectedHeader, jws.RawHeader)
}
expectedPayload := strings.Split(tt.t, ".")[1]
if jws.RawPayload != expectedPayload {
t.Errorf("test: %d. expected: %s, actual: %s", i, expectedPayload, jws.RawPayload)
}
}
for i, tt := range invalidInput {
_, err := ParseJWS(tt.t)
if err == nil {
t.Errorf("test: %d. expected: invalid, actual: valid", i)
}
}
}

View file

@ -1,82 +0,0 @@
package jose
import "strings"
type JWT JWS
func ParseJWT(token string) (jwt JWT, err error) {
jws, err := ParseJWS(token)
if err != nil {
return
}
return JWT(jws), nil
}
func NewJWT(header JOSEHeader, claims Claims) (jwt JWT, err error) {
jwt = JWT{}
jwt.Header = header
jwt.Header[HeaderMediaType] = "JWT"
claimBytes, err := marshalClaims(claims)
if err != nil {
return
}
jwt.Payload = claimBytes
eh, err := encodeHeader(header)
if err != nil {
return
}
jwt.RawHeader = eh
ec, err := encodeClaims(claims)
if err != nil {
return
}
jwt.RawPayload = ec
return
}
func (j *JWT) KeyID() (string, bool) {
kID, ok := j.Header[HeaderKeyID]
return kID, ok
}
func (j *JWT) Claims() (Claims, error) {
return decodeClaims(j.Payload)
}
// Encoded data part of the token which may be signed.
func (j *JWT) Data() string {
return strings.Join([]string{j.RawHeader, j.RawPayload}, ".")
}
// Full encoded JWT token string in format: header.claims.signature
func (j *JWT) Encode() string {
d := j.Data()
s := encodeSegment(j.Signature)
return strings.Join([]string{d, s}, ".")
}
func NewSignedJWT(claims Claims, s Signer) (*JWT, error) {
header := JOSEHeader{
HeaderKeyAlgorithm: s.Alg(),
HeaderKeyID: s.ID(),
}
jwt, err := NewJWT(header, claims)
if err != nil {
return nil, err
}
sig, err := s.Sign([]byte(jwt.Data()))
if err != nil {
return nil, err
}
jwt.Signature = sig
return &jwt, nil
}

View file

@ -1,94 +0,0 @@
package jose
import (
"reflect"
"testing"
)
func TestParseJWT(t *testing.T) {
tests := []struct {
r string
h JOSEHeader
c Claims
}{
{
// Example from JWT spec:
// http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#ExampleJWT
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
JOSEHeader{
HeaderMediaType: "JWT",
HeaderKeyAlgorithm: "HS256",
},
Claims{
"iss": "joe",
// NOTE: test numbers must be floats for equality checks to work since values are converted form interface{} to float64 by default.
"exp": 1300819380.0,
"http://example.com/is_root": true,
},
},
}
for i, tt := range tests {
jwt, err := ParseJWT(tt.r)
if err != nil {
t.Errorf("raw token should parse. test: %d. expected: valid, actual: invalid. err=%v", i, err)
}
if !reflect.DeepEqual(tt.h, jwt.Header) {
t.Errorf("JOSE headers should match. test: %d. expected: %v, actual: %v", i, tt.h, jwt.Header)
}
claims, err := jwt.Claims()
if err != nil {
t.Errorf("test: %d. expected: valid claim parsing. err=%v", i, err)
}
if !reflect.DeepEqual(tt.c, claims) {
t.Errorf("claims should match. test: %d. expected: %v, actual: %v", i, tt.c, claims)
}
enc := jwt.Encode()
if enc != tt.r {
t.Errorf("encoded jwt should match raw jwt. test: %d. expected: %v, actual: %v", i, tt.r, enc)
}
}
}
func TestNewJWTHeaderType(t *testing.T) {
jwt, err := NewJWT(JOSEHeader{}, Claims{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
want := "JWT"
got := jwt.Header[HeaderMediaType]
if want != got {
t.Fatalf("Header %q incorrect: want=%s got=%s", HeaderMediaType, want, got)
}
}
func TestNewJWTHeaderKeyID(t *testing.T) {
jwt, err := NewJWT(JOSEHeader{HeaderKeyID: "foo"}, Claims{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
want := "foo"
got, ok := jwt.KeyID()
if !ok {
t.Fatalf("KeyID not set")
} else if want != got {
t.Fatalf("KeyID incorrect: want=%s got=%s", want, got)
}
}
func TestNewJWTHeaderKeyIDNotSet(t *testing.T) {
jwt, err := NewJWT(JOSEHeader{}, Claims{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if _, ok := jwt.KeyID(); ok {
t.Fatalf("KeyID set, but should not be")
}
}

View file

@ -1,24 +0,0 @@
package jose
import (
"fmt"
)
type Verifier interface {
ID() string
Alg() string
Verify(sig []byte, data []byte) error
}
type Signer interface {
Verifier
Sign(data []byte) (sig []byte, err error)
}
func NewVerifier(jwk JWK) (Verifier, error) {
if jwk.Type != "RSA" {
return nil, fmt.Errorf("unsupported key type %q", jwk.Type)
}
return NewVerifierRSA(jwk)
}

View file

@ -1,67 +0,0 @@
package jose
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"fmt"
)
type VerifierRSA struct {
KeyID string
Hash crypto.Hash
PublicKey rsa.PublicKey
}
type SignerRSA struct {
PrivateKey rsa.PrivateKey
VerifierRSA
}
func NewVerifierRSA(jwk JWK) (*VerifierRSA, error) {
if jwk.Alg != "" && jwk.Alg != "RS256" {
return nil, fmt.Errorf("unsupported key algorithm %q", jwk.Alg)
}
v := VerifierRSA{
KeyID: jwk.ID,
PublicKey: rsa.PublicKey{
N: jwk.Modulus,
E: jwk.Exponent,
},
Hash: crypto.SHA256,
}
return &v, nil
}
func NewSignerRSA(kid string, key rsa.PrivateKey) *SignerRSA {
return &SignerRSA{
PrivateKey: key,
VerifierRSA: VerifierRSA{
KeyID: kid,
PublicKey: key.PublicKey,
Hash: crypto.SHA256,
},
}
}
func (v *VerifierRSA) ID() string {
return v.KeyID
}
func (v *VerifierRSA) Alg() string {
return "RS256"
}
func (v *VerifierRSA) Verify(sig []byte, data []byte) error {
h := v.Hash.New()
h.Write(data)
return rsa.VerifyPKCS1v15(&v.PublicKey, v.Hash, h.Sum(nil), sig)
}
func (s *SignerRSA) Sign(data []byte) ([]byte, error) {
h := s.Hash.New()
h.Write(data)
return rsa.SignPKCS1v15(rand.Reader, &s.PrivateKey, s.Hash, h.Sum(nil))
}

View file

@ -1,2 +0,0 @@
// Package key is DEPRECATED. Use github.com/coreos/go-oidc instead.
package key

View file

@ -1,153 +0,0 @@
package key
import (
"crypto/rand"
"crypto/rsa"
"encoding/hex"
"encoding/json"
"io"
"time"
"github.com/coreos/go-oidc/jose"
)
func NewPublicKey(jwk jose.JWK) *PublicKey {
return &PublicKey{jwk: jwk}
}
type PublicKey struct {
jwk jose.JWK
}
func (k *PublicKey) MarshalJSON() ([]byte, error) {
return json.Marshal(&k.jwk)
}
func (k *PublicKey) UnmarshalJSON(data []byte) error {
var jwk jose.JWK
if err := json.Unmarshal(data, &jwk); err != nil {
return err
}
k.jwk = jwk
return nil
}
func (k *PublicKey) ID() string {
return k.jwk.ID
}
func (k *PublicKey) Verifier() (jose.Verifier, error) {
return jose.NewVerifierRSA(k.jwk)
}
type PrivateKey struct {
KeyID string
PrivateKey *rsa.PrivateKey
}
func (k *PrivateKey) ID() string {
return k.KeyID
}
func (k *PrivateKey) Signer() jose.Signer {
return jose.NewSignerRSA(k.ID(), *k.PrivateKey)
}
func (k *PrivateKey) JWK() jose.JWK {
return jose.JWK{
ID: k.KeyID,
Type: "RSA",
Alg: "RS256",
Use: "sig",
Exponent: k.PrivateKey.PublicKey.E,
Modulus: k.PrivateKey.PublicKey.N,
}
}
type KeySet interface {
ExpiresAt() time.Time
}
type PublicKeySet struct {
keys []PublicKey
index map[string]*PublicKey
expiresAt time.Time
}
func NewPublicKeySet(jwks []jose.JWK, exp time.Time) *PublicKeySet {
keys := make([]PublicKey, len(jwks))
index := make(map[string]*PublicKey)
for i, jwk := range jwks {
keys[i] = *NewPublicKey(jwk)
index[keys[i].ID()] = &keys[i]
}
return &PublicKeySet{
keys: keys,
index: index,
expiresAt: exp,
}
}
func (s *PublicKeySet) ExpiresAt() time.Time {
return s.expiresAt
}
func (s *PublicKeySet) Keys() []PublicKey {
return s.keys
}
func (s *PublicKeySet) Key(id string) *PublicKey {
return s.index[id]
}
type PrivateKeySet struct {
keys []*PrivateKey
ActiveKeyID string
expiresAt time.Time
}
func NewPrivateKeySet(keys []*PrivateKey, exp time.Time) *PrivateKeySet {
return &PrivateKeySet{
keys: keys,
ActiveKeyID: keys[0].ID(),
expiresAt: exp.UTC(),
}
}
func (s *PrivateKeySet) Keys() []*PrivateKey {
return s.keys
}
func (s *PrivateKeySet) ExpiresAt() time.Time {
return s.expiresAt
}
func (s *PrivateKeySet) Active() *PrivateKey {
for i, k := range s.keys {
if k.ID() == s.ActiveKeyID {
return s.keys[i]
}
}
return nil
}
type GeneratePrivateKeyFunc func() (*PrivateKey, error)
func GeneratePrivateKey() (*PrivateKey, error) {
pk, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
keyID := make([]byte, 20)
if _, err := io.ReadFull(rand.Reader, keyID); err != nil {
return nil, err
}
k := PrivateKey{
KeyID: hex.EncodeToString(keyID),
PrivateKey: pk,
}
return &k, nil
}

View file

@ -1,103 +0,0 @@
package key
import (
"crypto/rsa"
"math/big"
"reflect"
"testing"
"time"
"github.com/coreos/go-oidc/jose"
)
func TestPrivateRSAKeyJWK(t *testing.T) {
n := big.NewInt(int64(17))
if n == nil {
panic("NewInt returned nil")
}
k := &PrivateKey{
KeyID: "foo",
PrivateKey: &rsa.PrivateKey{
PublicKey: rsa.PublicKey{N: n, E: 65537},
},
}
want := jose.JWK{
ID: "foo",
Type: "RSA",
Alg: "RS256",
Use: "sig",
Modulus: n,
Exponent: 65537,
}
got := k.JWK()
if !reflect.DeepEqual(want, got) {
t.Fatalf("JWK mismatch: want=%#v got=%#v", want, got)
}
}
func TestPublicKeySetKey(t *testing.T) {
n := big.NewInt(int64(17))
if n == nil {
panic("NewInt returned nil")
}
k := jose.JWK{
ID: "foo",
Type: "RSA",
Alg: "RS256",
Use: "sig",
Modulus: n,
Exponent: 65537,
}
now := time.Now().UTC()
ks := NewPublicKeySet([]jose.JWK{k}, now)
want := &PublicKey{jwk: k}
got := ks.Key("foo")
if !reflect.DeepEqual(want, got) {
t.Errorf("Unexpected response from PublicKeySet.Key: want=%#v got=%#v", want, got)
}
got = ks.Key("bar")
if got != nil {
t.Errorf("Expected nil response from PublicKeySet.Key, got %#v", got)
}
}
func TestPublicKeyMarshalJSON(t *testing.T) {
k := jose.JWK{
ID: "foo",
Type: "RSA",
Alg: "RS256",
Use: "sig",
Modulus: big.NewInt(int64(17)),
Exponent: 65537,
}
want := `{"kid":"foo","kty":"RSA","alg":"RS256","use":"sig","e":"AQAB","n":"EQ"}`
pubKey := NewPublicKey(k)
gotBytes, err := pubKey.MarshalJSON()
if err != nil {
t.Fatalf("failed to marshal public key: %v", err)
}
got := string(gotBytes)
if got != want {
t.Errorf("got != want:\n%s\n%s", got, want)
}
}
func TestGeneratePrivateKeyIDs(t *testing.T) {
key1, err := GeneratePrivateKey()
if err != nil {
t.Fatalf("GeneratePrivateKey(): %v", err)
}
key2, err := GeneratePrivateKey()
if err != nil {
t.Fatalf("GeneratePrivateKey(): %v", err)
}
if key1.KeyID == key2.KeyID {
t.Fatalf("expected different keys to have different key IDs")
}
}

View file

@ -1,99 +0,0 @@
package key
import (
"errors"
"time"
"github.com/jonboulle/clockwork"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/pkg/health"
)
type PrivateKeyManager interface {
ExpiresAt() time.Time
Signer() (jose.Signer, error)
JWKs() ([]jose.JWK, error)
PublicKeys() ([]PublicKey, error)
WritableKeySetRepo
health.Checkable
}
func NewPrivateKeyManager() PrivateKeyManager {
return &privateKeyManager{
clock: clockwork.NewRealClock(),
}
}
type privateKeyManager struct {
keySet *PrivateKeySet
clock clockwork.Clock
}
func (m *privateKeyManager) ExpiresAt() time.Time {
if m.keySet == nil {
return m.clock.Now().UTC()
}
return m.keySet.ExpiresAt()
}
func (m *privateKeyManager) Signer() (jose.Signer, error) {
if err := m.Healthy(); err != nil {
return nil, err
}
return m.keySet.Active().Signer(), nil
}
func (m *privateKeyManager) JWKs() ([]jose.JWK, error) {
if err := m.Healthy(); err != nil {
return nil, err
}
keys := m.keySet.Keys()
jwks := make([]jose.JWK, len(keys))
for i, k := range keys {
jwks[i] = k.JWK()
}
return jwks, nil
}
func (m *privateKeyManager) PublicKeys() ([]PublicKey, error) {
jwks, err := m.JWKs()
if err != nil {
return nil, err
}
keys := make([]PublicKey, len(jwks))
for i, jwk := range jwks {
keys[i] = *NewPublicKey(jwk)
}
return keys, nil
}
func (m *privateKeyManager) Healthy() error {
if m.keySet == nil {
return errors.New("private key manager uninitialized")
}
if len(m.keySet.Keys()) == 0 {
return errors.New("private key manager zero keys")
}
if m.keySet.ExpiresAt().Before(m.clock.Now().UTC()) {
return errors.New("private key manager keys expired")
}
return nil
}
func (m *privateKeyManager) Set(keySet KeySet) error {
privKeySet, ok := keySet.(*PrivateKeySet)
if !ok {
return errors.New("unable to cast to PrivateKeySet")
}
m.keySet = privKeySet
return nil
}

View file

@ -1,225 +0,0 @@
package key
import (
"crypto/rsa"
"math/big"
"reflect"
"strconv"
"testing"
"time"
"github.com/jonboulle/clockwork"
"github.com/coreos/go-oidc/jose"
)
var (
jwk1 jose.JWK
jwk2 jose.JWK
jwk3 jose.JWK
)
func init() {
jwk1 = jose.JWK{
ID: "1",
Type: "RSA",
Alg: "RS256",
Use: "sig",
Modulus: big.NewInt(1),
Exponent: 65537,
}
jwk2 = jose.JWK{
ID: "2",
Type: "RSA",
Alg: "RS256",
Use: "sig",
Modulus: big.NewInt(2),
Exponent: 65537,
}
jwk3 = jose.JWK{
ID: "3",
Type: "RSA",
Alg: "RS256",
Use: "sig",
Modulus: big.NewInt(3),
Exponent: 65537,
}
}
func generatePrivateKeyStatic(t *testing.T, idAndN int) *PrivateKey {
n := big.NewInt(int64(idAndN))
if n == nil {
t.Fatalf("Call to NewInt(%d) failed", idAndN)
}
pk := &rsa.PrivateKey{
PublicKey: rsa.PublicKey{N: n, E: 65537},
}
return &PrivateKey{
KeyID: strconv.Itoa(idAndN),
PrivateKey: pk,
}
}
func TestPrivateKeyManagerJWKsRotate(t *testing.T) {
k1 := generatePrivateKeyStatic(t, 1)
k2 := generatePrivateKeyStatic(t, 2)
k3 := generatePrivateKeyStatic(t, 3)
km := NewPrivateKeyManager()
err := km.Set(&PrivateKeySet{
keys: []*PrivateKey{k1, k2, k3},
ActiveKeyID: k1.KeyID,
expiresAt: time.Now().Add(time.Minute),
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
want := []jose.JWK{jwk1, jwk2, jwk3}
got, err := km.JWKs()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !reflect.DeepEqual(want, got) {
t.Fatalf("JWK mismatch: want=%#v got=%#v", want, got)
}
}
func TestPrivateKeyManagerSigner(t *testing.T) {
k := generatePrivateKeyStatic(t, 13)
km := NewPrivateKeyManager()
err := km.Set(&PrivateKeySet{
keys: []*PrivateKey{k},
ActiveKeyID: k.KeyID,
expiresAt: time.Now().Add(time.Minute),
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
signer, err := km.Signer()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
wantID := "13"
gotID := signer.ID()
if wantID != gotID {
t.Fatalf("Signer has incorrect ID: want=%s got=%s", wantID, gotID)
}
}
func TestPrivateKeyManagerHealthyFail(t *testing.T) {
keyFixture := generatePrivateKeyStatic(t, 1)
tests := []*privateKeyManager{
// keySet nil
&privateKeyManager{
keySet: nil,
clock: clockwork.NewRealClock(),
},
// zero keys
&privateKeyManager{
keySet: &PrivateKeySet{
keys: []*PrivateKey{},
expiresAt: time.Now().Add(time.Minute),
},
clock: clockwork.NewRealClock(),
},
// key set expired
&privateKeyManager{
keySet: &PrivateKeySet{
keys: []*PrivateKey{keyFixture},
expiresAt: time.Now().Add(-1 * time.Minute),
},
clock: clockwork.NewRealClock(),
},
}
for i, tt := range tests {
if err := tt.Healthy(); err == nil {
t.Errorf("case %d: nil error", i)
}
}
}
func TestPrivateKeyManagerHealthyFailsOtherMethods(t *testing.T) {
km := NewPrivateKeyManager()
if _, err := km.JWKs(); err == nil {
t.Fatalf("Expected non-nil error")
}
if _, err := km.Signer(); err == nil {
t.Fatalf("Expected non-nil error")
}
}
func TestPrivateKeyManagerExpiresAt(t *testing.T) {
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
k := generatePrivateKeyStatic(t, 17)
km := &privateKeyManager{
clock: fc,
}
want := fc.Now().UTC()
got := km.ExpiresAt()
if want != got {
t.Fatalf("Incorrect expiration time: want=%v got=%v", want, got)
}
err := km.Set(&PrivateKeySet{
keys: []*PrivateKey{k},
ActiveKeyID: k.KeyID,
expiresAt: now.Add(2 * time.Minute),
})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
want = fc.Now().UTC().Add(2 * time.Minute)
got = km.ExpiresAt()
if want != got {
t.Fatalf("Incorrect expiration time: want=%v got=%v", want, got)
}
}
func TestPublicKeys(t *testing.T) {
km := NewPrivateKeyManager()
k1 := generatePrivateKeyStatic(t, 1)
k2 := generatePrivateKeyStatic(t, 2)
k3 := generatePrivateKeyStatic(t, 3)
tests := [][]*PrivateKey{
[]*PrivateKey{k1},
[]*PrivateKey{k1, k2},
[]*PrivateKey{k1, k2, k3},
}
for i, tt := range tests {
ks := &PrivateKeySet{
keys: tt,
expiresAt: time.Now().Add(time.Hour),
}
km.Set(ks)
jwks, err := km.JWKs()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
pks := NewPublicKeySet(jwks, time.Now().Add(time.Hour))
want := pks.Keys()
got, err := km.PublicKeys()
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if !reflect.DeepEqual(want, got) {
t.Errorf("case %d: Invalid public keys: want=%v got=%v", i, want, got)
}
}
}

View file

@ -1,55 +0,0 @@
package key
import (
"errors"
"sync"
)
var ErrorNoKeys = errors.New("no keys found")
type WritableKeySetRepo interface {
Set(KeySet) error
}
type ReadableKeySetRepo interface {
Get() (KeySet, error)
}
type PrivateKeySetRepo interface {
WritableKeySetRepo
ReadableKeySetRepo
}
func NewPrivateKeySetRepo() PrivateKeySetRepo {
return &memPrivateKeySetRepo{}
}
type memPrivateKeySetRepo struct {
mu sync.RWMutex
pks PrivateKeySet
}
func (r *memPrivateKeySetRepo) Set(ks KeySet) error {
pks, ok := ks.(*PrivateKeySet)
if !ok {
return errors.New("unable to cast to PrivateKeySet")
} else if pks == nil {
return errors.New("nil KeySet")
}
r.mu.Lock()
defer r.mu.Unlock()
r.pks = *pks
return nil
}
func (r *memPrivateKeySetRepo) Get() (KeySet, error) {
r.mu.RLock()
defer r.mu.RUnlock()
if r.pks.keys == nil {
return nil, ErrorNoKeys
}
return KeySet(&r.pks), nil
}

View file

@ -1,159 +0,0 @@
package key
import (
"errors"
"log"
"time"
ptime "github.com/coreos/pkg/timeutil"
"github.com/jonboulle/clockwork"
)
var (
ErrorPrivateKeysExpired = errors.New("private keys have expired")
)
func NewPrivateKeyRotator(repo PrivateKeySetRepo, ttl time.Duration) *PrivateKeyRotator {
return &PrivateKeyRotator{
repo: repo,
ttl: ttl,
keep: 2,
generateKey: GeneratePrivateKey,
clock: clockwork.NewRealClock(),
}
}
type PrivateKeyRotator struct {
repo PrivateKeySetRepo
generateKey GeneratePrivateKeyFunc
clock clockwork.Clock
keep int
ttl time.Duration
}
func (r *PrivateKeyRotator) expiresAt() time.Time {
return r.clock.Now().UTC().Add(r.ttl)
}
func (r *PrivateKeyRotator) Healthy() error {
pks, err := r.privateKeySet()
if err != nil {
return err
}
if r.clock.Now().After(pks.ExpiresAt()) {
return ErrorPrivateKeysExpired
}
return nil
}
func (r *PrivateKeyRotator) privateKeySet() (*PrivateKeySet, error) {
ks, err := r.repo.Get()
if err != nil {
return nil, err
}
pks, ok := ks.(*PrivateKeySet)
if !ok {
return nil, errors.New("unable to cast to PrivateKeySet")
}
return pks, nil
}
func (r *PrivateKeyRotator) nextRotation() (time.Duration, error) {
pks, err := r.privateKeySet()
if err == ErrorNoKeys {
return 0, nil
}
if err != nil {
return 0, err
}
now := r.clock.Now()
// Ideally, we want to rotate after half the TTL has elapsed.
idealRotationTime := pks.ExpiresAt().Add(-r.ttl / 2)
// If we are past the ideal rotation time, rotate immediatly.
return max(0, idealRotationTime.Sub(now)), nil
}
func max(a, b time.Duration) time.Duration {
if a > b {
return a
}
return b
}
func (r *PrivateKeyRotator) Run() chan struct{} {
attempt := func() {
k, err := r.generateKey()
if err != nil {
log.Printf("go-oidc: failed generating signing key: %v", err)
return
}
exp := r.expiresAt()
if err := rotatePrivateKeys(r.repo, k, r.keep, exp); err != nil {
log.Printf("go-oidc: key rotation failed: %v", err)
return
}
}
stop := make(chan struct{})
go func() {
for {
var nextRotation time.Duration
var sleep time.Duration
var err error
for {
if nextRotation, err = r.nextRotation(); err == nil {
break
}
sleep = ptime.ExpBackoff(sleep, time.Minute)
log.Printf("go-oidc: error getting nextRotation, retrying in %v: %v", sleep, err)
time.Sleep(sleep)
}
select {
case <-r.clock.After(nextRotation):
attempt()
case <-stop:
return
}
}
}()
return stop
}
func rotatePrivateKeys(repo PrivateKeySetRepo, k *PrivateKey, keep int, exp time.Time) error {
ks, err := repo.Get()
if err != nil && err != ErrorNoKeys {
return err
}
var keys []*PrivateKey
if ks != nil {
pks, ok := ks.(*PrivateKeySet)
if !ok {
return errors.New("unable to cast to PrivateKeySet")
}
keys = pks.Keys()
}
keys = append([]*PrivateKey{k}, keys...)
if l := len(keys); l > keep {
keys = keys[0:keep]
}
nks := PrivateKeySet{
keys: keys,
ActiveKeyID: k.ID(),
expiresAt: exp,
}
return repo.Set(KeySet(&nks))
}

View file

@ -1,311 +0,0 @@
package key
import (
"reflect"
"testing"
"time"
"github.com/jonboulle/clockwork"
)
func generatePrivateKeySerialFunc(t *testing.T) GeneratePrivateKeyFunc {
var n int
return func() (*PrivateKey, error) {
n++
return generatePrivateKeyStatic(t, n), nil
}
}
func TestRotate(t *testing.T) {
now := time.Now()
k1 := generatePrivateKeyStatic(t, 1)
k2 := generatePrivateKeyStatic(t, 2)
k3 := generatePrivateKeyStatic(t, 3)
tests := []struct {
start *PrivateKeySet
key *PrivateKey
keep int
exp time.Time
want *PrivateKeySet
}{
// start with nil keys
{
start: nil,
key: k1,
keep: 2,
exp: now.Add(time.Second),
want: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(time.Second),
},
},
// start with zero keys
{
start: &PrivateKeySet{},
key: k1,
keep: 2,
exp: now.Add(time.Second),
want: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(time.Second),
},
},
// add second key
{
start: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now,
},
key: k2,
keep: 2,
exp: now.Add(time.Second),
want: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(time.Second),
},
},
// rotate in third key
{
start: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now,
},
key: k3,
keep: 2,
exp: now.Add(time.Second),
want: &PrivateKeySet{
keys: []*PrivateKey{k3, k2},
ActiveKeyID: k3.KeyID,
expiresAt: now.Add(time.Second),
},
},
}
for i, tt := range tests {
repo := NewPrivateKeySetRepo()
if tt.start != nil {
err := repo.Set(tt.start)
if err != nil {
t.Fatalf("case %d: unexpected error: %v", i, err)
}
}
rotatePrivateKeys(repo, tt.key, tt.keep, tt.exp)
got, err := repo.Get()
if err != nil {
t.Errorf("case %d: unexpected error: %v", i, err)
continue
}
if !reflect.DeepEqual(tt.want, got) {
t.Errorf("case %d: unexpected result: want=%#v got=%#v", i, tt.want, got)
}
}
}
func TestPrivateKeyRotatorRun(t *testing.T) {
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
k1 := generatePrivateKeyStatic(t, 1)
k2 := generatePrivateKeyStatic(t, 2)
k3 := generatePrivateKeyStatic(t, 3)
k4 := generatePrivateKeyStatic(t, 4)
kRepo := NewPrivateKeySetRepo()
krot := NewPrivateKeyRotator(kRepo, 4*time.Second)
krot.clock = fc
krot.generateKey = generatePrivateKeySerialFunc(t)
steps := []*PrivateKeySet{
&PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(4 * time.Second),
},
&PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(6 * time.Second),
},
&PrivateKeySet{
keys: []*PrivateKey{k3, k2},
ActiveKeyID: k3.KeyID,
expiresAt: now.Add(8 * time.Second),
},
&PrivateKeySet{
keys: []*PrivateKey{k4, k3},
ActiveKeyID: k4.KeyID,
expiresAt: now.Add(10 * time.Second),
},
}
stop := krot.Run()
defer close(stop)
for i, st := range steps {
// wait for the rotater to get sleepy
fc.BlockUntil(1)
got, err := kRepo.Get()
if err != nil {
t.Fatalf("step %d: unexpected error: %v", i, err)
}
if !reflect.DeepEqual(st, got) {
t.Fatalf("step %d: unexpected state: want=%#v got=%#v", i, st, got)
}
fc.Advance(2 * time.Second)
}
}
func TestPrivateKeyRotatorExpiresAt(t *testing.T) {
fc := clockwork.NewFakeClock()
krot := &PrivateKeyRotator{
clock: fc,
ttl: time.Minute,
}
got := krot.expiresAt()
want := fc.Now().UTC().Add(time.Minute)
if !reflect.DeepEqual(want, got) {
t.Errorf("Incorrect expiration time: want=%v got=%v", want, got)
}
}
func TestNextRotation(t *testing.T) {
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
tests := []struct {
expiresAt time.Time
ttl time.Duration
numKeys int
expected time.Duration
}{
{
// closest to prod
expiresAt: now.Add(time.Hour * 24),
ttl: time.Hour * 24,
numKeys: 2,
expected: time.Hour * 12,
},
{
expiresAt: now.Add(time.Hour * 2),
ttl: time.Hour * 4,
numKeys: 2,
expected: 0,
},
{
// No keys.
expiresAt: now.Add(time.Hour * 2),
ttl: time.Hour * 4,
numKeys: 0,
expected: 0,
},
{
// Nil keyset.
expiresAt: now.Add(time.Hour * 2),
ttl: time.Hour * 4,
numKeys: -1,
expected: 0,
},
{
// KeySet expired.
expiresAt: now.Add(time.Hour * -2),
ttl: time.Hour * 4,
numKeys: 2,
expected: 0,
},
{
// Expiry past now + TTL
expiresAt: now.Add(time.Hour * 5),
ttl: time.Hour * 4,
numKeys: 2,
expected: 3 * time.Hour,
},
}
for i, tt := range tests {
kRepo := NewPrivateKeySetRepo()
krot := NewPrivateKeyRotator(kRepo, tt.ttl)
krot.clock = fc
pks := &PrivateKeySet{
expiresAt: tt.expiresAt,
}
if tt.numKeys != -1 {
for n := 0; n < tt.numKeys; n++ {
pks.keys = append(pks.keys, generatePrivateKeyStatic(t, n))
}
err := kRepo.Set(pks)
if err != nil {
t.Fatalf("case %d: unexpected error: %v", i, err)
}
}
actual, err := krot.nextRotation()
if err != nil {
t.Errorf("case %d: error calling shouldRotate(): %v", i, err)
}
if actual != tt.expected {
t.Errorf("case %d: actual == %v, want %v", i, actual, tt.expected)
}
}
}
func TestHealthy(t *testing.T) {
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
tests := []struct {
expiresAt time.Time
numKeys int
expected error
}{
{
expiresAt: now.Add(time.Hour),
numKeys: 2,
expected: nil,
},
{
expiresAt: now.Add(time.Hour),
numKeys: -1,
expected: ErrorNoKeys,
},
{
expiresAt: now.Add(time.Hour),
numKeys: 0,
expected: ErrorNoKeys,
},
{
expiresAt: now.Add(-time.Hour),
numKeys: 2,
expected: ErrorPrivateKeysExpired,
},
}
for i, tt := range tests {
kRepo := NewPrivateKeySetRepo()
krot := NewPrivateKeyRotator(kRepo, time.Hour)
krot.clock = fc
pks := &PrivateKeySet{
expiresAt: tt.expiresAt,
}
if tt.numKeys != -1 {
for n := 0; n < tt.numKeys; n++ {
pks.keys = append(pks.keys, generatePrivateKeyStatic(t, n))
}
err := kRepo.Set(pks)
if err != nil {
t.Fatalf("case %d: unexpected error: %v", i, err)
}
}
if err := krot.Healthy(); err != tt.expected {
t.Errorf("case %d: got==%q, want==%q", i, err, tt.expected)
}
}
}

View file

@ -1,91 +0,0 @@
package key
import (
"errors"
"log"
"time"
"github.com/jonboulle/clockwork"
"github.com/coreos/pkg/timeutil"
)
func NewKeySetSyncer(r ReadableKeySetRepo, w WritableKeySetRepo) *KeySetSyncer {
return &KeySetSyncer{
readable: r,
writable: w,
clock: clockwork.NewRealClock(),
}
}
type KeySetSyncer struct {
readable ReadableKeySetRepo
writable WritableKeySetRepo
clock clockwork.Clock
}
func (s *KeySetSyncer) Run() chan struct{} {
stop := make(chan struct{})
go func() {
var failing bool
var next time.Duration
for {
exp, err := syncKeySet(s.readable, s.writable, s.clock)
if err != nil || exp == 0 {
if !failing {
failing = true
next = time.Second
} else {
next = timeutil.ExpBackoff(next, time.Minute)
}
if exp == 0 {
log.Printf("Synced to already expired key set, retrying in %v: %v", next, err)
} else {
log.Printf("Failed syncing key set, retrying in %v: %v", next, err)
}
} else {
failing = false
next = exp / 2
}
select {
case <-s.clock.After(next):
continue
case <-stop:
return
}
}
}()
return stop
}
func Sync(r ReadableKeySetRepo, w WritableKeySetRepo) (time.Duration, error) {
return syncKeySet(r, w, clockwork.NewRealClock())
}
// syncKeySet copies the keyset from r to the KeySet at w and returns the duration in which the KeySet will expire.
// If keyset has already expired, returns a zero duration.
func syncKeySet(r ReadableKeySetRepo, w WritableKeySetRepo, clock clockwork.Clock) (exp time.Duration, err error) {
var ks KeySet
ks, err = r.Get()
if err != nil {
return
}
if ks == nil {
err = errors.New("no source KeySet")
return
}
if err = w.Set(ks); err != nil {
return
}
now := clock.Now()
if ks.ExpiresAt().After(now) {
exp = ks.ExpiresAt().Sub(now)
}
return
}

View file

@ -1,214 +0,0 @@
package key
import (
"errors"
"reflect"
"sync"
"testing"
"time"
"github.com/jonboulle/clockwork"
)
type staticReadableKeySetRepo struct {
mu sync.RWMutex
ks KeySet
err error
}
func (r *staticReadableKeySetRepo) Get() (KeySet, error) {
r.mu.RLock()
defer r.mu.RUnlock()
return r.ks, r.err
}
func (r *staticReadableKeySetRepo) set(ks KeySet, err error) {
r.mu.Lock()
defer r.mu.Unlock()
r.ks, r.err = ks, err
}
func TestKeySyncerSync(t *testing.T) {
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
k1 := generatePrivateKeyStatic(t, 1)
k2 := generatePrivateKeyStatic(t, 2)
k3 := generatePrivateKeyStatic(t, 3)
steps := []struct {
fromKS KeySet
fromErr error
advance time.Duration
want *PrivateKeySet
}{
// on startup, first sync should trigger within a second
{
fromKS: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(10 * time.Second),
},
advance: time.Second,
want: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(10 * time.Second),
},
},
// advance halfway into TTL, triggering sync
{
fromKS: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(15 * time.Second),
},
advance: 5 * time.Second,
want: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(15 * time.Second),
},
},
// advance halfway into TTL, triggering sync that fails
{
fromErr: errors.New("fail!"),
advance: 10 * time.Second,
want: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(15 * time.Second),
},
},
// sync retries quickly, and succeeds with fixed data
{
fromKS: &PrivateKeySet{
keys: []*PrivateKey{k3, k2, k1},
ActiveKeyID: k3.KeyID,
expiresAt: now.Add(25 * time.Second),
},
advance: 3 * time.Second,
want: &PrivateKeySet{
keys: []*PrivateKey{k3, k2, k1},
ActiveKeyID: k3.KeyID,
expiresAt: now.Add(25 * time.Second),
},
},
}
from := &staticReadableKeySetRepo{}
to := NewPrivateKeySetRepo()
syncer := NewKeySetSyncer(from, to)
syncer.clock = fc
stop := syncer.Run()
defer close(stop)
for i, st := range steps {
from.set(st.fromKS, st.fromErr)
fc.Advance(st.advance)
fc.BlockUntil(1)
ks, err := to.Get()
if err != nil {
t.Fatalf("step %d: unable to get keys: %v", i, err)
}
if !reflect.DeepEqual(st.want, ks) {
t.Fatalf("step %d: incorrect state: want=%#v got=%#v", i, st.want, ks)
}
}
}
func TestSync(t *testing.T) {
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
k1 := generatePrivateKeyStatic(t, 1)
k2 := generatePrivateKeyStatic(t, 2)
k3 := generatePrivateKeyStatic(t, 3)
tests := []struct {
keySet *PrivateKeySet
want time.Duration
}{
{
keySet: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(time.Minute),
},
want: time.Minute,
},
{
keySet: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(time.Minute),
},
want: time.Minute,
},
{
keySet: &PrivateKeySet{
keys: []*PrivateKey{k3, k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(time.Minute),
},
want: time.Minute,
},
{
keySet: &PrivateKeySet{
keys: []*PrivateKey{k2, k1},
ActiveKeyID: k2.KeyID,
expiresAt: now.Add(time.Hour),
},
want: time.Hour,
},
{
keySet: &PrivateKeySet{
keys: []*PrivateKey{k1},
ActiveKeyID: k1.KeyID,
expiresAt: now.Add(-time.Hour),
},
want: 0,
},
}
for i, tt := range tests {
from := NewPrivateKeySetRepo()
to := NewPrivateKeySetRepo()
err := from.Set(tt.keySet)
if err != nil {
t.Errorf("case %d: unexpected error: %v", i, err)
continue
}
exp, err := syncKeySet(from, to, fc)
if err != nil {
t.Errorf("case %d: unexpected error: %v", i, err)
continue
}
if tt.want != exp {
t.Errorf("case %d: want=%v got=%v", i, tt.want, exp)
}
}
}
func TestSyncFail(t *testing.T) {
tests := []error{
nil,
errors.New("fail!"),
}
for i, tt := range tests {
from := &staticReadableKeySetRepo{ks: nil, err: tt}
to := NewPrivateKeySetRepo()
if _, err := syncKeySet(from, to, clockwork.NewFakeClock()); err == nil {
t.Errorf("case %d: expected non-nil error", i)
}
}
}

View file

@ -1,2 +0,0 @@
// Package oauth2 is DEPRECATED. Use golang.org/x/oauth instead.
package oauth2

View file

@ -1,29 +0,0 @@
package oauth2
const (
ErrorAccessDenied = "access_denied"
ErrorInvalidClient = "invalid_client"
ErrorInvalidGrant = "invalid_grant"
ErrorInvalidRequest = "invalid_request"
ErrorServerError = "server_error"
ErrorUnauthorizedClient = "unauthorized_client"
ErrorUnsupportedGrantType = "unsupported_grant_type"
ErrorUnsupportedResponseType = "unsupported_response_type"
)
type Error struct {
Type string `json:"error"`
Description string `json:"error_description,omitempty"`
State string `json:"state,omitempty"`
}
func (e *Error) Error() string {
if e.Description != "" {
return e.Type + ": " + e.Description
}
return e.Type
}
func NewError(typ string) *Error {
return &Error{Type: typ}
}

View file

@ -1,416 +0,0 @@
package oauth2
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"mime"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
phttp "github.com/coreos/go-oidc/http"
)
// ResponseTypesEqual compares two response_type values. If either
// contains a space, it is treated as an unordered list. For example,
// comparing "code id_token" and "id_token code" would evaluate to true.
func ResponseTypesEqual(r1, r2 string) bool {
if !strings.Contains(r1, " ") || !strings.Contains(r2, " ") {
// fast route, no split needed
return r1 == r2
}
// split, sort, and compare
r1Fields := strings.Fields(r1)
r2Fields := strings.Fields(r2)
if len(r1Fields) != len(r2Fields) {
return false
}
sort.Strings(r1Fields)
sort.Strings(r2Fields)
for i, r1Field := range r1Fields {
if r1Field != r2Fields[i] {
return false
}
}
return true
}
const (
// OAuth2.0 response types registered by OIDC.
//
// See: https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#RegistryContents
ResponseTypeCode = "code"
ResponseTypeCodeIDToken = "code id_token"
ResponseTypeCodeIDTokenToken = "code id_token token"
ResponseTypeIDToken = "id_token"
ResponseTypeIDTokenToken = "id_token token"
ResponseTypeToken = "token"
ResponseTypeNone = "none"
)
const (
GrantTypeAuthCode = "authorization_code"
GrantTypeClientCreds = "client_credentials"
GrantTypeUserCreds = "password"
GrantTypeImplicit = "implicit"
GrantTypeRefreshToken = "refresh_token"
AuthMethodClientSecretPost = "client_secret_post"
AuthMethodClientSecretBasic = "client_secret_basic"
AuthMethodClientSecretJWT = "client_secret_jwt"
AuthMethodPrivateKeyJWT = "private_key_jwt"
)
type Config struct {
Credentials ClientCredentials
Scope []string
RedirectURL string
AuthURL string
TokenURL string
// Must be one of the AuthMethodXXX methods above. Right now, only
// AuthMethodClientSecretPost and AuthMethodClientSecretBasic are supported.
AuthMethod string
}
type Client struct {
hc phttp.Client
creds ClientCredentials
scope []string
authURL *url.URL
redirectURL *url.URL
tokenURL *url.URL
authMethod string
}
type ClientCredentials struct {
ID string
Secret string
}
func NewClient(hc phttp.Client, cfg Config) (c *Client, err error) {
if len(cfg.Credentials.ID) == 0 {
err = errors.New("missing client id")
return
}
if len(cfg.Credentials.Secret) == 0 {
err = errors.New("missing client secret")
return
}
if cfg.AuthMethod == "" {
cfg.AuthMethod = AuthMethodClientSecretBasic
} else if cfg.AuthMethod != AuthMethodClientSecretPost && cfg.AuthMethod != AuthMethodClientSecretBasic {
err = fmt.Errorf("auth method %q is not supported", cfg.AuthMethod)
return
}
au, err := phttp.ParseNonEmptyURL(cfg.AuthURL)
if err != nil {
return
}
tu, err := phttp.ParseNonEmptyURL(cfg.TokenURL)
if err != nil {
return
}
// Allow empty redirect URL in the case where the client
// only needs to verify a given token.
ru, err := url.Parse(cfg.RedirectURL)
if err != nil {
return
}
c = &Client{
creds: cfg.Credentials,
scope: cfg.Scope,
redirectURL: ru,
authURL: au,
tokenURL: tu,
hc: hc,
authMethod: cfg.AuthMethod,
}
return
}
// Return the embedded HTTP client
func (c *Client) HttpClient() phttp.Client {
return c.hc
}
// Generate the url for initial redirect to oauth provider.
func (c *Client) AuthCodeURL(state, accessType, prompt string) string {
v := c.commonURLValues()
v.Set("state", state)
if strings.ToLower(accessType) == "offline" {
v.Set("access_type", "offline")
}
if prompt != "" {
v.Set("prompt", prompt)
}
v.Set("response_type", "code")
q := v.Encode()
u := *c.authURL
if u.RawQuery == "" {
u.RawQuery = q
} else {
u.RawQuery += "&" + q
}
return u.String()
}
func (c *Client) commonURLValues() url.Values {
return url.Values{
"redirect_uri": {c.redirectURL.String()},
"scope": {strings.Join(c.scope, " ")},
"client_id": {c.creds.ID},
}
}
func (c *Client) newAuthenticatedRequest(urlToken string, values url.Values) (*http.Request, error) {
var req *http.Request
var err error
switch c.authMethod {
case AuthMethodClientSecretPost:
values.Set("client_secret", c.creds.Secret)
req, err = http.NewRequest("POST", urlToken, strings.NewReader(values.Encode()))
if err != nil {
return nil, err
}
case AuthMethodClientSecretBasic:
req, err = http.NewRequest("POST", urlToken, strings.NewReader(values.Encode()))
if err != nil {
return nil, err
}
encodedID := url.QueryEscape(c.creds.ID)
encodedSecret := url.QueryEscape(c.creds.Secret)
req.SetBasicAuth(encodedID, encodedSecret)
default:
panic("misconfigured client: auth method not supported")
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
return req, nil
}
// ClientCredsToken posts the client id and secret to obtain a token scoped to the OAuth2 client via the "client_credentials" grant type.
// May not be supported by all OAuth2 servers.
func (c *Client) ClientCredsToken(scope []string) (result TokenResponse, err error) {
v := url.Values{
"scope": {strings.Join(scope, " ")},
"grant_type": {GrantTypeClientCreds},
}
req, err := c.newAuthenticatedRequest(c.tokenURL.String(), v)
if err != nil {
return
}
resp, err := c.hc.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
return parseTokenResponse(resp)
}
// UserCredsToken posts the username and password to obtain a token scoped to the OAuth2 client via the "password" grant_type
// May not be supported by all OAuth2 servers.
func (c *Client) UserCredsToken(username, password string) (result TokenResponse, err error) {
v := url.Values{
"scope": {strings.Join(c.scope, " ")},
"grant_type": {GrantTypeUserCreds},
"username": {username},
"password": {password},
}
req, err := c.newAuthenticatedRequest(c.tokenURL.String(), v)
if err != nil {
return
}
resp, err := c.hc.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
return parseTokenResponse(resp)
}
// RequestToken requests a token from the Token Endpoint with the specified grantType.
// If 'grantType' == GrantTypeAuthCode, then 'value' should be the authorization code.
// If 'grantType' == GrantTypeRefreshToken, then 'value' should be the refresh token.
func (c *Client) RequestToken(grantType, value string) (result TokenResponse, err error) {
v := c.commonURLValues()
v.Set("grant_type", grantType)
v.Set("client_secret", c.creds.Secret)
switch grantType {
case GrantTypeAuthCode:
v.Set("code", value)
case GrantTypeRefreshToken:
v.Set("refresh_token", value)
default:
err = fmt.Errorf("unsupported grant_type: %v", grantType)
return
}
req, err := c.newAuthenticatedRequest(c.tokenURL.String(), v)
if err != nil {
return
}
resp, err := c.hc.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
return parseTokenResponse(resp)
}
func parseTokenResponse(resp *http.Response) (result TokenResponse, err error) {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
badStatusCode := resp.StatusCode < 200 || resp.StatusCode > 299
contentType, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
if err != nil {
return
}
result = TokenResponse{
RawBody: body,
}
newError := func(typ, desc, state string) error {
if typ == "" {
return fmt.Errorf("unrecognized error %s", body)
}
return &Error{typ, desc, state}
}
if contentType == "application/x-www-form-urlencoded" || contentType == "text/plain" {
var vals url.Values
vals, err = url.ParseQuery(string(body))
if err != nil {
return
}
if error := vals.Get("error"); error != "" || badStatusCode {
err = newError(error, vals.Get("error_description"), vals.Get("state"))
return
}
e := vals.Get("expires_in")
if e == "" {
e = vals.Get("expires")
}
if e != "" {
result.Expires, err = strconv.Atoi(e)
if err != nil {
return
}
}
result.AccessToken = vals.Get("access_token")
result.TokenType = vals.Get("token_type")
result.IDToken = vals.Get("id_token")
result.RefreshToken = vals.Get("refresh_token")
result.Scope = vals.Get("scope")
} else {
var r struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
IDToken string `json:"id_token"`
RefreshToken string `json:"refresh_token"`
Scope string `json:"scope"`
State string `json:"state"`
ExpiresIn json.Number `json:"expires_in"` // Azure AD returns string
Expires int `json:"expires"`
Error string `json:"error"`
Desc string `json:"error_description"`
}
if err = json.Unmarshal(body, &r); err != nil {
return
}
if r.Error != "" || badStatusCode {
err = newError(r.Error, r.Desc, r.State)
return
}
result.AccessToken = r.AccessToken
result.TokenType = r.TokenType
result.IDToken = r.IDToken
result.RefreshToken = r.RefreshToken
result.Scope = r.Scope
if expiresIn, err := r.ExpiresIn.Int64(); err != nil {
result.Expires = r.Expires
} else {
result.Expires = int(expiresIn)
}
}
return
}
type TokenResponse struct {
AccessToken string
TokenType string
Expires int
IDToken string
RefreshToken string // OPTIONAL.
Scope string // OPTIONAL, if identical to the scope requested by the client, otherwise, REQUIRED.
RawBody []byte // In case callers need some other non-standard info from the token response
}
type AuthCodeRequest struct {
ResponseType string
ClientID string
RedirectURL *url.URL
Scope []string
State string
}
func ParseAuthCodeRequest(q url.Values) (AuthCodeRequest, error) {
acr := AuthCodeRequest{
ResponseType: q.Get("response_type"),
ClientID: q.Get("client_id"),
State: q.Get("state"),
Scope: make([]string, 0),
}
qs := strings.TrimSpace(q.Get("scope"))
if qs != "" {
acr.Scope = strings.Split(qs, " ")
}
err := func() error {
if acr.ClientID == "" {
return NewError(ErrorInvalidRequest)
}
redirectURL := q.Get("redirect_uri")
if redirectURL != "" {
ru, err := url.Parse(redirectURL)
if err != nil {
return NewError(ErrorInvalidRequest)
}
acr.RedirectURL = ru
}
return nil
}()
return acr, err
}

View file

@ -1,518 +0,0 @@
package oauth2
import (
"errors"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strconv"
"strings"
"testing"
phttp "github.com/coreos/go-oidc/http"
)
func TestResponseTypesEqual(t *testing.T) {
tests := []struct {
r1, r2 string
want bool
}{
{"code", "code", true},
{"id_token", "code", false},
{"code token", "token code", true},
{"code token", "code token", true},
{"foo", "bar code", false},
{"code token id_token", "token id_token code", true},
{"code token id_token", "token id_token code zoo", false},
}
for i, tt := range tests {
got1 := ResponseTypesEqual(tt.r1, tt.r2)
got2 := ResponseTypesEqual(tt.r2, tt.r1)
if got1 != got2 {
t.Errorf("case %d: got different answers with different orders", i)
}
if tt.want != got1 {
t.Errorf("case %d: want=%t, got=%t", i, tt.want, got1)
}
}
}
func TestParseAuthCodeRequest(t *testing.T) {
tests := []struct {
query url.Values
wantACR AuthCodeRequest
wantErr error
}{
// no redirect_uri
{
query: url.Values{
"response_type": []string{"code"},
"scope": []string{"foo bar baz"},
"client_id": []string{"XXX"},
"state": []string{"pants"},
},
wantACR: AuthCodeRequest{
ResponseType: "code",
ClientID: "XXX",
Scope: []string{"foo", "bar", "baz"},
State: "pants",
RedirectURL: nil,
},
},
// with redirect_uri
{
query: url.Values{
"response_type": []string{"code"},
"redirect_uri": []string{"https://127.0.0.1:5555/callback?foo=bar"},
"scope": []string{"foo bar baz"},
"client_id": []string{"XXX"},
"state": []string{"pants"},
},
wantACR: AuthCodeRequest{
ResponseType: "code",
ClientID: "XXX",
Scope: []string{"foo", "bar", "baz"},
State: "pants",
RedirectURL: &url.URL{
Scheme: "https",
Host: "127.0.0.1:5555",
Path: "/callback",
RawQuery: "foo=bar",
},
},
},
// unsupported response_type doesn't trigger error
{
query: url.Values{
"response_type": []string{"token"},
"redirect_uri": []string{"https://127.0.0.1:5555/callback?foo=bar"},
"scope": []string{"foo bar baz"},
"client_id": []string{"XXX"},
"state": []string{"pants"},
},
wantACR: AuthCodeRequest{
ResponseType: "token",
ClientID: "XXX",
Scope: []string{"foo", "bar", "baz"},
State: "pants",
RedirectURL: &url.URL{
Scheme: "https",
Host: "127.0.0.1:5555",
Path: "/callback",
RawQuery: "foo=bar",
},
},
},
// unparseable redirect_uri
{
query: url.Values{
"response_type": []string{"code"},
"redirect_uri": []string{":"},
"scope": []string{"foo bar baz"},
"client_id": []string{"XXX"},
"state": []string{"pants"},
},
wantACR: AuthCodeRequest{
ResponseType: "code",
ClientID: "XXX",
Scope: []string{"foo", "bar", "baz"},
State: "pants",
},
wantErr: NewError(ErrorInvalidRequest),
},
// no client_id, redirect_uri not parsed
{
query: url.Values{
"response_type": []string{"code"},
"redirect_uri": []string{"https://127.0.0.1:5555/callback?foo=bar"},
"scope": []string{"foo bar baz"},
"client_id": []string{},
"state": []string{"pants"},
},
wantACR: AuthCodeRequest{
ResponseType: "code",
ClientID: "",
Scope: []string{"foo", "bar", "baz"},
State: "pants",
RedirectURL: nil,
},
wantErr: NewError(ErrorInvalidRequest),
},
}
for i, tt := range tests {
got, err := ParseAuthCodeRequest(tt.query)
if !reflect.DeepEqual(tt.wantErr, err) {
t.Errorf("case %d: incorrect error value: want=%q got=%q", i, tt.wantErr, err)
}
if !reflect.DeepEqual(tt.wantACR, got) {
t.Errorf("case %d: incorrect AuthCodeRequest value: want=%#v got=%#v", i, tt.wantACR, got)
}
}
}
type fakeBadClient struct {
Request *http.Request
err error
}
func (f *fakeBadClient) Do(r *http.Request) (*http.Response, error) {
f.Request = r
return nil, f.err
}
func TestClientCredsToken(t *testing.T) {
hc := &fakeBadClient{nil, errors.New("error")}
cfg := Config{
Credentials: ClientCredentials{ID: "c#id", Secret: "c secret"},
Scope: []string{"foo-scope", "bar-scope"},
TokenURL: "http://example.com/token",
AuthMethod: AuthMethodClientSecretBasic,
RedirectURL: "http://example.com/redirect",
AuthURL: "http://example.com/auth",
}
c, err := NewClient(hc, cfg)
if err != nil {
t.Errorf("unexpected error %v", err)
}
scope := []string{"openid"}
c.ClientCredsToken(scope)
if hc.Request == nil {
t.Error("request is empty")
}
tu := hc.Request.URL.String()
if cfg.TokenURL != tu {
t.Errorf("wrong token url, want=%v, got=%v", cfg.TokenURL, tu)
}
ct := hc.Request.Header.Get("Content-Type")
if ct != "application/x-www-form-urlencoded" {
t.Errorf("wrong content-type, want=application/x-www-form-urlencoded, got=%v", ct)
}
cid, secret, ok := phttp.BasicAuth(hc.Request)
if !ok {
t.Error("unexpected error parsing basic auth")
}
if url.QueryEscape(cfg.Credentials.ID) != cid {
t.Errorf("wrong client ID, want=%v, got=%v", cfg.Credentials.ID, cid)
}
if url.QueryEscape(cfg.Credentials.Secret) != secret {
t.Errorf("wrong client secret, want=%v, got=%v", cfg.Credentials.Secret, secret)
}
err = hc.Request.ParseForm()
if err != nil {
t.Error("unexpected error parsing form")
}
gt := hc.Request.PostForm.Get("grant_type")
if gt != GrantTypeClientCreds {
t.Errorf("wrong grant_type, want=%v, got=%v", GrantTypeClientCreds, gt)
}
sc := strings.Split(hc.Request.PostForm.Get("scope"), " ")
if !reflect.DeepEqual(scope, sc) {
t.Errorf("wrong scope, want=%v, got=%v", scope, sc)
}
}
func TestUserCredsToken(t *testing.T) {
hc := &fakeBadClient{nil, errors.New("error")}
cfg := Config{
Credentials: ClientCredentials{ID: "c#id", Secret: "c secret"},
Scope: []string{"foo-scope", "bar-scope"},
TokenURL: "http://example.com/token",
AuthMethod: AuthMethodClientSecretBasic,
RedirectURL: "http://example.com/redirect",
AuthURL: "http://example.com/auth",
}
c, err := NewClient(hc, cfg)
if err != nil {
t.Errorf("unexpected error %v", err)
}
c.UserCredsToken("username", "password")
if hc.Request == nil {
t.Error("request is empty")
}
tu := hc.Request.URL.String()
if cfg.TokenURL != tu {
t.Errorf("wrong token url, want=%v, got=%v", cfg.TokenURL, tu)
}
ct := hc.Request.Header.Get("Content-Type")
if ct != "application/x-www-form-urlencoded" {
t.Errorf("wrong content-type, want=application/x-www-form-urlencoded, got=%v", ct)
}
cid, secret, ok := phttp.BasicAuth(hc.Request)
if !ok {
t.Error("unexpected error parsing basic auth")
}
if url.QueryEscape(cfg.Credentials.ID) != cid {
t.Errorf("wrong client ID, want=%v, got=%v", cfg.Credentials.ID, cid)
}
if url.QueryEscape(cfg.Credentials.Secret) != secret {
t.Errorf("wrong client secret, want=%v, got=%v", cfg.Credentials.Secret, secret)
}
err = hc.Request.ParseForm()
if err != nil {
t.Error("unexpected error parsing form")
}
gt := hc.Request.PostForm.Get("grant_type")
if gt != GrantTypeUserCreds {
t.Errorf("wrong grant_type, want=%v, got=%v", GrantTypeUserCreds, gt)
}
sc := strings.Split(hc.Request.PostForm.Get("scope"), " ")
if !reflect.DeepEqual(c.scope, sc) {
t.Errorf("wrong scope, want=%v, got=%v", c.scope, sc)
}
}
func TestNewAuthenticatedRequest(t *testing.T) {
tests := []struct {
authMethod string
url string
values url.Values
}{
{
authMethod: AuthMethodClientSecretBasic,
url: "http://example.com/token",
values: url.Values{},
},
{
authMethod: AuthMethodClientSecretPost,
url: "http://example.com/token",
values: url.Values{},
},
}
for i, tt := range tests {
cfg := Config{
Credentials: ClientCredentials{ID: "c#id", Secret: "c secret"},
Scope: []string{"foo-scope", "bar-scope"},
TokenURL: "http://example.com/token",
AuthURL: "http://example.com/auth",
RedirectURL: "http://example.com/redirect",
AuthMethod: tt.authMethod,
}
c, err := NewClient(nil, cfg)
req, err := c.newAuthenticatedRequest(tt.url, tt.values)
if err != nil {
t.Errorf("case %d: unexpected error: %v", i, err)
continue
}
err = req.ParseForm()
if err != nil {
t.Errorf("case %d: want nil err, got %v", i, err)
}
if tt.authMethod == AuthMethodClientSecretBasic {
cid, secret, ok := phttp.BasicAuth(req)
if !ok {
t.Errorf("case %d: !ok parsing Basic Auth headers", i)
continue
}
if cid != url.QueryEscape(cfg.Credentials.ID) {
t.Errorf("case %d: want CID == %q, got CID == %q", i, cfg.Credentials.ID, cid)
}
if secret != url.QueryEscape(cfg.Credentials.Secret) {
t.Errorf("case %d: want secret == %q, got secret == %q", i, cfg.Credentials.Secret, secret)
}
} else if tt.authMethod == AuthMethodClientSecretPost {
if req.PostFormValue("client_secret") != cfg.Credentials.Secret {
t.Errorf("case %d: want client_secret == %q, got client_secret == %q",
i, cfg.Credentials.Secret, req.PostFormValue("client_secret"))
}
}
for k, v := range tt.values {
if !reflect.DeepEqual(v, req.PostForm[k]) {
t.Errorf("case %d: key:%q want==%q, got==%q", i, k, v, req.PostForm[k])
}
}
if req.URL.String() != tt.url {
t.Errorf("case %d: want URL==%q, got URL==%q", i, tt.url, req.URL.String())
}
}
}
func TestParseTokenResponse(t *testing.T) {
type response struct {
body string
contentType string
statusCode int // defaults to http.StatusOK
}
tests := []struct {
resp response
wantResp TokenResponse
wantError *Error
}{
{
resp: response{
body: "{ \"error\": \"invalid_client\", \"state\": \"foo\" }",
contentType: "application/json",
statusCode: http.StatusBadRequest,
},
wantError: &Error{Type: "invalid_client", State: "foo"},
},
{
resp: response{
body: "{ \"error\": \"invalid_request\", \"state\": \"bar\" }",
contentType: "application/json",
statusCode: http.StatusBadRequest,
},
wantError: &Error{Type: "invalid_request", State: "bar"},
},
{
// Actual response from bitbucket
resp: response{
body: `{"error_description": "Invalid OAuth client credentials", "error": "unauthorized_client"}`,
contentType: "application/json",
statusCode: http.StatusBadRequest,
},
wantError: &Error{Type: "unauthorized_client", Description: "Invalid OAuth client credentials"},
},
{
// Actual response from github
resp: response{
body: `error=incorrect_client_credentials&error_description=The+client_id+and%2For+client_secret+passed+are+incorrect.&error_uri=https%3A%2F%2Fdeveloper.github.com%2Fv3%2Foauth%2F%23incorrect-client-credentials`,
contentType: "application/x-www-form-urlencoded; charset=utf-8",
},
wantError: &Error{Type: "incorrect_client_credentials", Description: "The client_id and/or client_secret passed are incorrect."},
},
{
resp: response{
body: `{"access_token":"e72e16c7e42f292c6912e7710c838347ae178b4a", "scope":"repo,gist", "token_type":"bearer"}`,
contentType: "application/json",
},
wantResp: TokenResponse{
AccessToken: "e72e16c7e42f292c6912e7710c838347ae178b4a",
TokenType: "bearer",
Scope: "repo,gist",
},
},
{
resp: response{
body: `access_token=e72e16c7e42f292c6912e7710c838347ae178b4a&scope=user%2Cgist&token_type=bearer`,
contentType: "application/x-www-form-urlencoded",
},
wantResp: TokenResponse{
AccessToken: "e72e16c7e42f292c6912e7710c838347ae178b4a",
TokenType: "bearer",
Scope: "user,gist",
},
},
{
resp: response{
body: `{"access_token":"foo","id_token":"bar","expires_in":200,"token_type":"bearer","refresh_token":"spam"}`,
contentType: "application/json; charset=utf-8",
},
wantResp: TokenResponse{
AccessToken: "foo",
IDToken: "bar",
Expires: 200,
TokenType: "bearer",
RefreshToken: "spam",
},
},
{
// Azure AD returns "expires_in" value as string
resp: response{
body: `{"access_token":"foo","id_token":"bar","expires_in":"300","token_type":"bearer","refresh_token":"spam"}`,
contentType: "application/json; charset=utf-8",
},
wantResp: TokenResponse{
AccessToken: "foo",
IDToken: "bar",
Expires: 300,
TokenType: "bearer",
RefreshToken: "spam",
},
},
{
resp: response{
body: `{"access_token":"foo","id_token":"bar","expires":200,"token_type":"bearer","refresh_token":"spam"}`,
contentType: "application/json; charset=utf-8",
},
wantResp: TokenResponse{
AccessToken: "foo",
IDToken: "bar",
Expires: 200,
TokenType: "bearer",
RefreshToken: "spam",
},
},
{
resp: response{
body: `access_token=foo&id_token=bar&expires_in=200&token_type=bearer&refresh_token=spam`,
contentType: "application/x-www-form-urlencoded",
},
wantResp: TokenResponse{
AccessToken: "foo",
IDToken: "bar",
Expires: 200,
TokenType: "bearer",
RefreshToken: "spam",
},
},
}
for i, tt := range tests {
r := &http.Response{
StatusCode: http.StatusOK,
Header: http.Header{
"Content-Type": []string{tt.resp.contentType},
"Content-Length": []string{strconv.Itoa(len([]byte(tt.resp.body)))},
},
Body: ioutil.NopCloser(strings.NewReader(tt.resp.body)),
ContentLength: int64(len([]byte(tt.resp.body))),
}
if tt.resp.statusCode != 0 {
r.StatusCode = tt.resp.statusCode
}
result, err := parseTokenResponse(r)
if err != nil {
if tt.wantError == nil {
t.Errorf("case %d: got error==%v", i, err)
continue
}
if !reflect.DeepEqual(tt.wantError, err) {
t.Errorf("case %d: want=%+v, got=%+v", i, tt.wantError, err)
}
} else {
if tt.wantError != nil {
t.Errorf("case %d: want error==%v, got==nil", i, tt.wantError)
continue
}
// don't compare the raw body (it's really big and clogs error messages)
result.RawBody = tt.wantResp.RawBody
if !reflect.DeepEqual(tt.wantResp, result) {
t.Errorf("case %d: want=%+v, got=%+v", i, tt.wantResp, result)
}
}
}
}

View file

@ -1,846 +0,0 @@
package oidc
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/mail"
"net/url"
"sync"
"time"
phttp "github.com/coreos/go-oidc/http"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/go-oidc/key"
"github.com/coreos/go-oidc/oauth2"
)
const (
// amount of time that must pass after the last key sync
// completes before another attempt may begin
keySyncWindow = 5 * time.Second
)
var (
DefaultScope = []string{"openid", "email", "profile"}
supportedAuthMethods = map[string]struct{}{
oauth2.AuthMethodClientSecretBasic: struct{}{},
oauth2.AuthMethodClientSecretPost: struct{}{},
}
)
type ClientCredentials oauth2.ClientCredentials
type ClientIdentity struct {
Credentials ClientCredentials
Metadata ClientMetadata
}
type JWAOptions struct {
// SigningAlg specifies an JWA alg for signing JWTs.
//
// Specifying this field implies different actions depending on the context. It may
// require objects be serialized and signed as a JWT instead of plain JSON, or
// require an existing JWT object use the specified alg.
//
// See: http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
SigningAlg string
// EncryptionAlg, if provided, specifies that the returned or sent object be stored
// (or nested) within a JWT object and encrypted with the provided JWA alg.
EncryptionAlg string
// EncryptionEnc specifies the JWA enc algorithm to use with EncryptionAlg. If
// EncryptionAlg is provided and EncryptionEnc is omitted, this field defaults
// to A128CBC-HS256.
//
// If EncryptionEnc is provided EncryptionAlg must also be specified.
EncryptionEnc string
}
func (opt JWAOptions) valid() error {
if opt.EncryptionEnc != "" && opt.EncryptionAlg == "" {
return errors.New("encryption encoding provided with no encryption algorithm")
}
return nil
}
func (opt JWAOptions) defaults() JWAOptions {
if opt.EncryptionAlg != "" && opt.EncryptionEnc == "" {
opt.EncryptionEnc = jose.EncA128CBCHS256
}
return opt
}
var (
// Ensure ClientMetadata satisfies these interfaces.
_ json.Marshaler = &ClientMetadata{}
_ json.Unmarshaler = &ClientMetadata{}
)
// ClientMetadata holds metadata that the authorization server associates
// with a client identifier. The fields range from human-facing display
// strings such as client name, to items that impact the security of the
// protocol, such as the list of valid redirect URIs.
//
// See http://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata
//
// TODO: support language specific claim representations
// http://openid.net/specs/openid-connect-registration-1_0.html#LanguagesAndScripts
type ClientMetadata struct {
RedirectURIs []url.URL // Required
// A list of OAuth 2.0 "response_type" values that the client wishes to restrict
// itself to. Either "code", "token", or another registered extension.
//
// If omitted, only "code" will be used.
ResponseTypes []string
// A list of OAuth 2.0 grant types the client wishes to restrict itself to.
// The grant type values used by OIDC are "authorization_code", "implicit",
// and "refresh_token".
//
// If ommitted, only "authorization_code" will be used.
GrantTypes []string
// "native" or "web". If omitted, "web".
ApplicationType string
// List of email addresses.
Contacts []mail.Address
// Name of client to be presented to the end-user.
ClientName string
// URL that references a logo for the Client application.
LogoURI *url.URL
// URL of the home page of the Client.
ClientURI *url.URL
// Profile data policies and terms of use to be provided to the end user.
PolicyURI *url.URL
TermsOfServiceURI *url.URL
// URL to or the value of the client's JSON Web Key Set document.
JWKSURI *url.URL
JWKS *jose.JWKSet
// URL referencing a flie with a single JSON array of redirect URIs.
SectorIdentifierURI *url.URL
SubjectType string
// Options to restrict the JWS alg and enc values used for server responses and requests.
IDTokenResponseOptions JWAOptions
UserInfoResponseOptions JWAOptions
RequestObjectOptions JWAOptions
// Client requested authorization method and signing options for the token endpoint.
//
// Defaults to "client_secret_basic"
TokenEndpointAuthMethod string
TokenEndpointAuthSigningAlg string
// DefaultMaxAge specifies the maximum amount of time in seconds before an authorized
// user must reauthroize.
//
// If 0, no limitation is placed on the maximum.
DefaultMaxAge int64
// RequireAuthTime specifies if the auth_time claim in the ID token is required.
RequireAuthTime bool
// Default Authentication Context Class Reference values for authentication requests.
DefaultACRValues []string
// URI that a third party can use to initiate a login by the relaying party.
//
// See: http://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin
InitiateLoginURI *url.URL
// Pre-registered request_uri values that may be cached by the server.
RequestURIs []url.URL
}
// Defaults returns a shallow copy of ClientMetadata with default
// values replacing omitted fields.
func (m ClientMetadata) Defaults() ClientMetadata {
if len(m.ResponseTypes) == 0 {
m.ResponseTypes = []string{oauth2.ResponseTypeCode}
}
if len(m.GrantTypes) == 0 {
m.GrantTypes = []string{oauth2.GrantTypeAuthCode}
}
if m.ApplicationType == "" {
m.ApplicationType = "web"
}
if m.TokenEndpointAuthMethod == "" {
m.TokenEndpointAuthMethod = oauth2.AuthMethodClientSecretBasic
}
m.IDTokenResponseOptions = m.IDTokenResponseOptions.defaults()
m.UserInfoResponseOptions = m.UserInfoResponseOptions.defaults()
m.RequestObjectOptions = m.RequestObjectOptions.defaults()
return m
}
func (m *ClientMetadata) MarshalJSON() ([]byte, error) {
e := m.toEncodableStruct()
return json.Marshal(&e)
}
func (m *ClientMetadata) UnmarshalJSON(data []byte) error {
var e encodableClientMetadata
if err := json.Unmarshal(data, &e); err != nil {
return err
}
meta, err := e.toStruct()
if err != nil {
return err
}
if err := meta.Valid(); err != nil {
return err
}
*m = meta
return nil
}
type encodableClientMetadata struct {
RedirectURIs []string `json:"redirect_uris"` // Required
ResponseTypes []string `json:"response_types,omitempty"`
GrantTypes []string `json:"grant_types,omitempty"`
ApplicationType string `json:"application_type,omitempty"`
Contacts []string `json:"contacts,omitempty"`
ClientName string `json:"client_name,omitempty"`
LogoURI string `json:"logo_uri,omitempty"`
ClientURI string `json:"client_uri,omitempty"`
PolicyURI string `json:"policy_uri,omitempty"`
TermsOfServiceURI string `json:"tos_uri,omitempty"`
JWKSURI string `json:"jwks_uri,omitempty"`
JWKS *jose.JWKSet `json:"jwks,omitempty"`
SectorIdentifierURI string `json:"sector_identifier_uri,omitempty"`
SubjectType string `json:"subject_type,omitempty"`
IDTokenSignedResponseAlg string `json:"id_token_signed_response_alg,omitempty"`
IDTokenEncryptedResponseAlg string `json:"id_token_encrypted_response_alg,omitempty"`
IDTokenEncryptedResponseEnc string `json:"id_token_encrypted_response_enc,omitempty"`
UserInfoSignedResponseAlg string `json:"userinfo_signed_response_alg,omitempty"`
UserInfoEncryptedResponseAlg string `json:"userinfo_encrypted_response_alg,omitempty"`
UserInfoEncryptedResponseEnc string `json:"userinfo_encrypted_response_enc,omitempty"`
RequestObjectSigningAlg string `json:"request_object_signing_alg,omitempty"`
RequestObjectEncryptionAlg string `json:"request_object_encryption_alg,omitempty"`
RequestObjectEncryptionEnc string `json:"request_object_encryption_enc,omitempty"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
TokenEndpointAuthSigningAlg string `json:"token_endpoint_auth_signing_alg,omitempty"`
DefaultMaxAge int64 `json:"default_max_age,omitempty"`
RequireAuthTime bool `json:"require_auth_time,omitempty"`
DefaultACRValues []string `json:"default_acr_values,omitempty"`
InitiateLoginURI string `json:"initiate_login_uri,omitempty"`
RequestURIs []string `json:"request_uris,omitempty"`
}
func (c *encodableClientMetadata) toStruct() (ClientMetadata, error) {
p := stickyErrParser{}
m := ClientMetadata{
RedirectURIs: p.parseURIs(c.RedirectURIs, "redirect_uris"),
ResponseTypes: c.ResponseTypes,
GrantTypes: c.GrantTypes,
ApplicationType: c.ApplicationType,
Contacts: p.parseEmails(c.Contacts, "contacts"),
ClientName: c.ClientName,
LogoURI: p.parseURI(c.LogoURI, "logo_uri"),
ClientURI: p.parseURI(c.ClientURI, "client_uri"),
PolicyURI: p.parseURI(c.PolicyURI, "policy_uri"),
TermsOfServiceURI: p.parseURI(c.TermsOfServiceURI, "tos_uri"),
JWKSURI: p.parseURI(c.JWKSURI, "jwks_uri"),
JWKS: c.JWKS,
SectorIdentifierURI: p.parseURI(c.SectorIdentifierURI, "sector_identifier_uri"),
SubjectType: c.SubjectType,
TokenEndpointAuthMethod: c.TokenEndpointAuthMethod,
TokenEndpointAuthSigningAlg: c.TokenEndpointAuthSigningAlg,
DefaultMaxAge: c.DefaultMaxAge,
RequireAuthTime: c.RequireAuthTime,
DefaultACRValues: c.DefaultACRValues,
InitiateLoginURI: p.parseURI(c.InitiateLoginURI, "initiate_login_uri"),
RequestURIs: p.parseURIs(c.RequestURIs, "request_uris"),
IDTokenResponseOptions: JWAOptions{
c.IDTokenSignedResponseAlg,
c.IDTokenEncryptedResponseAlg,
c.IDTokenEncryptedResponseEnc,
},
UserInfoResponseOptions: JWAOptions{
c.UserInfoSignedResponseAlg,
c.UserInfoEncryptedResponseAlg,
c.UserInfoEncryptedResponseEnc,
},
RequestObjectOptions: JWAOptions{
c.RequestObjectSigningAlg,
c.RequestObjectEncryptionAlg,
c.RequestObjectEncryptionEnc,
},
}
if p.firstErr != nil {
return ClientMetadata{}, p.firstErr
}
return m, nil
}
// stickyErrParser parses URIs and email addresses. Once it encounters
// a parse error, subsequent calls become no-op.
type stickyErrParser struct {
firstErr error
}
func (p *stickyErrParser) parseURI(s, field string) *url.URL {
if p.firstErr != nil || s == "" {
return nil
}
u, err := url.Parse(s)
if err == nil {
if u.Host == "" {
err = errors.New("no host in URI")
} else if u.Scheme != "http" && u.Scheme != "https" {
err = errors.New("invalid URI scheme")
}
}
if err != nil {
p.firstErr = fmt.Errorf("failed to parse %s: %v", field, err)
return nil
}
return u
}
func (p *stickyErrParser) parseURIs(s []string, field string) []url.URL {
if p.firstErr != nil || len(s) == 0 {
return nil
}
uris := make([]url.URL, len(s))
for i, val := range s {
if val == "" {
p.firstErr = fmt.Errorf("invalid URI in field %s", field)
return nil
}
if u := p.parseURI(val, field); u != nil {
uris[i] = *u
}
}
return uris
}
func (p *stickyErrParser) parseEmails(s []string, field string) []mail.Address {
if p.firstErr != nil || len(s) == 0 {
return nil
}
addrs := make([]mail.Address, len(s))
for i, addr := range s {
if addr == "" {
p.firstErr = fmt.Errorf("invalid email in field %s", field)
return nil
}
a, err := mail.ParseAddress(addr)
if err != nil {
p.firstErr = fmt.Errorf("invalid email in field %s: %v", field, err)
return nil
}
addrs[i] = *a
}
return addrs
}
func (m *ClientMetadata) toEncodableStruct() encodableClientMetadata {
return encodableClientMetadata{
RedirectURIs: urisToStrings(m.RedirectURIs),
ResponseTypes: m.ResponseTypes,
GrantTypes: m.GrantTypes,
ApplicationType: m.ApplicationType,
Contacts: emailsToStrings(m.Contacts),
ClientName: m.ClientName,
LogoURI: uriToString(m.LogoURI),
ClientURI: uriToString(m.ClientURI),
PolicyURI: uriToString(m.PolicyURI),
TermsOfServiceURI: uriToString(m.TermsOfServiceURI),
JWKSURI: uriToString(m.JWKSURI),
JWKS: m.JWKS,
SectorIdentifierURI: uriToString(m.SectorIdentifierURI),
SubjectType: m.SubjectType,
IDTokenSignedResponseAlg: m.IDTokenResponseOptions.SigningAlg,
IDTokenEncryptedResponseAlg: m.IDTokenResponseOptions.EncryptionAlg,
IDTokenEncryptedResponseEnc: m.IDTokenResponseOptions.EncryptionEnc,
UserInfoSignedResponseAlg: m.UserInfoResponseOptions.SigningAlg,
UserInfoEncryptedResponseAlg: m.UserInfoResponseOptions.EncryptionAlg,
UserInfoEncryptedResponseEnc: m.UserInfoResponseOptions.EncryptionEnc,
RequestObjectSigningAlg: m.RequestObjectOptions.SigningAlg,
RequestObjectEncryptionAlg: m.RequestObjectOptions.EncryptionAlg,
RequestObjectEncryptionEnc: m.RequestObjectOptions.EncryptionEnc,
TokenEndpointAuthMethod: m.TokenEndpointAuthMethod,
TokenEndpointAuthSigningAlg: m.TokenEndpointAuthSigningAlg,
DefaultMaxAge: m.DefaultMaxAge,
RequireAuthTime: m.RequireAuthTime,
DefaultACRValues: m.DefaultACRValues,
InitiateLoginURI: uriToString(m.InitiateLoginURI),
RequestURIs: urisToStrings(m.RequestURIs),
}
}
func uriToString(u *url.URL) string {
if u == nil {
return ""
}
return u.String()
}
func urisToStrings(urls []url.URL) []string {
if len(urls) == 0 {
return nil
}
sli := make([]string, len(urls))
for i, u := range urls {
sli[i] = u.String()
}
return sli
}
func emailsToStrings(addrs []mail.Address) []string {
if len(addrs) == 0 {
return nil
}
sli := make([]string, len(addrs))
for i, addr := range addrs {
sli[i] = addr.String()
}
return sli
}
// Valid determines if a ClientMetadata conforms with the OIDC specification.
//
// Valid is called by UnmarshalJSON.
//
// NOTE(ericchiang): For development purposes Valid does not mandate 'https' for
// URLs fields where the OIDC spec requires it. This may change in future releases
// of this package. See: https://github.com/coreos/go-oidc/issues/34
func (m *ClientMetadata) Valid() error {
if len(m.RedirectURIs) == 0 {
return errors.New("zero redirect URLs")
}
validURI := func(u *url.URL, fieldName string) error {
if u.Host == "" {
return fmt.Errorf("no host for uri field %s", fieldName)
}
if u.Scheme != "http" && u.Scheme != "https" {
return fmt.Errorf("uri field %s scheme is not http or https", fieldName)
}
return nil
}
uris := []struct {
val *url.URL
name string
}{
{m.LogoURI, "logo_uri"},
{m.ClientURI, "client_uri"},
{m.PolicyURI, "policy_uri"},
{m.TermsOfServiceURI, "tos_uri"},
{m.JWKSURI, "jwks_uri"},
{m.SectorIdentifierURI, "sector_identifier_uri"},
{m.InitiateLoginURI, "initiate_login_uri"},
}
for _, uri := range uris {
if uri.val == nil {
continue
}
if err := validURI(uri.val, uri.name); err != nil {
return err
}
}
uriLists := []struct {
vals []url.URL
name string
}{
{m.RedirectURIs, "redirect_uris"},
{m.RequestURIs, "request_uris"},
}
for _, list := range uriLists {
for _, uri := range list.vals {
if err := validURI(&uri, list.name); err != nil {
return err
}
}
}
options := []struct {
option JWAOptions
name string
}{
{m.IDTokenResponseOptions, "id_token response"},
{m.UserInfoResponseOptions, "userinfo response"},
{m.RequestObjectOptions, "request_object"},
}
for _, option := range options {
if err := option.option.valid(); err != nil {
return fmt.Errorf("invalid JWA values for %s: %v", option.name, err)
}
}
return nil
}
type ClientRegistrationResponse struct {
ClientID string // Required
ClientSecret string
RegistrationAccessToken string
RegistrationClientURI string
// If IsZero is true, unspecified.
ClientIDIssuedAt time.Time
// Time at which the client_secret will expire.
// If IsZero is true, it will not expire.
ClientSecretExpiresAt time.Time
ClientMetadata
}
type encodableClientRegistrationResponse struct {
ClientID string `json:"client_id"` // Required
ClientSecret string `json:"client_secret,omitempty"`
RegistrationAccessToken string `json:"registration_access_token,omitempty"`
RegistrationClientURI string `json:"registration_client_uri,omitempty"`
ClientIDIssuedAt int64 `json:"client_id_issued_at,omitempty"`
// Time at which the client_secret will expire, in seconds since the epoch.
// If 0 it will not expire.
ClientSecretExpiresAt int64 `json:"client_secret_expires_at"` // Required
encodableClientMetadata
}
func unixToSec(t time.Time) int64 {
if t.IsZero() {
return 0
}
return t.Unix()
}
func (c *ClientRegistrationResponse) MarshalJSON() ([]byte, error) {
e := encodableClientRegistrationResponse{
ClientID: c.ClientID,
ClientSecret: c.ClientSecret,
RegistrationAccessToken: c.RegistrationAccessToken,
RegistrationClientURI: c.RegistrationClientURI,
ClientIDIssuedAt: unixToSec(c.ClientIDIssuedAt),
ClientSecretExpiresAt: unixToSec(c.ClientSecretExpiresAt),
encodableClientMetadata: c.ClientMetadata.toEncodableStruct(),
}
return json.Marshal(&e)
}
func secToUnix(sec int64) time.Time {
if sec == 0 {
return time.Time{}
}
return time.Unix(sec, 0)
}
func (c *ClientRegistrationResponse) UnmarshalJSON(data []byte) error {
var e encodableClientRegistrationResponse
if err := json.Unmarshal(data, &e); err != nil {
return err
}
if e.ClientID == "" {
return errors.New("no client_id in client registration response")
}
metadata, err := e.encodableClientMetadata.toStruct()
if err != nil {
return err
}
*c = ClientRegistrationResponse{
ClientID: e.ClientID,
ClientSecret: e.ClientSecret,
RegistrationAccessToken: e.RegistrationAccessToken,
RegistrationClientURI: e.RegistrationClientURI,
ClientIDIssuedAt: secToUnix(e.ClientIDIssuedAt),
ClientSecretExpiresAt: secToUnix(e.ClientSecretExpiresAt),
ClientMetadata: metadata,
}
return nil
}
type ClientConfig struct {
HTTPClient phttp.Client
Credentials ClientCredentials
Scope []string
RedirectURL string
ProviderConfig ProviderConfig
KeySet key.PublicKeySet
}
func NewClient(cfg ClientConfig) (*Client, error) {
// Allow empty redirect URL in the case where the client
// only needs to verify a given token.
ru, err := url.Parse(cfg.RedirectURL)
if err != nil {
return nil, fmt.Errorf("invalid redirect URL: %v", err)
}
c := Client{
credentials: cfg.Credentials,
httpClient: cfg.HTTPClient,
scope: cfg.Scope,
redirectURL: ru.String(),
providerConfig: newProviderConfigRepo(cfg.ProviderConfig),
keySet: cfg.KeySet,
}
if c.httpClient == nil {
c.httpClient = http.DefaultClient
}
if c.scope == nil {
c.scope = make([]string, len(DefaultScope))
copy(c.scope, DefaultScope)
}
return &c, nil
}
type Client struct {
httpClient phttp.Client
providerConfig *providerConfigRepo
credentials ClientCredentials
redirectURL string
scope []string
keySet key.PublicKeySet
providerSyncer *ProviderConfigSyncer
keySetSyncMutex sync.RWMutex
lastKeySetSync time.Time
}
func (c *Client) Healthy() error {
now := time.Now().UTC()
cfg := c.providerConfig.Get()
if cfg.Empty() {
return errors.New("oidc client provider config empty")
}
if !cfg.ExpiresAt.IsZero() && cfg.ExpiresAt.Before(now) {
return errors.New("oidc client provider config expired")
}
return nil
}
func (c *Client) OAuthClient() (*oauth2.Client, error) {
cfg := c.providerConfig.Get()
authMethod, err := chooseAuthMethod(cfg)
if err != nil {
return nil, err
}
ocfg := oauth2.Config{
Credentials: oauth2.ClientCredentials(c.credentials),
RedirectURL: c.redirectURL,
AuthURL: cfg.AuthEndpoint.String(),
TokenURL: cfg.TokenEndpoint.String(),
Scope: c.scope,
AuthMethod: authMethod,
}
return oauth2.NewClient(c.httpClient, ocfg)
}
func chooseAuthMethod(cfg ProviderConfig) (string, error) {
if len(cfg.TokenEndpointAuthMethodsSupported) == 0 {
return oauth2.AuthMethodClientSecretBasic, nil
}
for _, authMethod := range cfg.TokenEndpointAuthMethodsSupported {
if _, ok := supportedAuthMethods[authMethod]; ok {
return authMethod, nil
}
}
return "", errors.New("no supported auth methods")
}
// SyncProviderConfig starts the provider config syncer
func (c *Client) SyncProviderConfig(discoveryURL string) chan struct{} {
r := NewHTTPProviderConfigGetter(c.httpClient, discoveryURL)
s := NewProviderConfigSyncer(r, c.providerConfig)
stop := s.Run()
s.WaitUntilInitialSync()
return stop
}
func (c *Client) maybeSyncKeys() error {
tooSoon := func() bool {
return time.Now().UTC().Before(c.lastKeySetSync.Add(keySyncWindow))
}
// ignore request to sync keys if a sync operation has been
// attempted too recently
if tooSoon() {
return nil
}
c.keySetSyncMutex.Lock()
defer c.keySetSyncMutex.Unlock()
// check again, as another goroutine may have been holding
// the lock while updating the keys
if tooSoon() {
return nil
}
cfg := c.providerConfig.Get()
r := NewRemotePublicKeyRepo(c.httpClient, cfg.KeysEndpoint.String())
w := &clientKeyRepo{client: c}
_, err := key.Sync(r, w)
c.lastKeySetSync = time.Now().UTC()
return err
}
type clientKeyRepo struct {
client *Client
}
func (r *clientKeyRepo) Set(ks key.KeySet) error {
pks, ok := ks.(*key.PublicKeySet)
if !ok {
return errors.New("unable to cast to PublicKey")
}
r.client.keySet = *pks
return nil
}
func (c *Client) ClientCredsToken(scope []string) (jose.JWT, error) {
cfg := c.providerConfig.Get()
if !cfg.SupportsGrantType(oauth2.GrantTypeClientCreds) {
return jose.JWT{}, fmt.Errorf("%v grant type is not supported", oauth2.GrantTypeClientCreds)
}
oac, err := c.OAuthClient()
if err != nil {
return jose.JWT{}, err
}
t, err := oac.ClientCredsToken(scope)
if err != nil {
return jose.JWT{}, err
}
jwt, err := jose.ParseJWT(t.IDToken)
if err != nil {
return jose.JWT{}, err
}
return jwt, c.VerifyJWT(jwt)
}
// ExchangeAuthCode exchanges an OAuth2 auth code for an OIDC JWT ID token.
func (c *Client) ExchangeAuthCode(code string) (jose.JWT, error) {
oac, err := c.OAuthClient()
if err != nil {
return jose.JWT{}, err
}
t, err := oac.RequestToken(oauth2.GrantTypeAuthCode, code)
if err != nil {
return jose.JWT{}, err
}
jwt, err := jose.ParseJWT(t.IDToken)
if err != nil {
return jose.JWT{}, err
}
return jwt, c.VerifyJWT(jwt)
}
// RefreshToken uses a refresh token to exchange for a new OIDC JWT ID Token.
func (c *Client) RefreshToken(refreshToken string) (jose.JWT, error) {
oac, err := c.OAuthClient()
if err != nil {
return jose.JWT{}, err
}
t, err := oac.RequestToken(oauth2.GrantTypeRefreshToken, refreshToken)
if err != nil {
return jose.JWT{}, err
}
jwt, err := jose.ParseJWT(t.IDToken)
if err != nil {
return jose.JWT{}, err
}
return jwt, c.VerifyJWT(jwt)
}
func (c *Client) VerifyJWT(jwt jose.JWT) error {
var keysFunc func() []key.PublicKey
if kID, ok := jwt.KeyID(); ok {
keysFunc = c.keysFuncWithID(kID)
} else {
keysFunc = c.keysFuncAll()
}
v := NewJWTVerifier(
c.providerConfig.Get().Issuer.String(),
c.credentials.ID,
c.maybeSyncKeys, keysFunc)
return v.Verify(jwt)
}
// keysFuncWithID returns a function that retrieves at most unexpired
// public key from the Client that matches the provided ID
func (c *Client) keysFuncWithID(kID string) func() []key.PublicKey {
return func() []key.PublicKey {
c.keySetSyncMutex.RLock()
defer c.keySetSyncMutex.RUnlock()
if c.keySet.ExpiresAt().Before(time.Now()) {
return []key.PublicKey{}
}
k := c.keySet.Key(kID)
if k == nil {
return []key.PublicKey{}
}
return []key.PublicKey{*k}
}
}
// keysFuncAll returns a function that retrieves all unexpired public
// keys from the Client
func (c *Client) keysFuncAll() func() []key.PublicKey {
return func() []key.PublicKey {
c.keySetSyncMutex.RLock()
defer c.keySetSyncMutex.RUnlock()
if c.keySet.ExpiresAt().Before(time.Now()) {
return []key.PublicKey{}
}
return c.keySet.Keys()
}
}
type providerConfigRepo struct {
mu sync.RWMutex
config ProviderConfig // do not access directly, use Get()
}
func newProviderConfigRepo(pc ProviderConfig) *providerConfigRepo {
return &providerConfigRepo{sync.RWMutex{}, pc}
}
// returns an error to implement ProviderConfigSetter
func (r *providerConfigRepo) Set(cfg ProviderConfig) error {
r.mu.Lock()
defer r.mu.Unlock()
r.config = cfg
return nil
}
func (r *providerConfigRepo) Get() ProviderConfig {
r.mu.RLock()
defer r.mu.RUnlock()
return r.config
}

View file

@ -1,81 +0,0 @@
// This file contains tests which depend on the race detector being enabled.
// +build race
package oidc
import (
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
)
type testProvider struct {
baseURL *url.URL
}
func (p *testProvider) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != discoveryConfigPath {
http.NotFound(w, r)
return
}
cfg := ProviderConfig{
Issuer: p.baseURL,
ExpiresAt: time.Now().Add(time.Second),
}
cfg = fillRequiredProviderFields(cfg)
json.NewEncoder(w).Encode(&cfg)
}
// This test fails by triggering the race detector, not by calling t.Error or t.Fatal.
func TestProviderSyncRace(t *testing.T) {
prov := &testProvider{}
s := httptest.NewServer(prov)
defer s.Close()
u, err := url.Parse(s.URL)
if err != nil {
t.Fatal(err)
}
prov.baseURL = u
prevValue := minimumProviderConfigSyncInterval
defer func() { minimumProviderConfigSyncInterval = prevValue }()
// Reduce the sync interval to increase the write frequencey.
minimumProviderConfigSyncInterval = 5 * time.Millisecond
cliCfg := ClientConfig{
HTTPClient: http.DefaultClient,
}
cli, err := NewClient(cliCfg)
if err != nil {
t.Error(err)
return
}
if !cli.providerConfig.Get().Empty() {
t.Errorf("want c.ProviderConfig == nil, got c.ProviderConfig=%#v")
}
// SyncProviderConfig beings a goroutine which writes to the client's provider config.
c := cli.SyncProviderConfig(s.URL)
if cli.providerConfig.Get().Empty() {
t.Errorf("want c.ProviderConfig != nil")
}
defer func() {
// stop the background process
c <- struct{}{}
}()
for i := 0; i < 10; i++ {
time.Sleep(5 * time.Millisecond)
// Creating an OAuth client reads from the provider config.
cli.OAuthClient()
}
}

View file

@ -1,654 +0,0 @@
package oidc
import (
"encoding/json"
"net/mail"
"net/url"
"reflect"
"testing"
"time"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/go-oidc/key"
"github.com/coreos/go-oidc/oauth2"
"github.com/kylelemons/godebug/pretty"
)
func TestNewClientScopeDefault(t *testing.T) {
tests := []struct {
c ClientConfig
e []string
}{
{
// No scope
c: ClientConfig{RedirectURL: "http://example.com/redirect"},
e: DefaultScope,
},
{
// Nil scope
c: ClientConfig{RedirectURL: "http://example.com/redirect", Scope: nil},
e: DefaultScope,
},
{
// Empty scope
c: ClientConfig{RedirectURL: "http://example.com/redirect", Scope: []string{}},
e: []string{},
},
{
// Custom scope equal to default
c: ClientConfig{RedirectURL: "http://example.com/redirect", Scope: []string{"openid", "email", "profile"}},
e: DefaultScope,
},
{
// Custom scope not including defaults
c: ClientConfig{RedirectURL: "http://example.com/redirect", Scope: []string{"foo", "bar"}},
e: []string{"foo", "bar"},
},
{
// Custom scopes overlapping with defaults
c: ClientConfig{RedirectURL: "http://example.com/redirect", Scope: []string{"openid", "foo"}},
e: []string{"openid", "foo"},
},
}
for i, tt := range tests {
c, err := NewClient(tt.c)
if err != nil {
t.Errorf("case %d: unexpected error from NewClient: %v", i, err)
continue
}
if !reflect.DeepEqual(tt.e, c.scope) {
t.Errorf("case %d: want: %v, got: %v", i, tt.e, c.scope)
}
}
}
func TestHealthy(t *testing.T) {
now := time.Now().UTC()
tests := []struct {
p ProviderConfig
h bool
}{
// all ok
{
p: ProviderConfig{
Issuer: &url.URL{Scheme: "http", Host: "example.com"},
ExpiresAt: now.Add(time.Hour),
},
h: true,
},
// zero-value ProviderConfig.ExpiresAt
{
p: ProviderConfig{
Issuer: &url.URL{Scheme: "http", Host: "example.com"},
},
h: true,
},
// expired ProviderConfig
{
p: ProviderConfig{
Issuer: &url.URL{Scheme: "http", Host: "example.com"},
ExpiresAt: now.Add(time.Hour * -1),
},
h: false,
},
// empty ProviderConfig
{
p: ProviderConfig{},
h: false,
},
}
for i, tt := range tests {
c := &Client{providerConfig: newProviderConfigRepo(tt.p)}
err := c.Healthy()
want := tt.h
got := (err == nil)
if want != got {
t.Errorf("case %d: want: healthy=%v, got: healhty=%v, err: %v", i, want, got, err)
}
}
}
func TestClientKeysFuncAll(t *testing.T) {
priv1, err := key.GeneratePrivateKey()
if err != nil {
t.Fatalf("failed to generate private key, error=%v", err)
}
priv2, err := key.GeneratePrivateKey()
if err != nil {
t.Fatalf("failed to generate private key, error=%v", err)
}
now := time.Now()
future := now.Add(time.Hour)
past := now.Add(-1 * time.Hour)
tests := []struct {
keySet *key.PublicKeySet
want []key.PublicKey
}{
// two keys, non-expired set
{
keySet: key.NewPublicKeySet([]jose.JWK{priv2.JWK(), priv1.JWK()}, future),
want: []key.PublicKey{*key.NewPublicKey(priv2.JWK()), *key.NewPublicKey(priv1.JWK())},
},
// no keys, non-expired set
{
keySet: key.NewPublicKeySet([]jose.JWK{}, future),
want: []key.PublicKey{},
},
// two keys, expired set
{
keySet: key.NewPublicKeySet([]jose.JWK{priv2.JWK(), priv1.JWK()}, past),
want: []key.PublicKey{},
},
// no keys, expired set
{
keySet: key.NewPublicKeySet([]jose.JWK{}, past),
want: []key.PublicKey{},
},
}
for i, tt := range tests {
var c Client
c.keySet = *tt.keySet
keysFunc := c.keysFuncAll()
got := keysFunc()
if !reflect.DeepEqual(tt.want, got) {
t.Errorf("case %d: want=%#v got=%#v", i, tt.want, got)
}
}
}
func TestClientKeysFuncWithID(t *testing.T) {
priv1, err := key.GeneratePrivateKey()
if err != nil {
t.Fatalf("failed to generate private key, error=%v", err)
}
priv2, err := key.GeneratePrivateKey()
if err != nil {
t.Fatalf("failed to generate private key, error=%v", err)
}
now := time.Now()
future := now.Add(time.Hour)
past := now.Add(-1 * time.Hour)
tests := []struct {
keySet *key.PublicKeySet
argID string
want []key.PublicKey
}{
// two keys, match, non-expired set
{
keySet: key.NewPublicKeySet([]jose.JWK{priv2.JWK(), priv1.JWK()}, future),
argID: priv2.ID(),
want: []key.PublicKey{*key.NewPublicKey(priv2.JWK())},
},
// two keys, no match, non-expired set
{
keySet: key.NewPublicKeySet([]jose.JWK{priv2.JWK(), priv1.JWK()}, future),
argID: "XXX",
want: []key.PublicKey{},
},
// no keys, no match, non-expired set
{
keySet: key.NewPublicKeySet([]jose.JWK{}, future),
argID: priv2.ID(),
want: []key.PublicKey{},
},
// two keys, match, expired set
{
keySet: key.NewPublicKeySet([]jose.JWK{priv2.JWK(), priv1.JWK()}, past),
argID: priv2.ID(),
want: []key.PublicKey{},
},
// no keys, no match, expired set
{
keySet: key.NewPublicKeySet([]jose.JWK{}, past),
argID: priv2.ID(),
want: []key.PublicKey{},
},
}
for i, tt := range tests {
var c Client
c.keySet = *tt.keySet
keysFunc := c.keysFuncWithID(tt.argID)
got := keysFunc()
if !reflect.DeepEqual(tt.want, got) {
t.Errorf("case %d: want=%#v got=%#v", i, tt.want, got)
}
}
}
func TestClientMetadataValid(t *testing.T) {
tests := []ClientMetadata{
// one RedirectURL
ClientMetadata{
RedirectURIs: []url.URL{url.URL{Scheme: "http", Host: "example.com"}},
},
// one RedirectURL w/ nonempty path
ClientMetadata{
RedirectURIs: []url.URL{url.URL{Scheme: "http", Host: "example.com", Path: "/foo"}},
},
// two RedirectURIs
ClientMetadata{
RedirectURIs: []url.URL{
url.URL{Scheme: "http", Host: "foo.example.com"},
url.URL{Scheme: "http", Host: "bar.example.com"},
},
},
}
for i, tt := range tests {
if err := tt.Valid(); err != nil {
t.Errorf("case %d: unexpected error: %v", i, err)
}
}
}
func TestClientMetadataInvalid(t *testing.T) {
tests := []ClientMetadata{
// nil RedirectURls slice
ClientMetadata{
RedirectURIs: nil,
},
// empty RedirectURIs slice
ClientMetadata{
RedirectURIs: []url.URL{},
},
// empty url.URL
ClientMetadata{
RedirectURIs: []url.URL{url.URL{}},
},
// empty url.URL following OK item
ClientMetadata{
RedirectURIs: []url.URL{url.URL{Scheme: "http", Host: "example.com"}, url.URL{}},
},
// url.URL with empty Host
ClientMetadata{
RedirectURIs: []url.URL{url.URL{Scheme: "http", Host: ""}},
},
// url.URL with empty Scheme
ClientMetadata{
RedirectURIs: []url.URL{url.URL{Scheme: "", Host: "example.com"}},
},
// url.URL with non-HTTP(S) Scheme
ClientMetadata{
RedirectURIs: []url.URL{url.URL{Scheme: "tcp", Host: "127.0.0.1"}},
},
// EncryptionEnc without EncryptionAlg
ClientMetadata{
RedirectURIs: []url.URL{url.URL{Scheme: "http", Host: "example.com"}},
IDTokenResponseOptions: JWAOptions{
EncryptionEnc: "A128CBC-HS256",
},
},
// List of URIs with one empty element
ClientMetadata{
RedirectURIs: []url.URL{url.URL{Scheme: "http", Host: "example.com"}},
RequestURIs: []url.URL{
url.URL{Scheme: "http", Host: "example.com"},
url.URL{},
},
},
}
for i, tt := range tests {
if err := tt.Valid(); err == nil {
t.Errorf("case %d: expected non-nil error", i)
}
}
}
func TestChooseAuthMethod(t *testing.T) {
tests := []struct {
supported []string
chosen string
err bool
}{
{
supported: []string{},
chosen: oauth2.AuthMethodClientSecretBasic,
},
{
supported: []string{oauth2.AuthMethodClientSecretBasic},
chosen: oauth2.AuthMethodClientSecretBasic,
},
{
supported: []string{oauth2.AuthMethodClientSecretPost},
chosen: oauth2.AuthMethodClientSecretPost,
},
{
supported: []string{oauth2.AuthMethodClientSecretPost, oauth2.AuthMethodClientSecretBasic},
chosen: oauth2.AuthMethodClientSecretPost,
},
{
supported: []string{oauth2.AuthMethodClientSecretBasic, oauth2.AuthMethodClientSecretPost},
chosen: oauth2.AuthMethodClientSecretBasic,
},
{
supported: []string{oauth2.AuthMethodClientSecretJWT, oauth2.AuthMethodClientSecretPost},
chosen: oauth2.AuthMethodClientSecretPost,
},
{
supported: []string{oauth2.AuthMethodClientSecretJWT},
chosen: "",
err: true,
},
}
for i, tt := range tests {
cfg := ProviderConfig{
TokenEndpointAuthMethodsSupported: tt.supported,
}
got, err := chooseAuthMethod(cfg)
if tt.err {
if err == nil {
t.Errorf("case %d: expected non-nil err", i)
}
continue
}
if got != tt.chosen {
t.Errorf("case %d: want=%q, got=%q", i, tt.chosen, got)
}
}
}
func TestClientMetadataUnmarshal(t *testing.T) {
tests := []struct {
data string
want ClientMetadata
wantErr bool
}{
{
`{"redirect_uris":["https://example.com"]}`,
ClientMetadata{
RedirectURIs: []url.URL{
{Scheme: "https", Host: "example.com"},
},
},
false,
},
{
// redirect_uris required
`{}`,
ClientMetadata{},
true,
},
{
// must have at least one redirect_uris
`{"redirect_uris":[]}`,
ClientMetadata{},
true,
},
{
`{"redirect_uris":["https://example.com"],"contacts":["Ms. Foo <foo@example.com>"]}`,
ClientMetadata{
RedirectURIs: []url.URL{
{Scheme: "https", Host: "example.com"},
},
Contacts: []mail.Address{
{Name: "Ms. Foo", Address: "foo@example.com"},
},
},
false,
},
{
// invalid URI provided for field
`{"redirect_uris":["https://example.com"],"logo_uri":"not a valid uri"}`,
ClientMetadata{},
true,
},
{
// logo_uri can't be a list
`{"redirect_uris":["https://example.com"],"logo_uri":["https://example.com/logo"]}`,
ClientMetadata{},
true,
},
{
`{
"redirect_uris":["https://example.com"],
"userinfo_encrypted_response_alg":"RSA1_5",
"userinfo_encrypted_response_enc":"A128CBC-HS256",
"contacts": [
"jane doe <jane.doe@example.com>", "john doe <john.doe@example.com>"
]
}`,
ClientMetadata{
RedirectURIs: []url.URL{
{Scheme: "https", Host: "example.com"},
},
UserInfoResponseOptions: JWAOptions{
EncryptionAlg: "RSA1_5",
EncryptionEnc: "A128CBC-HS256",
},
Contacts: []mail.Address{
{Name: "jane doe", Address: "jane.doe@example.com"},
{Name: "john doe", Address: "john.doe@example.com"},
},
},
false,
},
{
// If encrypted_response_enc is provided encrypted_response_alg must also be.
`{
"redirect_uris":["https://example.com"],
"userinfo_encrypted_response_enc":"A128CBC-HS256"
}`,
ClientMetadata{},
true,
},
}
for i, tt := range tests {
var got ClientMetadata
if err := got.UnmarshalJSON([]byte(tt.data)); err != nil {
if !tt.wantErr {
t.Errorf("case %d: unmarshal failed: %v", i, err)
}
continue
}
if tt.wantErr {
t.Errorf("case %d: expected unmarshal to produce error", i)
continue
}
if diff := pretty.Compare(tt.want, got); diff != "" {
t.Errorf("case %d: results not equal: %s", i, diff)
}
}
}
func TestClientMetadataMarshal(t *testing.T) {
tests := []struct {
metadata ClientMetadata
want string
}{
{
ClientMetadata{
RedirectURIs: []url.URL{
{Scheme: "https", Host: "example.com", Path: "/callback"},
},
},
`{"redirect_uris":["https://example.com/callback"]}`,
},
{
ClientMetadata{
RedirectURIs: []url.URL{
{Scheme: "https", Host: "example.com", Path: "/callback"},
},
RequestObjectOptions: JWAOptions{
EncryptionAlg: "RSA1_5",
EncryptionEnc: "A128CBC-HS256",
},
},
`{"redirect_uris":["https://example.com/callback"],"request_object_encryption_alg":"RSA1_5","request_object_encryption_enc":"A128CBC-HS256"}`,
},
}
for i, tt := range tests {
got, err := json.Marshal(&tt.metadata)
if err != nil {
t.Errorf("case %d: failed to marshal metadata: %v", i, err)
continue
}
if string(got) != tt.want {
t.Errorf("case %d: marshaled string did not match expected string", i)
}
}
}
func TestClientMetadataMarshalRoundTrip(t *testing.T) {
tests := []ClientMetadata{
{
RedirectURIs: []url.URL{
{Scheme: "https", Host: "example.com", Path: "/callback"},
},
LogoURI: &url.URL{Scheme: "https", Host: "example.com", Path: "/logo"},
RequestObjectOptions: JWAOptions{
EncryptionAlg: "RSA1_5",
EncryptionEnc: "A128CBC-HS256",
},
ApplicationType: "native",
TokenEndpointAuthMethod: "client_secret_basic",
},
}
for i, want := range tests {
data, err := json.Marshal(&want)
if err != nil {
t.Errorf("case %d: failed to marshal metadata: %v", i, err)
continue
}
var got ClientMetadata
if err := json.Unmarshal(data, &got); err != nil {
t.Errorf("case %d: failed to unmarshal metadata: %v", i, err)
continue
}
if diff := pretty.Compare(want, got); diff != "" {
t.Errorf("case %d: struct did not survive a marshaling round trip: %s", i, diff)
}
}
}
func TestClientRegistrationResponseUnmarshal(t *testing.T) {
tests := []struct {
data string
want ClientRegistrationResponse
wantErr bool
secretExpires bool
}{
{
`{
"client_id":"foo",
"client_secret":"bar",
"client_secret_expires_at": 1577858400,
"redirect_uris":[
"https://client.example.org/callback",
"https://client.example.org/callback2"
],
"client_name":"my_example"
}`,
ClientRegistrationResponse{
ClientID: "foo",
ClientSecret: "bar",
ClientSecretExpiresAt: time.Unix(1577858400, 0),
ClientMetadata: ClientMetadata{
RedirectURIs: []url.URL{
{Scheme: "https", Host: "client.example.org", Path: "/callback"},
{Scheme: "https", Host: "client.example.org", Path: "/callback2"},
},
ClientName: "my_example",
},
},
false,
true,
},
{
`{
"client_id":"foo",
"client_secret_expires_at": 0,
"redirect_uris":[
"https://client.example.org/callback",
"https://client.example.org/callback2"
],
"client_name":"my_example"
}`,
ClientRegistrationResponse{
ClientID: "foo",
ClientMetadata: ClientMetadata{
RedirectURIs: []url.URL{
{Scheme: "https", Host: "client.example.org", Path: "/callback"},
{Scheme: "https", Host: "client.example.org", Path: "/callback2"},
},
ClientName: "my_example",
},
},
false,
false,
},
{
// no client id
`{
"client_secret_expires_at": 0,
"redirect_uris":[
"https://client.example.org/callback",
"https://client.example.org/callback2"
],
"client_name":"my_example"
}`,
ClientRegistrationResponse{},
true,
false,
},
}
for i, tt := range tests {
var got ClientRegistrationResponse
if err := json.Unmarshal([]byte(tt.data), &got); err != nil {
if !tt.wantErr {
t.Errorf("case %d: unmarshal failed: %v", i, err)
}
continue
}
if tt.wantErr {
t.Errorf("case %d: expected unmarshal to produce error", i)
continue
}
if diff := pretty.Compare(tt.want, got); diff != "" {
t.Errorf("case %d: results not equal: %s", i, diff)
}
if tt.secretExpires && got.ClientSecretExpiresAt.IsZero() {
t.Errorf("case %d: expected client_secret to expire, but it doesn't", i)
} else if !tt.secretExpires && !got.ClientSecretExpiresAt.IsZero() {
t.Errorf("case %d: expected client_secret to not expire, but it does", i)
}
}
}

View file

@ -1,2 +0,0 @@
// Package oidc is DEPRECATED. Use github.com/coreos/go-oidc instead.
package oidc

View file

@ -1,44 +0,0 @@
package oidc
import (
"errors"
"time"
"github.com/coreos/go-oidc/jose"
)
type Identity struct {
ID string
Name string
Email string
ExpiresAt time.Time
}
func IdentityFromClaims(claims jose.Claims) (*Identity, error) {
if claims == nil {
return nil, errors.New("nil claim set")
}
var ident Identity
var err error
var ok bool
if ident.ID, ok, err = claims.StringClaim("sub"); err != nil {
return nil, err
} else if !ok {
return nil, errors.New("missing required claim: sub")
}
if ident.Email, _, err = claims.StringClaim("email"); err != nil {
return nil, err
}
exp, ok, err := claims.TimeClaim("exp")
if err != nil {
return nil, err
} else if ok {
ident.ExpiresAt = exp
}
return &ident, nil
}

View file

@ -1,113 +0,0 @@
package oidc
import (
"reflect"
"testing"
"time"
"github.com/coreos/go-oidc/jose"
)
func TestIdentityFromClaims(t *testing.T) {
tests := []struct {
claims jose.Claims
want Identity
}{
{
claims: jose.Claims{
"sub": "123850281",
"name": "Elroy",
"email": "elroy@example.com",
"exp": float64(1.416935146e+09),
},
want: Identity{
ID: "123850281",
Name: "",
Email: "elroy@example.com",
ExpiresAt: time.Date(2014, time.November, 25, 17, 05, 46, 0, time.UTC),
},
},
{
claims: jose.Claims{
"sub": "123850281",
"name": "Elroy",
"exp": float64(1.416935146e+09),
},
want: Identity{
ID: "123850281",
Name: "",
Email: "",
ExpiresAt: time.Date(2014, time.November, 25, 17, 05, 46, 0, time.UTC),
},
},
{
claims: jose.Claims{
"sub": "123850281",
"name": "Elroy",
"email": "elroy@example.com",
"exp": int64(1416935146),
},
want: Identity{
ID: "123850281",
Name: "",
Email: "elroy@example.com",
ExpiresAt: time.Date(2014, time.November, 25, 17, 05, 46, 0, time.UTC),
},
},
{
claims: jose.Claims{
"sub": "123850281",
"name": "Elroy",
"email": "elroy@example.com",
},
want: Identity{
ID: "123850281",
Name: "",
Email: "elroy@example.com",
ExpiresAt: time.Time{},
},
},
}
for i, tt := range tests {
got, err := IdentityFromClaims(tt.claims)
if err != nil {
t.Errorf("case %d: unexpected error: %v", i, err)
continue
}
if !reflect.DeepEqual(tt.want, *got) {
t.Errorf("case %d: want=%#v got=%#v", i, tt.want, *got)
}
}
}
func TestIdentityFromClaimsFail(t *testing.T) {
tests := []jose.Claims{
// sub incorrect type
jose.Claims{
"sub": 123,
"name": "foo",
"email": "elroy@example.com",
},
// email incorrect type
jose.Claims{
"sub": "123850281",
"name": "Elroy",
"email": false,
},
// exp incorrect type
jose.Claims{
"sub": "123850281",
"name": "Elroy",
"email": "elroy@example.com",
"exp": "2014-11-25 18:05:46 +0000 UTC",
},
}
for i, tt := range tests {
_, err := IdentityFromClaims(tt)
if err == nil {
t.Errorf("case %d: expected non-nil error", i)
}
}
}

View file

@ -1,3 +0,0 @@
package oidc
type LoginFunc func(ident Identity, sessionKey string) (redirectURL string, err error)

View file

@ -1,67 +0,0 @@
package oidc
import (
"encoding/json"
"errors"
"net/http"
"time"
phttp "github.com/coreos/go-oidc/http"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/go-oidc/key"
)
// DefaultPublicKeySetTTL is the default TTL set on the PublicKeySet if no
// Cache-Control header is provided by the JWK Set document endpoint.
const DefaultPublicKeySetTTL = 24 * time.Hour
// NewRemotePublicKeyRepo is responsible for fetching the JWK Set document.
func NewRemotePublicKeyRepo(hc phttp.Client, ep string) *remotePublicKeyRepo {
return &remotePublicKeyRepo{hc: hc, ep: ep}
}
type remotePublicKeyRepo struct {
hc phttp.Client
ep string
}
// Get returns a PublicKeySet fetched from the JWK Set document endpoint. A TTL
// is set on the Key Set to avoid it having to be re-retrieved for every
// encryption event. This TTL is typically controlled by the endpoint returning
// a Cache-Control header, but defaults to 24 hours if no Cache-Control header
// is found.
func (r *remotePublicKeyRepo) Get() (key.KeySet, error) {
req, err := http.NewRequest("GET", r.ep, nil)
if err != nil {
return nil, err
}
resp, err := r.hc.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var d struct {
Keys []jose.JWK `json:"keys"`
}
if err := json.NewDecoder(resp.Body).Decode(&d); err != nil {
return nil, err
}
if len(d.Keys) == 0 {
return nil, errors.New("zero keys in response")
}
ttl, ok, err := phttp.Cacheable(resp.Header)
if err != nil {
return nil, err
}
if !ok {
ttl = DefaultPublicKeySetTTL
}
exp := time.Now().UTC().Add(ttl)
ks := key.NewPublicKeySet(d.Keys, exp)
return ks, nil
}

View file

@ -1,690 +0,0 @@
package oidc
import (
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/coreos/pkg/timeutil"
"github.com/jonboulle/clockwork"
phttp "github.com/coreos/go-oidc/http"
"github.com/coreos/go-oidc/oauth2"
)
const (
// Subject Identifier types defined by the OIDC spec. Specifies if the provider
// should provide the same sub claim value to all clients (public) or a unique
// value for each client (pairwise).
//
// See: http://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes
SubjectTypePublic = "public"
SubjectTypePairwise = "pairwise"
)
var (
// Default values for omitted provider config fields.
//
// Use ProviderConfig's Defaults method to fill a provider config with these values.
DefaultGrantTypesSupported = []string{oauth2.GrantTypeAuthCode, oauth2.GrantTypeImplicit}
DefaultResponseModesSupported = []string{"query", "fragment"}
DefaultTokenEndpointAuthMethodsSupported = []string{oauth2.AuthMethodClientSecretBasic}
DefaultClaimTypesSupported = []string{"normal"}
)
const (
MaximumProviderConfigSyncInterval = 24 * time.Hour
MinimumProviderConfigSyncInterval = time.Minute
discoveryConfigPath = "/.well-known/openid-configuration"
)
// internally configurable for tests
var minimumProviderConfigSyncInterval = MinimumProviderConfigSyncInterval
var (
// Ensure ProviderConfig satisfies these interfaces.
_ json.Marshaler = &ProviderConfig{}
_ json.Unmarshaler = &ProviderConfig{}
)
// ProviderConfig represents the OpenID Provider Metadata specifying what
// configurations a provider supports.
//
// See: http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
type ProviderConfig struct {
Issuer *url.URL // Required
AuthEndpoint *url.URL // Required
TokenEndpoint *url.URL // Required if grant types other than "implicit" are supported
UserInfoEndpoint *url.URL
KeysEndpoint *url.URL // Required
RegistrationEndpoint *url.URL
EndSessionEndpoint *url.URL
CheckSessionIFrame *url.URL
// Servers MAY choose not to advertise some supported scope values even when this
// parameter is used, although those defined in OpenID Core SHOULD be listed, if supported.
ScopesSupported []string
// OAuth2.0 response types supported.
ResponseTypesSupported []string // Required
// OAuth2.0 response modes supported.
//
// If omitted, defaults to DefaultResponseModesSupported.
ResponseModesSupported []string
// OAuth2.0 grant types supported.
//
// If omitted, defaults to DefaultGrantTypesSupported.
GrantTypesSupported []string
ACRValuesSupported []string
// SubjectTypesSupported specifies strategies for providing values for the sub claim.
SubjectTypesSupported []string // Required
// JWA signing and encryption algorith values supported for ID tokens.
IDTokenSigningAlgValues []string // Required
IDTokenEncryptionAlgValues []string
IDTokenEncryptionEncValues []string
// JWA signing and encryption algorith values supported for user info responses.
UserInfoSigningAlgValues []string
UserInfoEncryptionAlgValues []string
UserInfoEncryptionEncValues []string
// JWA signing and encryption algorith values supported for request objects.
ReqObjSigningAlgValues []string
ReqObjEncryptionAlgValues []string
ReqObjEncryptionEncValues []string
TokenEndpointAuthMethodsSupported []string
TokenEndpointAuthSigningAlgValuesSupported []string
DisplayValuesSupported []string
ClaimTypesSupported []string
ClaimsSupported []string
ServiceDocs *url.URL
ClaimsLocalsSupported []string
UILocalsSupported []string
ClaimsParameterSupported bool
RequestParameterSupported bool
RequestURIParamaterSupported bool
RequireRequestURIRegistration bool
Policy *url.URL
TermsOfService *url.URL
// Not part of the OpenID Provider Metadata
ExpiresAt time.Time
}
// Defaults returns a shallow copy of ProviderConfig with default
// values replacing omitted fields.
//
// var cfg oidc.ProviderConfig
// // Fill provider config with default values for omitted fields.
// cfg = cfg.Defaults()
//
func (p ProviderConfig) Defaults() ProviderConfig {
setDefault := func(val *[]string, defaultVal []string) {
if len(*val) == 0 {
*val = defaultVal
}
}
setDefault(&p.GrantTypesSupported, DefaultGrantTypesSupported)
setDefault(&p.ResponseModesSupported, DefaultResponseModesSupported)
setDefault(&p.TokenEndpointAuthMethodsSupported, DefaultTokenEndpointAuthMethodsSupported)
setDefault(&p.ClaimTypesSupported, DefaultClaimTypesSupported)
return p
}
func (p *ProviderConfig) MarshalJSON() ([]byte, error) {
e := p.toEncodableStruct()
return json.Marshal(&e)
}
func (p *ProviderConfig) UnmarshalJSON(data []byte) error {
var e encodableProviderConfig
if err := json.Unmarshal(data, &e); err != nil {
return err
}
conf, err := e.toStruct()
if err != nil {
return err
}
if err := conf.Valid(); err != nil {
return err
}
*p = conf
return nil
}
type encodableProviderConfig struct {
Issuer string `json:"issuer"`
AuthEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
UserInfoEndpoint string `json:"userinfo_endpoint,omitempty"`
KeysEndpoint string `json:"jwks_uri"`
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
CheckSessionIFrame string `json:"check_session_iframe,omitempty"`
// Use 'omitempty' for all slices as per OIDC spec:
// "Claims that return multiple values are represented as JSON arrays.
// Claims with zero elements MUST be omitted from the response."
// http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse
ScopesSupported []string `json:"scopes_supported,omitempty"`
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
ResponseModesSupported []string `json:"response_modes_supported,omitempty"`
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
ACRValuesSupported []string `json:"acr_values_supported,omitempty"`
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
IDTokenSigningAlgValues []string `json:"id_token_signing_alg_values_supported,omitempty"`
IDTokenEncryptionAlgValues []string `json:"id_token_encryption_alg_values_supported,omitempty"`
IDTokenEncryptionEncValues []string `json:"id_token_encryption_enc_values_supported,omitempty"`
UserInfoSigningAlgValues []string `json:"userinfo_signing_alg_values_supported,omitempty"`
UserInfoEncryptionAlgValues []string `json:"userinfo_encryption_alg_values_supported,omitempty"`
UserInfoEncryptionEncValues []string `json:"userinfo_encryption_enc_values_supported,omitempty"`
ReqObjSigningAlgValues []string `json:"request_object_signing_alg_values_supported,omitempty"`
ReqObjEncryptionAlgValues []string `json:"request_object_encryption_alg_values_supported,omitempty"`
ReqObjEncryptionEncValues []string `json:"request_object_encryption_enc_values_supported,omitempty"`
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`
TokenEndpointAuthSigningAlgValuesSupported []string `json:"token_endpoint_auth_signing_alg_values_supported,omitempty"`
DisplayValuesSupported []string `json:"display_values_supported,omitempty"`
ClaimTypesSupported []string `json:"claim_types_supported,omitempty"`
ClaimsSupported []string `json:"claims_supported,omitempty"`
ServiceDocs string `json:"service_documentation,omitempty"`
ClaimsLocalsSupported []string `json:"claims_locales_supported,omitempty"`
UILocalsSupported []string `json:"ui_locales_supported,omitempty"`
ClaimsParameterSupported bool `json:"claims_parameter_supported,omitempty"`
RequestParameterSupported bool `json:"request_parameter_supported,omitempty"`
RequestURIParamaterSupported bool `json:"request_uri_parameter_supported,omitempty"`
RequireRequestURIRegistration bool `json:"require_request_uri_registration,omitempty"`
Policy string `json:"op_policy_uri,omitempty"`
TermsOfService string `json:"op_tos_uri,omitempty"`
}
func (cfg ProviderConfig) toEncodableStruct() encodableProviderConfig {
return encodableProviderConfig{
Issuer: uriToString(cfg.Issuer),
AuthEndpoint: uriToString(cfg.AuthEndpoint),
TokenEndpoint: uriToString(cfg.TokenEndpoint),
UserInfoEndpoint: uriToString(cfg.UserInfoEndpoint),
KeysEndpoint: uriToString(cfg.KeysEndpoint),
RegistrationEndpoint: uriToString(cfg.RegistrationEndpoint),
EndSessionEndpoint: uriToString(cfg.EndSessionEndpoint),
CheckSessionIFrame: uriToString(cfg.CheckSessionIFrame),
ScopesSupported: cfg.ScopesSupported,
ResponseTypesSupported: cfg.ResponseTypesSupported,
ResponseModesSupported: cfg.ResponseModesSupported,
GrantTypesSupported: cfg.GrantTypesSupported,
ACRValuesSupported: cfg.ACRValuesSupported,
SubjectTypesSupported: cfg.SubjectTypesSupported,
IDTokenSigningAlgValues: cfg.IDTokenSigningAlgValues,
IDTokenEncryptionAlgValues: cfg.IDTokenEncryptionAlgValues,
IDTokenEncryptionEncValues: cfg.IDTokenEncryptionEncValues,
UserInfoSigningAlgValues: cfg.UserInfoSigningAlgValues,
UserInfoEncryptionAlgValues: cfg.UserInfoEncryptionAlgValues,
UserInfoEncryptionEncValues: cfg.UserInfoEncryptionEncValues,
ReqObjSigningAlgValues: cfg.ReqObjSigningAlgValues,
ReqObjEncryptionAlgValues: cfg.ReqObjEncryptionAlgValues,
ReqObjEncryptionEncValues: cfg.ReqObjEncryptionEncValues,
TokenEndpointAuthMethodsSupported: cfg.TokenEndpointAuthMethodsSupported,
TokenEndpointAuthSigningAlgValuesSupported: cfg.TokenEndpointAuthSigningAlgValuesSupported,
DisplayValuesSupported: cfg.DisplayValuesSupported,
ClaimTypesSupported: cfg.ClaimTypesSupported,
ClaimsSupported: cfg.ClaimsSupported,
ServiceDocs: uriToString(cfg.ServiceDocs),
ClaimsLocalsSupported: cfg.ClaimsLocalsSupported,
UILocalsSupported: cfg.UILocalsSupported,
ClaimsParameterSupported: cfg.ClaimsParameterSupported,
RequestParameterSupported: cfg.RequestParameterSupported,
RequestURIParamaterSupported: cfg.RequestURIParamaterSupported,
RequireRequestURIRegistration: cfg.RequireRequestURIRegistration,
Policy: uriToString(cfg.Policy),
TermsOfService: uriToString(cfg.TermsOfService),
}
}
func (e encodableProviderConfig) toStruct() (ProviderConfig, error) {
p := stickyErrParser{}
conf := ProviderConfig{
Issuer: p.parseURI(e.Issuer, "issuer"),
AuthEndpoint: p.parseURI(e.AuthEndpoint, "authorization_endpoint"),
TokenEndpoint: p.parseURI(e.TokenEndpoint, "token_endpoint"),
UserInfoEndpoint: p.parseURI(e.UserInfoEndpoint, "userinfo_endpoint"),
KeysEndpoint: p.parseURI(e.KeysEndpoint, "jwks_uri"),
RegistrationEndpoint: p.parseURI(e.RegistrationEndpoint, "registration_endpoint"),
EndSessionEndpoint: p.parseURI(e.EndSessionEndpoint, "end_session_endpoint"),
CheckSessionIFrame: p.parseURI(e.CheckSessionIFrame, "check_session_iframe"),
ScopesSupported: e.ScopesSupported,
ResponseTypesSupported: e.ResponseTypesSupported,
ResponseModesSupported: e.ResponseModesSupported,
GrantTypesSupported: e.GrantTypesSupported,
ACRValuesSupported: e.ACRValuesSupported,
SubjectTypesSupported: e.SubjectTypesSupported,
IDTokenSigningAlgValues: e.IDTokenSigningAlgValues,
IDTokenEncryptionAlgValues: e.IDTokenEncryptionAlgValues,
IDTokenEncryptionEncValues: e.IDTokenEncryptionEncValues,
UserInfoSigningAlgValues: e.UserInfoSigningAlgValues,
UserInfoEncryptionAlgValues: e.UserInfoEncryptionAlgValues,
UserInfoEncryptionEncValues: e.UserInfoEncryptionEncValues,
ReqObjSigningAlgValues: e.ReqObjSigningAlgValues,
ReqObjEncryptionAlgValues: e.ReqObjEncryptionAlgValues,
ReqObjEncryptionEncValues: e.ReqObjEncryptionEncValues,
TokenEndpointAuthMethodsSupported: e.TokenEndpointAuthMethodsSupported,
TokenEndpointAuthSigningAlgValuesSupported: e.TokenEndpointAuthSigningAlgValuesSupported,
DisplayValuesSupported: e.DisplayValuesSupported,
ClaimTypesSupported: e.ClaimTypesSupported,
ClaimsSupported: e.ClaimsSupported,
ServiceDocs: p.parseURI(e.ServiceDocs, "service_documentation"),
ClaimsLocalsSupported: e.ClaimsLocalsSupported,
UILocalsSupported: e.UILocalsSupported,
ClaimsParameterSupported: e.ClaimsParameterSupported,
RequestParameterSupported: e.RequestParameterSupported,
RequestURIParamaterSupported: e.RequestURIParamaterSupported,
RequireRequestURIRegistration: e.RequireRequestURIRegistration,
Policy: p.parseURI(e.Policy, "op_policy-uri"),
TermsOfService: p.parseURI(e.TermsOfService, "op_tos_uri"),
}
if p.firstErr != nil {
return ProviderConfig{}, p.firstErr
}
return conf, nil
}
// Empty returns if a ProviderConfig holds no information.
//
// This case generally indicates a ProviderConfigGetter has experienced an error
// and has nothing to report.
func (p ProviderConfig) Empty() bool {
return p.Issuer == nil
}
func contains(sli []string, ele string) bool {
for _, s := range sli {
if s == ele {
return true
}
}
return false
}
// Valid determines if a ProviderConfig conforms with the OIDC specification.
// If Valid returns successfully it guarantees required field are non-nil and
// URLs are well formed.
//
// Valid is called by UnmarshalJSON.
//
// NOTE(ericchiang): For development purposes Valid does not mandate 'https' for
// URLs fields where the OIDC spec requires it. This may change in future releases
// of this package. See: https://github.com/coreos/go-oidc/issues/34
func (p ProviderConfig) Valid() error {
grantTypes := p.GrantTypesSupported
if len(grantTypes) == 0 {
grantTypes = DefaultGrantTypesSupported
}
implicitOnly := true
for _, grantType := range grantTypes {
if grantType != oauth2.GrantTypeImplicit {
implicitOnly = false
break
}
}
if len(p.SubjectTypesSupported) == 0 {
return errors.New("missing required field subject_types_supported")
}
if len(p.IDTokenSigningAlgValues) == 0 {
return errors.New("missing required field id_token_signing_alg_values_supported")
}
if len(p.ScopesSupported) != 0 && !contains(p.ScopesSupported, "openid") {
return errors.New("scoped_supported must be unspecified or include 'openid'")
}
if !contains(p.IDTokenSigningAlgValues, "RS256") {
return errors.New("id_token_signing_alg_values_supported must include 'RS256'")
}
if contains(p.TokenEndpointAuthMethodsSupported, "none") {
return errors.New("token_endpoint_auth_signing_alg_values_supported cannot include 'none'")
}
uris := []struct {
val *url.URL
name string
required bool
}{
{p.Issuer, "issuer", true},
{p.AuthEndpoint, "authorization_endpoint", true},
{p.TokenEndpoint, "token_endpoint", !implicitOnly},
{p.UserInfoEndpoint, "userinfo_endpoint", false},
{p.KeysEndpoint, "jwks_uri", true},
{p.RegistrationEndpoint, "registration_endpoint", false},
{p.EndSessionEndpoint, "end_session_endpoint", false},
{p.CheckSessionIFrame, "check_session_iframe", false},
{p.ServiceDocs, "service_documentation", false},
{p.Policy, "op_policy_uri", false},
{p.TermsOfService, "op_tos_uri", false},
}
for _, uri := range uris {
if uri.val == nil {
if !uri.required {
continue
}
return fmt.Errorf("empty value for required uri field %s", uri.name)
}
if uri.val.Host == "" {
return fmt.Errorf("no host for uri field %s", uri.name)
}
if uri.val.Scheme != "http" && uri.val.Scheme != "https" {
return fmt.Errorf("uri field %s schemeis not http or https", uri.name)
}
}
return nil
}
// Supports determines if provider supports a client given their respective metadata.
func (p ProviderConfig) Supports(c ClientMetadata) error {
if err := p.Valid(); err != nil {
return fmt.Errorf("invalid provider config: %v", err)
}
if err := c.Valid(); err != nil {
return fmt.Errorf("invalid client config: %v", err)
}
// Fill default values for omitted fields
c = c.Defaults()
p = p.Defaults()
// Do the supported values list the requested one?
supports := []struct {
supported []string
requested string
name string
}{
{p.IDTokenSigningAlgValues, c.IDTokenResponseOptions.SigningAlg, "id_token_signed_response_alg"},
{p.IDTokenEncryptionAlgValues, c.IDTokenResponseOptions.EncryptionAlg, "id_token_encryption_response_alg"},
{p.IDTokenEncryptionEncValues, c.IDTokenResponseOptions.EncryptionEnc, "id_token_encryption_response_enc"},
{p.UserInfoSigningAlgValues, c.UserInfoResponseOptions.SigningAlg, "userinfo_signed_response_alg"},
{p.UserInfoEncryptionAlgValues, c.UserInfoResponseOptions.EncryptionAlg, "userinfo_encryption_response_alg"},
{p.UserInfoEncryptionEncValues, c.UserInfoResponseOptions.EncryptionEnc, "userinfo_encryption_response_enc"},
{p.ReqObjSigningAlgValues, c.RequestObjectOptions.SigningAlg, "request_object_signing_alg"},
{p.ReqObjEncryptionAlgValues, c.RequestObjectOptions.EncryptionAlg, "request_object_encryption_alg"},
{p.ReqObjEncryptionEncValues, c.RequestObjectOptions.EncryptionEnc, "request_object_encryption_enc"},
}
for _, field := range supports {
if field.requested == "" {
continue
}
if !contains(field.supported, field.requested) {
return fmt.Errorf("provider does not support requested value for field %s", field.name)
}
}
stringsEqual := func(s1, s2 string) bool { return s1 == s2 }
// For lists, are the list of requested values a subset of the supported ones?
supportsAll := []struct {
supported []string
requested []string
name string
// OAuth2.0 response_type can be space separated lists where order doesn't matter.
// For example "id_token token" is the same as "token id_token"
// Support a custom compare method.
comp func(s1, s2 string) bool
}{
{p.GrantTypesSupported, c.GrantTypes, "grant_types", stringsEqual},
{p.ResponseTypesSupported, c.ResponseTypes, "response_type", oauth2.ResponseTypesEqual},
}
for _, field := range supportsAll {
requestLoop:
for _, req := range field.requested {
for _, sup := range field.supported {
if field.comp(req, sup) {
continue requestLoop
}
}
return fmt.Errorf("provider does not support requested value for field %s", field.name)
}
}
// TODO(ericchiang): Are there more checks we feel comfortable with begin strict about?
return nil
}
func (p ProviderConfig) SupportsGrantType(grantType string) bool {
var supported []string
if len(p.GrantTypesSupported) == 0 {
supported = DefaultGrantTypesSupported
} else {
supported = p.GrantTypesSupported
}
for _, t := range supported {
if t == grantType {
return true
}
}
return false
}
type ProviderConfigGetter interface {
Get() (ProviderConfig, error)
}
type ProviderConfigSetter interface {
Set(ProviderConfig) error
}
type ProviderConfigSyncer struct {
from ProviderConfigGetter
to ProviderConfigSetter
clock clockwork.Clock
initialSyncDone bool
initialSyncWait sync.WaitGroup
}
func NewProviderConfigSyncer(from ProviderConfigGetter, to ProviderConfigSetter) *ProviderConfigSyncer {
return &ProviderConfigSyncer{
from: from,
to: to,
clock: clockwork.NewRealClock(),
}
}
func (s *ProviderConfigSyncer) Run() chan struct{} {
stop := make(chan struct{})
var next pcsStepper
next = &pcsStepNext{aft: time.Duration(0)}
s.initialSyncWait.Add(1)
go func() {
for {
select {
case <-s.clock.After(next.after()):
next = next.step(s.sync)
case <-stop:
return
}
}
}()
return stop
}
func (s *ProviderConfigSyncer) WaitUntilInitialSync() {
s.initialSyncWait.Wait()
}
func (s *ProviderConfigSyncer) sync() (time.Duration, error) {
cfg, err := s.from.Get()
if err != nil {
return 0, err
}
if err = s.to.Set(cfg); err != nil {
return 0, fmt.Errorf("error setting provider config: %v", err)
}
if !s.initialSyncDone {
s.initialSyncWait.Done()
s.initialSyncDone = true
}
return nextSyncAfter(cfg.ExpiresAt, s.clock), nil
}
type pcsStepFunc func() (time.Duration, error)
type pcsStepper interface {
after() time.Duration
step(pcsStepFunc) pcsStepper
}
type pcsStepNext struct {
aft time.Duration
}
func (n *pcsStepNext) after() time.Duration {
return n.aft
}
func (n *pcsStepNext) step(fn pcsStepFunc) (next pcsStepper) {
ttl, err := fn()
if err == nil {
next = &pcsStepNext{aft: ttl}
} else {
next = &pcsStepRetry{aft: time.Second}
log.Printf("go-oidc: provider config sync failed, retrying in %v: %v", next.after(), err)
}
return
}
type pcsStepRetry struct {
aft time.Duration
}
func (r *pcsStepRetry) after() time.Duration {
return r.aft
}
func (r *pcsStepRetry) step(fn pcsStepFunc) (next pcsStepper) {
ttl, err := fn()
if err == nil {
next = &pcsStepNext{aft: ttl}
} else {
next = &pcsStepRetry{aft: timeutil.ExpBackoff(r.aft, time.Minute)}
log.Printf("go-oidc: provider config sync failed, retrying in %v: %v", next.after(), err)
}
return
}
func nextSyncAfter(exp time.Time, clock clockwork.Clock) time.Duration {
if exp.IsZero() {
return MaximumProviderConfigSyncInterval
}
t := exp.Sub(clock.Now()) / 2
if t > MaximumProviderConfigSyncInterval {
t = MaximumProviderConfigSyncInterval
} else if t < minimumProviderConfigSyncInterval {
t = minimumProviderConfigSyncInterval
}
return t
}
type httpProviderConfigGetter struct {
hc phttp.Client
issuerURL string
clock clockwork.Clock
}
func NewHTTPProviderConfigGetter(hc phttp.Client, issuerURL string) *httpProviderConfigGetter {
return &httpProviderConfigGetter{
hc: hc,
issuerURL: issuerURL,
clock: clockwork.NewRealClock(),
}
}
func (r *httpProviderConfigGetter) Get() (cfg ProviderConfig, err error) {
// If the Issuer value contains a path component, any terminating / MUST be removed before
// appending /.well-known/openid-configuration.
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationRequest
discoveryURL := strings.TrimSuffix(r.issuerURL, "/") + discoveryConfigPath
req, err := http.NewRequest("GET", discoveryURL, nil)
if err != nil {
return
}
resp, err := r.hc.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if err = json.NewDecoder(resp.Body).Decode(&cfg); err != nil {
return
}
var ttl time.Duration
var ok bool
ttl, ok, err = phttp.Cacheable(resp.Header)
if err != nil {
return
} else if ok {
cfg.ExpiresAt = r.clock.Now().UTC().Add(ttl)
}
// The issuer value returned MUST be identical to the Issuer URL that was directly used to retrieve the configuration information.
// http://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationValidation
if !urlEqual(cfg.Issuer.String(), r.issuerURL) {
err = fmt.Errorf(`"issuer" in config (%v) does not match provided issuer URL (%v)`, cfg.Issuer, r.issuerURL)
return
}
return
}
func FetchProviderConfig(hc phttp.Client, issuerURL string) (ProviderConfig, error) {
if hc == nil {
hc = http.DefaultClient
}
g := NewHTTPProviderConfigGetter(hc, issuerURL)
return g.Get()
}
func WaitForProviderConfig(hc phttp.Client, issuerURL string) (pcfg ProviderConfig) {
return waitForProviderConfig(hc, issuerURL, clockwork.NewRealClock())
}
func waitForProviderConfig(hc phttp.Client, issuerURL string, clock clockwork.Clock) (pcfg ProviderConfig) {
var sleep time.Duration
var err error
for {
pcfg, err = FetchProviderConfig(hc, issuerURL)
if err == nil {
break
}
sleep = timeutil.ExpBackoff(sleep, time.Minute)
fmt.Printf("Failed fetching provider config, trying again in %v: %v\n", sleep, err)
time.Sleep(sleep)
}
return
}

View file

@ -1,940 +0,0 @@
package oidc
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"testing"
"time"
"github.com/jonboulle/clockwork"
"github.com/kylelemons/godebug/diff"
"github.com/kylelemons/godebug/pretty"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/go-oidc/oauth2"
)
func TestProviderConfigDefaults(t *testing.T) {
var cfg ProviderConfig
cfg = cfg.Defaults()
tests := []struct {
got, want []string
name string
}{
{cfg.GrantTypesSupported, DefaultGrantTypesSupported, "grant types"},
{cfg.ResponseModesSupported, DefaultResponseModesSupported, "response modes"},
{cfg.ClaimTypesSupported, DefaultClaimTypesSupported, "claim types"},
{
cfg.TokenEndpointAuthMethodsSupported,
DefaultTokenEndpointAuthMethodsSupported,
"token endpoint auth methods",
},
}
for _, tt := range tests {
if diff := pretty.Compare(tt.want, tt.got); diff != "" {
t.Errorf("%s: did not match %s", tt.name, diff)
}
}
}
func TestProviderConfigUnmarshal(t *testing.T) {
// helper for quickly creating uris
uri := func(path string) *url.URL {
return &url.URL{
Scheme: "https",
Host: "server.example.com",
Path: path,
}
}
tests := []struct {
data string
want ProviderConfig
wantErr bool
}{
{
data: `{
"issuer": "https://server.example.com",
"authorization_endpoint": "https://server.example.com/connect/authorize",
"token_endpoint": "https://server.example.com/connect/token",
"token_endpoint_auth_methods_supported": ["client_secret_basic", "private_key_jwt"],
"token_endpoint_auth_signing_alg_values_supported": ["RS256", "ES256"],
"userinfo_endpoint": "https://server.example.com/connect/userinfo",
"jwks_uri": "https://server.example.com/jwks.json",
"registration_endpoint": "https://server.example.com/connect/register",
"scopes_supported": [
"openid", "profile", "email", "address", "phone", "offline_access"
],
"response_types_supported": [
"code", "code id_token", "id_token", "id_token token"
],
"acr_values_supported": [
"urn:mace:incommon:iap:silver", "urn:mace:incommon:iap:bronze"
],
"subject_types_supported": ["public", "pairwise"],
"userinfo_signing_alg_values_supported": ["RS256", "ES256", "HS256"],
"userinfo_encryption_alg_values_supported": ["RSA1_5", "A128KW"],
"userinfo_encryption_enc_values_supported": ["A128CBC-HS256", "A128GCM"],
"id_token_signing_alg_values_supported": ["RS256", "ES256", "HS256"],
"id_token_encryption_alg_values_supported": ["RSA1_5", "A128KW"],
"id_token_encryption_enc_values_supported": ["A128CBC-HS256", "A128GCM"],
"request_object_signing_alg_values_supported": ["none", "RS256", "ES256"],
"display_values_supported": ["page", "popup"],
"claim_types_supported": ["normal", "distributed"],
"claims_supported": [
"sub", "iss", "auth_time", "acr", "name", "given_name",
"family_name", "nickname", "profile", "picture", "website",
"email", "email_verified", "locale", "zoneinfo",
"http://example.info/claims/groups"
],
"claims_parameter_supported": true,
"service_documentation": "https://server.example.com/connect/service_documentation.html",
"ui_locales_supported": ["en-US", "en-GB", "en-CA", "fr-FR", "fr-CA"]
}
`,
want: ProviderConfig{
Issuer: &url.URL{Scheme: "https", Host: "server.example.com"},
AuthEndpoint: uri("/connect/authorize"),
TokenEndpoint: uri("/connect/token"),
TokenEndpointAuthMethodsSupported: []string{
oauth2.AuthMethodClientSecretBasic, oauth2.AuthMethodPrivateKeyJWT,
},
TokenEndpointAuthSigningAlgValuesSupported: []string{
jose.AlgRS256, jose.AlgES256,
},
UserInfoEndpoint: uri("/connect/userinfo"),
KeysEndpoint: uri("/jwks.json"),
RegistrationEndpoint: uri("/connect/register"),
ScopesSupported: []string{
"openid", "profile", "email", "address", "phone", "offline_access",
},
ResponseTypesSupported: []string{
oauth2.ResponseTypeCode, oauth2.ResponseTypeCodeIDToken,
oauth2.ResponseTypeIDToken, oauth2.ResponseTypeIDTokenToken,
},
ACRValuesSupported: []string{
"urn:mace:incommon:iap:silver", "urn:mace:incommon:iap:bronze",
},
SubjectTypesSupported: []string{
SubjectTypePublic, SubjectTypePairwise,
},
UserInfoSigningAlgValues: []string{jose.AlgRS256, jose.AlgES256, jose.AlgHS256},
UserInfoEncryptionAlgValues: []string{"RSA1_5", "A128KW"},
UserInfoEncryptionEncValues: []string{"A128CBC-HS256", "A128GCM"},
IDTokenSigningAlgValues: []string{jose.AlgRS256, jose.AlgES256, jose.AlgHS256},
IDTokenEncryptionAlgValues: []string{"RSA1_5", "A128KW"},
IDTokenEncryptionEncValues: []string{"A128CBC-HS256", "A128GCM"},
ReqObjSigningAlgValues: []string{jose.AlgNone, jose.AlgRS256, jose.AlgES256},
DisplayValuesSupported: []string{"page", "popup"},
ClaimTypesSupported: []string{"normal", "distributed"},
ClaimsSupported: []string{
"sub", "iss", "auth_time", "acr", "name", "given_name",
"family_name", "nickname", "profile", "picture", "website",
"email", "email_verified", "locale", "zoneinfo",
"http://example.info/claims/groups",
},
ClaimsParameterSupported: true,
ServiceDocs: uri("/connect/service_documentation.html"),
UILocalsSupported: []string{"en-US", "en-GB", "en-CA", "fr-FR", "fr-CA"},
},
wantErr: false,
},
{
// missing a lot of required field
data: `{}`,
wantErr: true,
},
{
data: `{
"issuer": "https://server.example.com",
"authorization_endpoint": "https://server.example.com/connect/authorize",
"token_endpoint": "https://server.example.com/connect/token",
"jwks_uri": "https://server.example.com/jwks.json",
"response_types_supported": [
"code", "code id_token", "id_token", "id_token token"
],
"subject_types_supported": ["public", "pairwise"],
"id_token_signing_alg_values_supported": ["RS256", "ES256", "HS256"]
}
`,
want: ProviderConfig{
Issuer: &url.URL{Scheme: "https", Host: "server.example.com"},
AuthEndpoint: uri("/connect/authorize"),
TokenEndpoint: uri("/connect/token"),
KeysEndpoint: uri("/jwks.json"),
ResponseTypesSupported: []string{
oauth2.ResponseTypeCode, oauth2.ResponseTypeCodeIDToken,
oauth2.ResponseTypeIDToken, oauth2.ResponseTypeIDTokenToken,
},
SubjectTypesSupported: []string{
SubjectTypePublic, SubjectTypePairwise,
},
IDTokenSigningAlgValues: []string{jose.AlgRS256, jose.AlgES256, jose.AlgHS256},
},
wantErr: false,
},
{
// invalid scheme 'ftp://'
data: `{
"issuer": "https://server.example.com",
"authorization_endpoint": "https://server.example.com/connect/authorize",
"token_endpoint": "https://server.example.com/connect/token",
"jwks_uri": "ftp://server.example.com/jwks.json",
"response_types_supported": [
"code", "code id_token", "id_token", "id_token token"
],
"subject_types_supported": ["public", "pairwise"],
"id_token_signing_alg_values_supported": ["RS256", "ES256", "HS256"]
}
`,
wantErr: true,
},
}
for i, tt := range tests {
var got ProviderConfig
if err := json.Unmarshal([]byte(tt.data), &got); err != nil {
if !tt.wantErr {
t.Errorf("case %d: failed to unmarshal provider config: %v", i, err)
}
continue
}
if tt.wantErr {
t.Errorf("case %d: expected error", i)
continue
}
if diff := pretty.Compare(tt.want, got); diff != "" {
t.Errorf("case %d: unmarshaled struct did not match expected %s", i, diff)
}
}
}
func TestProviderConfigMarshal(t *testing.T) {
tests := []struct {
cfg ProviderConfig
want string
}{
{
cfg: ProviderConfig{
Issuer: &url.URL{Scheme: "https", Host: "auth.example.com"},
AuthEndpoint: &url.URL{
Scheme: "https", Host: "auth.example.com", Path: "/auth",
},
TokenEndpoint: &url.URL{
Scheme: "https", Host: "auth.example.com", Path: "/token",
},
UserInfoEndpoint: &url.URL{
Scheme: "https", Host: "auth.example.com", Path: "/userinfo",
},
KeysEndpoint: &url.URL{
Scheme: "https", Host: "auth.example.com", Path: "/jwk",
},
ResponseTypesSupported: []string{oauth2.ResponseTypeCode},
SubjectTypesSupported: []string{SubjectTypePublic},
IDTokenSigningAlgValues: []string{jose.AlgRS256},
},
// spacing must match json.MarshalIndent(cfg, "", "\t")
want: `{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/auth",
"token_endpoint": "https://auth.example.com/token",
"userinfo_endpoint": "https://auth.example.com/userinfo",
"jwks_uri": "https://auth.example.com/jwk",
"response_types_supported": [
"code"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
]
}`,
},
{
cfg: ProviderConfig{
Issuer: &url.URL{Scheme: "https", Host: "auth.example.com"},
AuthEndpoint: &url.URL{
Scheme: "https", Host: "auth.example.com", Path: "/auth",
},
TokenEndpoint: &url.URL{
Scheme: "https", Host: "auth.example.com", Path: "/token",
},
UserInfoEndpoint: &url.URL{
Scheme: "https", Host: "auth.example.com", Path: "/userinfo",
},
KeysEndpoint: &url.URL{
Scheme: "https", Host: "auth.example.com", Path: "/jwk",
},
RegistrationEndpoint: &url.URL{
Scheme: "https", Host: "auth.example.com", Path: "/register",
},
ScopesSupported: DefaultScope,
ResponseTypesSupported: []string{oauth2.ResponseTypeCode},
ResponseModesSupported: DefaultResponseModesSupported,
GrantTypesSupported: []string{oauth2.GrantTypeAuthCode},
SubjectTypesSupported: []string{SubjectTypePublic},
IDTokenSigningAlgValues: []string{jose.AlgRS256},
ServiceDocs: &url.URL{Scheme: "https", Host: "example.com", Path: "/docs"},
},
// spacing must match json.MarshalIndent(cfg, "", "\t")
want: `{
"issuer": "https://auth.example.com",
"authorization_endpoint": "https://auth.example.com/auth",
"token_endpoint": "https://auth.example.com/token",
"userinfo_endpoint": "https://auth.example.com/userinfo",
"jwks_uri": "https://auth.example.com/jwk",
"registration_endpoint": "https://auth.example.com/register",
"scopes_supported": [
"openid",
"email",
"profile"
],
"response_types_supported": [
"code"
],
"response_modes_supported": [
"query",
"fragment"
],
"grant_types_supported": [
"authorization_code"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"service_documentation": "https://example.com/docs"
}`,
},
}
for i, tt := range tests {
got, err := json.MarshalIndent(&tt.cfg, "", "\t")
if err != nil {
t.Errorf("case %d: failed to marshal config: %v", i, err)
continue
}
if d := diff.Diff(string(got), string(tt.want)); d != "" {
t.Errorf("case %d: expected did not match result: %s", i, d)
}
var cfg ProviderConfig
if err := json.Unmarshal(got, &cfg); err != nil {
t.Errorf("case %d: could not unmarshal marshal response: %v", i, err)
continue
}
if d := pretty.Compare(tt.cfg, cfg); d != "" {
t.Errorf("case %d: config did not survive JSON marshaling round trip: %s", i, d)
}
}
}
func TestProviderConfigSupports(t *testing.T) {
tests := []struct {
provider ProviderConfig
client ClientMetadata
fillRequiredProviderFields bool
ok bool
}{
{
provider: ProviderConfig{},
client: ClientMetadata{
RedirectURIs: []url.URL{
{Scheme: "https", Host: "example.com", Path: "/callback"},
},
},
fillRequiredProviderFields: true,
ok: true,
},
{
// invalid provider config
provider: ProviderConfig{},
client: ClientMetadata{
RedirectURIs: []url.URL{
{Scheme: "https", Host: "example.com", Path: "/callback"},
},
},
fillRequiredProviderFields: false,
ok: false,
},
{
// invalid client config
provider: ProviderConfig{},
client: ClientMetadata{},
fillRequiredProviderFields: true,
ok: false,
},
}
for i, tt := range tests {
if tt.fillRequiredProviderFields {
tt.provider = fillRequiredProviderFields(tt.provider)
}
err := tt.provider.Supports(tt.client)
if err == nil && !tt.ok {
t.Errorf("case %d: expected non-nil error", i)
}
if err != nil && tt.ok {
t.Errorf("case %d: supports failed: %v", i, err)
}
}
}
func newValidProviderConfig() ProviderConfig {
var cfg ProviderConfig
return fillRequiredProviderFields(cfg)
}
// fill a provider config with enough information to be valid
func fillRequiredProviderFields(cfg ProviderConfig) ProviderConfig {
if cfg.Issuer == nil {
cfg.Issuer = &url.URL{Scheme: "https", Host: "auth.example.com"}
}
urlPath := func(path string) *url.URL {
var u url.URL
u = *cfg.Issuer
u.Path = path
return &u
}
cfg.AuthEndpoint = urlPath("/auth")
cfg.TokenEndpoint = urlPath("/token")
cfg.UserInfoEndpoint = urlPath("/userinfo")
cfg.KeysEndpoint = urlPath("/jwk")
cfg.ResponseTypesSupported = []string{oauth2.ResponseTypeCode}
cfg.SubjectTypesSupported = []string{SubjectTypePublic}
cfg.IDTokenSigningAlgValues = []string{jose.AlgRS256}
return cfg
}
type fakeProviderConfigGetterSetter struct {
cfg *ProviderConfig
getCount int
setCount int
}
func (g *fakeProviderConfigGetterSetter) Get() (ProviderConfig, error) {
g.getCount++
return *g.cfg, nil
}
func (g *fakeProviderConfigGetterSetter) Set(cfg ProviderConfig) error {
g.cfg = &cfg
g.setCount++
return nil
}
type fakeProviderConfigHandler struct {
cfg ProviderConfig
maxAge time.Duration
}
func (s *fakeProviderConfigHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
b, _ := json.Marshal(&s.cfg)
if s.maxAge.Seconds() >= 0 {
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", int(s.maxAge.Seconds())))
}
w.Header().Set("Content-Type", "application/json")
w.Write(b)
}
func TestProviderConfigRequiredFields(t *testing.T) {
// Ensure provider metadata responses have all the required fields.
// taken from https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
requiredFields := []string{
"issuer",
"authorization_endpoint",
"token_endpoint", // "This is REQUIRED unless only the Implicit Flow is used."
"jwks_uri",
"response_types_supported",
"subject_types_supported",
"id_token_signing_alg_values_supported",
}
svr := &fakeProviderConfigHandler{
cfg: ProviderConfig{
Issuer: &url.URL{Scheme: "http", Host: "example.com"},
ExpiresAt: time.Now().Add(time.Minute),
},
maxAge: time.Minute,
}
svr.cfg = fillRequiredProviderFields(svr.cfg)
s := httptest.NewServer(svr)
defer s.Close()
resp, err := http.Get(s.URL + "/")
if err != nil {
t.Errorf("get: %v", err)
return
}
defer resp.Body.Close()
var data map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
t.Errorf("decode: %v", err)
return
}
for _, field := range requiredFields {
if _, ok := data[field]; !ok {
t.Errorf("provider metadata does not have required field '%s'", field)
}
}
}
type handlerClient struct {
Handler http.Handler
}
func (hc *handlerClient) Do(r *http.Request) (*http.Response, error) {
w := httptest.NewRecorder()
hc.Handler.ServeHTTP(w, r)
resp := http.Response{
StatusCode: w.Code,
Header: w.Header(),
Body: ioutil.NopCloser(w.Body),
}
return &resp, nil
}
func TestHTTPProviderConfigGetter(t *testing.T) {
svr := &fakeProviderConfigHandler{}
hc := &handlerClient{Handler: svr}
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
tests := []struct {
dsc string
age time.Duration
cfg ProviderConfig
ok bool
}{
// everything is good
{
dsc: "https://example.com",
age: time.Minute,
cfg: ProviderConfig{
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
ExpiresAt: now.Add(time.Minute),
},
ok: true,
},
// iss and disco url differ by scheme only (how google works)
{
dsc: "https://example.com",
age: time.Minute,
cfg: ProviderConfig{
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
ExpiresAt: now.Add(time.Minute),
},
ok: true,
},
// issuer and discovery URL mismatch
{
dsc: "https://foo.com",
age: time.Minute,
cfg: ProviderConfig{
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
ExpiresAt: now.Add(time.Minute),
},
ok: false,
},
// missing cache header results in zero ExpiresAt
{
dsc: "https://example.com",
age: -1,
cfg: ProviderConfig{
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
},
ok: true,
},
}
for i, tt := range tests {
tt.cfg = fillRequiredProviderFields(tt.cfg)
svr.cfg = tt.cfg
svr.maxAge = tt.age
getter := NewHTTPProviderConfigGetter(hc, tt.dsc)
getter.clock = fc
got, err := getter.Get()
if err != nil {
if tt.ok {
t.Errorf("test %d: unexpected error: %v", i, err)
}
continue
}
if !tt.ok {
t.Errorf("test %d: expected error", i)
continue
}
if !reflect.DeepEqual(tt.cfg, got) {
t.Errorf("test %d: want: %#v, got: %#v", i, tt.cfg, got)
}
}
}
func TestProviderConfigSyncerRun(t *testing.T) {
c1 := &ProviderConfig{
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
}
c2 := &ProviderConfig{
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
}
tests := []struct {
first *ProviderConfig
advance time.Duration
second *ProviderConfig
firstExp time.Duration
secondExp time.Duration
count int
}{
// exp is 10m, should have same config after 1s
{
first: c1,
firstExp: time.Duration(10 * time.Minute),
advance: time.Minute,
second: c1,
secondExp: time.Duration(10 * time.Minute),
count: 1,
},
// exp is 10m, should have new config after 10/2 = 5m
{
first: c1,
firstExp: time.Duration(10 * time.Minute),
advance: time.Duration(5 * time.Minute),
second: c2,
secondExp: time.Duration(10 * time.Minute),
count: 2,
},
// exp is 20m, should have new config after 20/2 = 10m
{
first: c1,
firstExp: time.Duration(20 * time.Minute),
advance: time.Duration(10 * time.Minute),
second: c2,
secondExp: time.Duration(30 * time.Minute),
count: 2,
},
}
assertCfg := func(i int, to *fakeProviderConfigGetterSetter, want ProviderConfig) {
got, err := to.Get()
if err != nil {
t.Fatalf("test %d: unable to get config: %v", i, err)
}
if !reflect.DeepEqual(want, got) {
t.Fatalf("test %d: incorrect state:\nwant=%#v\ngot=%#v", i, want, got)
}
}
for i, tt := range tests {
from := &fakeProviderConfigGetterSetter{}
to := &fakeProviderConfigGetterSetter{}
fc := clockwork.NewFakeClock()
now := fc.Now().UTC()
syncer := NewProviderConfigSyncer(from, to)
syncer.clock = fc
tt.first.ExpiresAt = now.Add(tt.firstExp)
tt.second.ExpiresAt = now.Add(tt.secondExp)
if err := from.Set(*tt.first); err != nil {
t.Fatalf("test %d: unexpected error: %v", i, err)
}
stop := syncer.Run()
defer close(stop)
fc.BlockUntil(1)
// first sync
assertCfg(i, to, *tt.first)
if err := from.Set(*tt.second); err != nil {
t.Fatalf("test %d: unexpected error: %v", i, err)
}
fc.Advance(tt.advance)
fc.BlockUntil(1)
// second sync
assertCfg(i, to, *tt.second)
if tt.count != from.getCount {
t.Fatalf("test %d: want: %v, got: %v", i, tt.count, from.getCount)
}
}
}
type staticProviderConfigGetter struct {
cfg ProviderConfig
err error
}
func (g *staticProviderConfigGetter) Get() (ProviderConfig, error) {
return g.cfg, g.err
}
type staticProviderConfigSetter struct {
cfg *ProviderConfig
err error
}
func (s *staticProviderConfigSetter) Set(cfg ProviderConfig) error {
s.cfg = &cfg
return s.err
}
func TestProviderConfigSyncerSyncFailure(t *testing.T) {
fc := clockwork.NewFakeClock()
tests := []struct {
from *staticProviderConfigGetter
to *staticProviderConfigSetter
// want indicates what ProviderConfig should be passed to Set.
// If nil, the Set should not be called.
want *ProviderConfig
}{
// generic Get failure
{
from: &staticProviderConfigGetter{err: errors.New("fail")},
to: &staticProviderConfigSetter{},
want: nil,
},
// generic Set failure
{
from: &staticProviderConfigGetter{cfg: ProviderConfig{ExpiresAt: fc.Now().Add(time.Minute)}},
to: &staticProviderConfigSetter{err: errors.New("fail")},
want: &ProviderConfig{ExpiresAt: fc.Now().Add(time.Minute)},
},
}
for i, tt := range tests {
pcs := &ProviderConfigSyncer{
from: tt.from,
to: tt.to,
clock: fc,
}
_, err := pcs.sync()
if err == nil {
t.Errorf("case %d: expected non-nil error", i)
}
if !reflect.DeepEqual(tt.want, tt.to.cfg) {
t.Errorf("case %d: Set mismatch: want=%#v got=%#v", i, tt.want, tt.to.cfg)
}
}
}
func TestNextSyncAfter(t *testing.T) {
fc := clockwork.NewFakeClock()
tests := []struct {
exp time.Time
want time.Duration
}{
{
exp: fc.Now().Add(time.Hour),
want: 30 * time.Minute,
},
// override large values with the maximum
{
exp: fc.Now().Add(168 * time.Hour), // one week
want: 24 * time.Hour,
},
// override "now" values with the minimum
{
exp: fc.Now(),
want: time.Minute,
},
// override negative values with the minimum
{
exp: fc.Now().Add(-1 * time.Minute),
want: time.Minute,
},
// zero-value Time results in maximum sync interval
{
exp: time.Time{},
want: 24 * time.Hour,
},
}
for i, tt := range tests {
got := nextSyncAfter(tt.exp, fc)
if tt.want != got {
t.Errorf("case %d: want=%v got=%v", i, tt.want, got)
}
}
}
func TestProviderConfigEmpty(t *testing.T) {
cfg := ProviderConfig{}
if !cfg.Empty() {
t.Fatalf("Empty provider config reports non-empty")
}
cfg = ProviderConfig{
Issuer: &url.URL{Scheme: "https", Host: "example.com"},
}
if cfg.Empty() {
t.Fatalf("Non-empty provider config reports empty")
}
}
func TestPCSStepAfter(t *testing.T) {
pass := func() (time.Duration, error) { return 7 * time.Second, nil }
fail := func() (time.Duration, error) { return 0, errors.New("fail") }
tests := []struct {
stepper pcsStepper
stepFunc pcsStepFunc
want pcsStepper
}{
// good step results in retry at TTL
{
stepper: &pcsStepNext{},
stepFunc: pass,
want: &pcsStepNext{aft: 7 * time.Second},
},
// good step after failed step results results in retry at TTL
{
stepper: &pcsStepRetry{aft: 2 * time.Second},
stepFunc: pass,
want: &pcsStepNext{aft: 7 * time.Second},
},
// failed step results in a retry in 1s
{
stepper: &pcsStepNext{},
stepFunc: fail,
want: &pcsStepRetry{aft: time.Second},
},
// failed retry backs off by a factor of 2
{
stepper: &pcsStepRetry{aft: time.Second},
stepFunc: fail,
want: &pcsStepRetry{aft: 2 * time.Second},
},
// failed retry backs off by a factor of 2, up to 1m
{
stepper: &pcsStepRetry{aft: 32 * time.Second},
stepFunc: fail,
want: &pcsStepRetry{aft: 60 * time.Second},
},
}
for i, tt := range tests {
got := tt.stepper.step(tt.stepFunc)
if !reflect.DeepEqual(tt.want, got) {
t.Errorf("case %d: want=%#v got=%#v", i, tt.want, got)
}
}
}
func TestProviderConfigSupportsGrantType(t *testing.T) {
tests := []struct {
types []string
typ string
want bool
}{
// explicitly supported
{
types: []string{"foo_type"},
typ: "foo_type",
want: true,
},
// explicitly unsupported
{
types: []string{"bar_type"},
typ: "foo_type",
want: false,
},
// default type explicitly unsupported
{
types: []string{oauth2.GrantTypeImplicit},
typ: oauth2.GrantTypeAuthCode,
want: false,
},
// type not found in default set
{
types: []string{},
typ: "foo_type",
want: false,
},
// type found in default set
{
types: []string{},
typ: oauth2.GrantTypeAuthCode,
want: true,
},
}
for i, tt := range tests {
cfg := ProviderConfig{
GrantTypesSupported: tt.types,
}
got := cfg.SupportsGrantType(tt.typ)
if tt.want != got {
t.Errorf("case %d: assert %v supports %v: want=%t got=%t", i, tt.types, tt.typ, tt.want, got)
}
}
}
type fakeClient struct {
resp *http.Response
}
func (f *fakeClient) Do(req *http.Request) (*http.Response, error) {
return f.resp, nil
}
func TestWaitForProviderConfigImmediateSuccess(t *testing.T) {
cfg := newValidProviderConfig()
b, err := json.Marshal(&cfg)
if err != nil {
t.Fatalf("Failed marshaling provider config")
}
resp := http.Response{Body: ioutil.NopCloser(bytes.NewBuffer(b))}
hc := &fakeClient{&resp}
fc := clockwork.NewFakeClock()
reschan := make(chan ProviderConfig)
go func() {
reschan <- waitForProviderConfig(hc, cfg.Issuer.String(), fc)
}()
var got ProviderConfig
select {
case got = <-reschan:
case <-time.After(time.Second):
t.Fatalf("Did not receive result within 1s")
}
if !reflect.DeepEqual(cfg, got) {
t.Fatalf("Received incorrect provider config: want=%#v got=%#v", cfg, got)
}
}

View file

@ -1,88 +0,0 @@
package oidc
import (
"fmt"
"net/http"
"sync"
phttp "github.com/coreos/go-oidc/http"
"github.com/coreos/go-oidc/jose"
)
type TokenRefresher interface {
// Verify checks if the provided token is currently valid or not.
Verify(jose.JWT) error
// Refresh attempts to authenticate and retrieve a new token.
Refresh() (jose.JWT, error)
}
type ClientCredsTokenRefresher struct {
Issuer string
OIDCClient *Client
}
func (c *ClientCredsTokenRefresher) Verify(jwt jose.JWT) (err error) {
_, err = VerifyClientClaims(jwt, c.Issuer)
return
}
func (c *ClientCredsTokenRefresher) Refresh() (jwt jose.JWT, err error) {
if err = c.OIDCClient.Healthy(); err != nil {
err = fmt.Errorf("unable to authenticate, unhealthy OIDC client: %v", err)
return
}
jwt, err = c.OIDCClient.ClientCredsToken([]string{"openid"})
if err != nil {
err = fmt.Errorf("unable to verify auth code with issuer: %v", err)
return
}
return
}
type AuthenticatedTransport struct {
TokenRefresher
http.RoundTripper
mu sync.Mutex
jwt jose.JWT
}
func (t *AuthenticatedTransport) verifiedJWT() (jose.JWT, error) {
t.mu.Lock()
defer t.mu.Unlock()
if t.TokenRefresher.Verify(t.jwt) == nil {
return t.jwt, nil
}
jwt, err := t.TokenRefresher.Refresh()
if err != nil {
return jose.JWT{}, fmt.Errorf("unable to acquire valid JWT: %v", err)
}
t.jwt = jwt
return t.jwt, nil
}
// SetJWT sets the JWT held by the Transport.
// This is useful for cases in which you want to set an initial JWT.
func (t *AuthenticatedTransport) SetJWT(jwt jose.JWT) {
t.mu.Lock()
defer t.mu.Unlock()
t.jwt = jwt
}
func (t *AuthenticatedTransport) RoundTrip(r *http.Request) (*http.Response, error) {
jwt, err := t.verifiedJWT()
if err != nil {
return nil, err
}
req := phttp.CopyRequest(r)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", jwt.Encode()))
return t.RoundTripper.RoundTrip(req)
}

View file

@ -1,176 +0,0 @@
package oidc
import (
"errors"
"net/http"
"reflect"
"testing"
"github.com/coreos/go-oidc/jose"
)
type staticTokenRefresher struct {
verify func(jose.JWT) error
refresh func() (jose.JWT, error)
}
func (s *staticTokenRefresher) Verify(jwt jose.JWT) error {
return s.verify(jwt)
}
func (s *staticTokenRefresher) Refresh() (jose.JWT, error) {
return s.refresh()
}
func TestAuthenticatedTransportVerifiedJWT(t *testing.T) {
tests := []struct {
refresher TokenRefresher
startJWT jose.JWT
wantJWT jose.JWT
wantError error
}{
// verification succeeds, so refresh is not called
{
refresher: &staticTokenRefresher{
verify: func(jose.JWT) error { return nil },
refresh: func() (jose.JWT, error) { return jose.JWT{RawPayload: "2"}, nil },
},
startJWT: jose.JWT{RawPayload: "1"},
wantJWT: jose.JWT{RawPayload: "1"},
},
// verification fails, refresh succeeds so cached JWT changes
{
refresher: &staticTokenRefresher{
verify: func(jose.JWT) error { return errors.New("fail!") },
refresh: func() (jose.JWT, error) { return jose.JWT{RawPayload: "2"}, nil },
},
startJWT: jose.JWT{RawPayload: "1"},
wantJWT: jose.JWT{RawPayload: "2"},
},
// verification succeeds, so failing refresh isn't attempted
{
refresher: &staticTokenRefresher{
verify: func(jose.JWT) error { return nil },
refresh: func() (jose.JWT, error) { return jose.JWT{}, errors.New("fail!") },
},
startJWT: jose.JWT{RawPayload: "1"},
wantJWT: jose.JWT{RawPayload: "1"},
},
// verification fails, but refresh fails, too
{
refresher: &staticTokenRefresher{
verify: func(jose.JWT) error { return errors.New("fail!") },
refresh: func() (jose.JWT, error) { return jose.JWT{}, errors.New("fail!") },
},
startJWT: jose.JWT{RawPayload: "1"},
wantJWT: jose.JWT{},
wantError: errors.New("unable to acquire valid JWT: fail!"),
},
}
for i, tt := range tests {
at := &AuthenticatedTransport{
TokenRefresher: tt.refresher,
}
at.SetJWT(tt.startJWT)
gotJWT, err := at.verifiedJWT()
if !reflect.DeepEqual(tt.wantError, err) {
t.Errorf("#%d: unexpected error: want=%#v got=%#v", i, tt.wantError, err)
}
if !reflect.DeepEqual(tt.wantJWT, gotJWT) {
t.Errorf("#%d: incorrect JWT returned from verifiedJWT: want=%#v got=%#v", i, tt.wantJWT, gotJWT)
}
}
}
func TestAuthenticatedTransportJWTCaching(t *testing.T) {
at := &AuthenticatedTransport{
TokenRefresher: &staticTokenRefresher{
verify: func(jose.JWT) error { return errors.New("fail!") },
refresh: func() (jose.JWT, error) { return jose.JWT{RawPayload: "2"}, nil },
},
jwt: jose.JWT{RawPayload: "1"},
}
wantJWT := jose.JWT{RawPayload: "2"}
gotJWT, err := at.verifiedJWT()
if err != nil {
t.Fatalf("got non-nil error: %#v", err)
}
if !reflect.DeepEqual(wantJWT, gotJWT) {
t.Fatalf("incorrect JWT returned from verifiedJWT: want=%#v got=%#v", wantJWT, gotJWT)
}
at.TokenRefresher = &staticTokenRefresher{
verify: func(jose.JWT) error { return nil },
refresh: func() (jose.JWT, error) { return jose.JWT{RawPayload: "3"}, nil },
}
// the previous JWT should still be cached on the AuthenticatedTransport since
// it is still valid, even though there's a new token ready to refresh
gotJWT, err = at.verifiedJWT()
if err != nil {
t.Fatalf("got non-nil error: %#v", err)
}
if !reflect.DeepEqual(wantJWT, gotJWT) {
t.Fatalf("incorrect JWT returned from verifiedJWT: want=%#v got=%#v", wantJWT, gotJWT)
}
}
type fakeRoundTripper struct {
Request *http.Request
resp *http.Response
}
func (r *fakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
r.Request = req
return r.resp, nil
}
func TestAuthenticatedTransportRoundTrip(t *testing.T) {
rr := &fakeRoundTripper{nil, &http.Response{StatusCode: http.StatusOK}}
at := &AuthenticatedTransport{
TokenRefresher: &staticTokenRefresher{
verify: func(jose.JWT) error { return nil },
},
RoundTripper: rr,
jwt: jose.JWT{RawPayload: "1"},
}
req := http.Request{}
_, err := at.RoundTrip(&req)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !reflect.DeepEqual(req, http.Request{}) {
t.Errorf("http.Request object was modified")
}
want := []string{"Bearer .1."}
got := rr.Request.Header["Authorization"]
if !reflect.DeepEqual(want, got) {
t.Errorf("incorrect Authorization header: want=%#v got=%#v", want, got)
}
}
func TestAuthenticatedTransportRoundTripRefreshFail(t *testing.T) {
rr := &fakeRoundTripper{nil, &http.Response{StatusCode: http.StatusOK}}
at := &AuthenticatedTransport{
TokenRefresher: &staticTokenRefresher{
verify: func(jose.JWT) error { return errors.New("fail!") },
refresh: func() (jose.JWT, error) { return jose.JWT{}, errors.New("fail!") },
},
RoundTripper: rr,
jwt: jose.JWT{RawPayload: "1"},
}
_, err := at.RoundTrip(&http.Request{})
if err == nil {
t.Errorf("expected non-nil error")
}
}

View file

@ -1,109 +0,0 @@
package oidc
import (
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/coreos/go-oidc/jose"
)
// RequestTokenExtractor funcs extract a raw encoded token from a request.
type RequestTokenExtractor func(r *http.Request) (string, error)
// ExtractBearerToken is a RequestTokenExtractor which extracts a bearer token from a request's
// Authorization header.
func ExtractBearerToken(r *http.Request) (string, error) {
ah := r.Header.Get("Authorization")
if ah == "" {
return "", errors.New("missing Authorization header")
}
if len(ah) <= 6 || strings.ToUpper(ah[0:6]) != "BEARER" {
return "", errors.New("should be a bearer token")
}
val := ah[7:]
if len(val) == 0 {
return "", errors.New("bearer token is empty")
}
return val, nil
}
// CookieTokenExtractor returns a RequestTokenExtractor which extracts a token from the named cookie in a request.
func CookieTokenExtractor(cookieName string) RequestTokenExtractor {
return func(r *http.Request) (string, error) {
ck, err := r.Cookie(cookieName)
if err != nil {
return "", fmt.Errorf("token cookie not found in request: %v", err)
}
if ck.Value == "" {
return "", errors.New("token cookie found but is empty")
}
return ck.Value, nil
}
}
func NewClaims(iss, sub string, aud interface{}, iat, exp time.Time) jose.Claims {
return jose.Claims{
// required
"iss": iss,
"sub": sub,
"aud": aud,
"iat": iat.Unix(),
"exp": exp.Unix(),
}
}
func GenClientID(hostport string) (string, error) {
b, err := randBytes(32)
if err != nil {
return "", err
}
var host string
if strings.Contains(hostport, ":") {
host, _, err = net.SplitHostPort(hostport)
if err != nil {
return "", err
}
} else {
host = hostport
}
return fmt.Sprintf("%s@%s", base64.URLEncoding.EncodeToString(b), host), nil
}
func randBytes(n int) ([]byte, error) {
b := make([]byte, n)
got, err := rand.Read(b)
if err != nil {
return nil, err
} else if n != got {
return nil, errors.New("unable to generate enough random data")
}
return b, nil
}
// urlEqual checks two urls for equality using only the host and path portions.
func urlEqual(url1, url2 string) bool {
u1, err := url.Parse(url1)
if err != nil {
return false
}
u2, err := url.Parse(url2)
if err != nil {
return false
}
return strings.ToLower(u1.Host+u1.Path) == strings.ToLower(u2.Host+u2.Path)
}

View file

@ -1,110 +0,0 @@
package oidc
import (
"fmt"
"net/http"
"reflect"
"testing"
"time"
"github.com/coreos/go-oidc/jose"
)
func TestCookieTokenExtractorInvalid(t *testing.T) {
ckName := "tokenCookie"
tests := []*http.Cookie{
&http.Cookie{},
&http.Cookie{Name: ckName},
&http.Cookie{Name: ckName, Value: ""},
}
for i, tt := range tests {
r, _ := http.NewRequest("", "", nil)
r.AddCookie(tt)
_, err := CookieTokenExtractor(ckName)(r)
if err == nil {
t.Errorf("case %d: want: error for invalid cookie token, got: no error.", i)
}
}
}
func TestCookieTokenExtractorValid(t *testing.T) {
validToken := "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
ckName := "tokenCookie"
tests := []*http.Cookie{
&http.Cookie{Name: ckName, Value: "some non-empty value"},
&http.Cookie{Name: ckName, Value: validToken},
}
for i, tt := range tests {
r, _ := http.NewRequest("", "", nil)
r.AddCookie(tt)
_, err := CookieTokenExtractor(ckName)(r)
if err != nil {
t.Errorf("case %d: want: valid cookie with no error, got: %v", i, err)
}
}
}
func TestExtractBearerTokenInvalid(t *testing.T) {
tests := []string{"", "x", "Bearer", "xxxxxxx", "Bearer "}
for i, tt := range tests {
r, _ := http.NewRequest("", "", nil)
r.Header.Add("Authorization", tt)
_, err := ExtractBearerToken(r)
if err == nil {
t.Errorf("case %d: want: invalid Authorization header, got: valid Authorization header.", i)
}
}
}
func TestExtractBearerTokenValid(t *testing.T) {
validToken := "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
tests := []string{
fmt.Sprintf("Bearer %s", validToken),
}
for i, tt := range tests {
r, _ := http.NewRequest("", "", nil)
r.Header.Add("Authorization", tt)
_, err := ExtractBearerToken(r)
if err != nil {
t.Errorf("case %d: want: valid Authorization header, got: invalid Authorization header: %v.", i, err)
}
}
}
func TestNewClaims(t *testing.T) {
issAt := time.Date(2, time.January, 1, 0, 0, 0, 0, time.UTC)
expAt := time.Date(2, time.January, 1, 1, 0, 0, 0, time.UTC)
want := jose.Claims{
"iss": "https://example.com",
"sub": "user-123",
"aud": "client-abc",
"iat": issAt.Unix(),
"exp": expAt.Unix(),
}
got := NewClaims("https://example.com", "user-123", "client-abc", issAt, expAt)
if !reflect.DeepEqual(want, got) {
t.Fatalf("want=%#v got=%#v", want, got)
}
want2 := jose.Claims{
"iss": "https://example.com",
"sub": "user-123",
"aud": []string{"client-abc", "client-def"},
"iat": issAt.Unix(),
"exp": expAt.Unix(),
}
got2 := NewClaims("https://example.com", "user-123", []string{"client-abc", "client-def"}, issAt, expAt)
if !reflect.DeepEqual(want2, got2) {
t.Fatalf("want=%#v got=%#v", want2, got2)
}
}

View file

@ -1,190 +0,0 @@
package oidc
import (
"errors"
"fmt"
"time"
"github.com/jonboulle/clockwork"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/go-oidc/key"
)
func VerifySignature(jwt jose.JWT, keys []key.PublicKey) (bool, error) {
jwtBytes := []byte(jwt.Data())
for _, k := range keys {
v, err := k.Verifier()
if err != nil {
return false, err
}
if v.Verify(jwt.Signature, jwtBytes) == nil {
return true, nil
}
}
return false, nil
}
// containsString returns true if the given string(needle) is found
// in the string array(haystack).
func containsString(needle string, haystack []string) bool {
for _, v := range haystack {
if v == needle {
return true
}
}
return false
}
// Verify claims in accordance with OIDC spec
// http://openid.net/specs/openid-connect-basic-1_0.html#IDTokenValidation
func VerifyClaims(jwt jose.JWT, issuer, clientID string) error {
now := time.Now().UTC()
claims, err := jwt.Claims()
if err != nil {
return err
}
ident, err := IdentityFromClaims(claims)
if err != nil {
return err
}
if ident.ExpiresAt.Before(now) {
return errors.New("token is expired")
}
// iss REQUIRED. Issuer Identifier for the Issuer of the response.
// The iss value is a case sensitive URL using the https scheme that contains scheme,
// host, and optionally, port number and path components and no query or fragment components.
if iss, exists := claims["iss"].(string); exists {
if !urlEqual(iss, issuer) {
return fmt.Errorf("invalid claim value: 'iss'. expected=%s, found=%s.", issuer, iss)
}
} else {
return errors.New("missing claim: 'iss'")
}
// iat REQUIRED. Time at which the JWT was issued.
// Its value is a JSON number representing the number of seconds from 1970-01-01T0:0:0Z
// as measured in UTC until the date/time.
if _, exists := claims["iat"].(float64); !exists {
return errors.New("missing claim: 'iat'")
}
// aud REQUIRED. Audience(s) that this ID Token is intended for.
// It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value.
// It MAY also contain identifiers for other audiences. In the general case, the aud
// value is an array of case sensitive strings. In the common special case when there
// is one audience, the aud value MAY be a single case sensitive string.
if aud, ok, err := claims.StringClaim("aud"); err == nil && ok {
if aud != clientID {
return fmt.Errorf("invalid claims, 'aud' claim and 'client_id' do not match, aud=%s, client_id=%s", aud, clientID)
}
} else if aud, ok, err := claims.StringsClaim("aud"); err == nil && ok {
if !containsString(clientID, aud) {
return fmt.Errorf("invalid claims, cannot find 'client_id' in 'aud' claim, aud=%v, client_id=%s", aud, clientID)
}
} else {
return errors.New("invalid claim value: 'aud' is required, and should be either string or string array")
}
return nil
}
// VerifyClientClaims verifies all the required claims are valid for a "client credentials" JWT.
// Returns the client ID if valid, or an error if invalid.
func VerifyClientClaims(jwt jose.JWT, issuer string) (string, error) {
claims, err := jwt.Claims()
if err != nil {
return "", fmt.Errorf("failed to parse JWT claims: %v", err)
}
iss, ok, err := claims.StringClaim("iss")
if err != nil {
return "", fmt.Errorf("failed to parse 'iss' claim: %v", err)
} else if !ok {
return "", errors.New("missing required 'iss' claim")
} else if !urlEqual(iss, issuer) {
return "", fmt.Errorf("'iss' claim does not match expected issuer, iss=%s", iss)
}
sub, ok, err := claims.StringClaim("sub")
if err != nil {
return "", fmt.Errorf("failed to parse 'sub' claim: %v", err)
} else if !ok {
return "", errors.New("missing required 'sub' claim")
}
if aud, ok, err := claims.StringClaim("aud"); err == nil && ok {
if aud != sub {
return "", fmt.Errorf("invalid claims, 'aud' claim and 'sub' claim do not match, aud=%s, sub=%s", aud, sub)
}
} else if aud, ok, err := claims.StringsClaim("aud"); err == nil && ok {
if !containsString(sub, aud) {
return "", fmt.Errorf("invalid claims, cannot find 'sud' in 'aud' claim, aud=%v, sub=%s", aud, sub)
}
} else {
return "", errors.New("invalid claim value: 'aud' is required, and should be either string or string array")
}
now := time.Now().UTC()
exp, ok, err := claims.TimeClaim("exp")
if err != nil {
return "", fmt.Errorf("failed to parse 'exp' claim: %v", err)
} else if !ok {
return "", errors.New("missing required 'exp' claim")
} else if exp.Before(now) {
return "", fmt.Errorf("token already expired at: %v", exp)
}
return sub, nil
}
type JWTVerifier struct {
issuer string
clientID string
syncFunc func() error
keysFunc func() []key.PublicKey
clock clockwork.Clock
}
func NewJWTVerifier(issuer, clientID string, syncFunc func() error, keysFunc func() []key.PublicKey) JWTVerifier {
return JWTVerifier{
issuer: issuer,
clientID: clientID,
syncFunc: syncFunc,
keysFunc: keysFunc,
clock: clockwork.NewRealClock(),
}
}
func (v *JWTVerifier) Verify(jwt jose.JWT) error {
// Verify claims before verifying the signature. This is an optimization to throw out
// tokens we know are invalid without undergoing an expensive signature check and
// possibly a re-sync event.
if err := VerifyClaims(jwt, v.issuer, v.clientID); err != nil {
return fmt.Errorf("oidc: JWT claims invalid: %v", err)
}
ok, err := VerifySignature(jwt, v.keysFunc())
if err != nil {
return fmt.Errorf("oidc: JWT signature verification failed: %v", err)
} else if ok {
return nil
}
if err = v.syncFunc(); err != nil {
return fmt.Errorf("oidc: failed syncing KeySet: %v", err)
}
ok, err = VerifySignature(jwt, v.keysFunc())
if err != nil {
return fmt.Errorf("oidc: JWT signature verification failed: %v", err)
} else if !ok {
return errors.New("oidc: unable to verify JWT signature: no matching keys")
}
return nil
}

View file

@ -1,380 +0,0 @@
package oidc
import (
"testing"
"time"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/go-oidc/key"
)
func TestVerifyClientClaims(t *testing.T) {
validIss := "https://example.com"
validClientID := "valid-client"
now := time.Now()
tomorrow := now.Add(24 * time.Hour)
header := jose.JOSEHeader{
jose.HeaderKeyAlgorithm: "test-alg",
jose.HeaderKeyID: "1",
}
tests := []struct {
claims jose.Claims
ok bool
}{
// valid token
{
claims: jose.Claims{
"iss": validIss,
"sub": validClientID,
"aud": validClientID,
"iat": float64(now.Unix()),
"exp": float64(tomorrow.Unix()),
},
ok: true,
},
// valid token, ('aud' claim is []string)
{
claims: jose.Claims{
"iss": validIss,
"sub": validClientID,
"aud": []string{"foo", validClientID},
"iat": float64(now.Unix()),
"exp": float64(tomorrow.Unix()),
},
ok: true,
},
// valid token, ('aud' claim is []interface{})
{
claims: jose.Claims{
"iss": validIss,
"sub": validClientID,
"aud": []interface{}{"foo", validClientID},
"iat": float64(now.Unix()),
"exp": float64(tomorrow.Unix()),
},
ok: true,
},
// missing 'iss' claim
{
claims: jose.Claims{
"sub": validClientID,
"aud": validClientID,
"iat": float64(now.Unix()),
"exp": float64(tomorrow.Unix()),
},
ok: false,
},
// invalid 'iss' claim
{
claims: jose.Claims{
"iss": "INVALID",
"sub": validClientID,
"aud": validClientID,
"iat": float64(now.Unix()),
"exp": float64(tomorrow.Unix()),
},
ok: false,
},
// missing 'sub' claim
{
claims: jose.Claims{
"iss": validIss,
"aud": validClientID,
"iat": float64(now.Unix()),
"exp": float64(tomorrow.Unix()),
},
ok: false,
},
// invalid 'sub' claim
{
claims: jose.Claims{
"iss": validIss,
"sub": "INVALID",
"aud": validClientID,
"iat": float64(now.Unix()),
"exp": float64(tomorrow.Unix()),
},
ok: false,
},
// missing 'aud' claim
{
claims: jose.Claims{
"iss": validIss,
"sub": validClientID,
"iat": float64(now.Unix()),
"exp": float64(tomorrow.Unix()),
},
ok: false,
},
// invalid 'aud' claim
{
claims: jose.Claims{
"iss": validIss,
"sub": validClientID,
"aud": "INVALID",
"iat": float64(now.Unix()),
"exp": float64(tomorrow.Unix()),
},
ok: false,
},
// invalid 'aud' claim
{
claims: jose.Claims{
"iss": validIss,
"sub": validClientID,
"aud": []string{"INVALID1", "INVALID2"},
"iat": float64(now.Unix()),
"exp": float64(tomorrow.Unix()),
},
ok: false,
},
// invalid 'aud' type
{
claims: jose.Claims{
"iss": validIss,
"sub": validClientID,
"aud": struct{}{},
"iat": float64(now.Unix()),
"exp": float64(tomorrow.Unix()),
},
ok: false,
},
// expired
{
claims: jose.Claims{
"iss": validIss,
"sub": validClientID,
"aud": validClientID,
"iat": float64(now.Unix()),
"exp": float64(now.Unix()),
},
ok: false,
},
}
for i, tt := range tests {
jwt, err := jose.NewJWT(header, tt.claims)
if err != nil {
t.Fatalf("case %d: Failed to generate JWT, error=%v", i, err)
}
got, err := VerifyClientClaims(jwt, validIss)
if tt.ok {
if err != nil {
t.Errorf("case %d: unexpected error, err=%v", i, err)
}
if got != validClientID {
t.Errorf("case %d: incorrect client ID, want=%s, got=%s", i, validClientID, got)
}
} else if err == nil {
t.Errorf("case %d: expected error but err is nil", i)
}
}
}
func TestJWTVerifier(t *testing.T) {
iss := "http://example.com"
now := time.Now()
future12 := now.Add(12 * time.Hour)
past36 := now.Add(-36 * time.Hour)
past12 := now.Add(-12 * time.Hour)
priv1, err := key.GeneratePrivateKey()
if err != nil {
t.Fatalf("failed to generate private key, error=%v", err)
}
pk1 := *key.NewPublicKey(priv1.JWK())
priv2, err := key.GeneratePrivateKey()
if err != nil {
t.Fatalf("failed to generate private key, error=%v", err)
}
pk2 := *key.NewPublicKey(priv2.JWK())
newJWT := func(issuer, subject string, aud interface{}, issuedAt, exp time.Time, signer jose.Signer) jose.JWT {
jwt, err := jose.NewSignedJWT(NewClaims(issuer, subject, aud, issuedAt, exp), signer)
if err != nil {
t.Fatal(err)
}
return *jwt
}
tests := []struct {
name string
verifier JWTVerifier
jwt jose.JWT
wantErr bool
}{
{
name: "JWT signed with available key",
verifier: JWTVerifier{
issuer: "example.com",
clientID: "XXX",
syncFunc: func() error { return nil },
keysFunc: func() []key.PublicKey {
return []key.PublicKey{pk1}
},
},
jwt: newJWT(iss, "XXX", "XXX", past12, future12, priv1.Signer()),
wantErr: false,
},
{
name: "JWT signed with available key, with bad claims",
verifier: JWTVerifier{
issuer: "example.com",
clientID: "XXX",
syncFunc: func() error { return nil },
keysFunc: func() []key.PublicKey {
return []key.PublicKey{pk1}
},
},
jwt: newJWT(iss, "XXX", "YYY", past12, future12, priv1.Signer()),
wantErr: true,
},
{
name: "JWT signed with available key",
verifier: JWTVerifier{
issuer: "example.com",
clientID: "XXX",
syncFunc: func() error { return nil },
keysFunc: func() []key.PublicKey {
return []key.PublicKey{pk1}
},
},
jwt: newJWT(iss, "XXX", []string{"YYY", "ZZZ"}, past12, future12, priv1.Signer()),
wantErr: true,
},
{
name: "expired JWT signed with available key",
verifier: JWTVerifier{
issuer: "example.com",
clientID: "XXX",
syncFunc: func() error { return nil },
keysFunc: func() []key.PublicKey {
return []key.PublicKey{pk1}
},
},
jwt: newJWT(iss, "XXX", "XXX", past36, past12, priv1.Signer()),
wantErr: true,
},
{
name: "JWT signed with unrecognized key, verifiable after sync",
verifier: JWTVerifier{
issuer: "example.com",
clientID: "XXX",
syncFunc: func() error { return nil },
keysFunc: func() func() []key.PublicKey {
var i int
return func() []key.PublicKey {
defer func() { i++ }()
return [][]key.PublicKey{
[]key.PublicKey{pk1},
[]key.PublicKey{pk2},
}[i]
}
}(),
},
jwt: newJWT(iss, "XXX", "XXX", past36, future12, priv2.Signer()),
wantErr: false,
},
{
name: "JWT signed with unrecognized key, not verifiable after sync",
verifier: JWTVerifier{
issuer: "example.com",
clientID: "XXX",
syncFunc: func() error { return nil },
keysFunc: func() []key.PublicKey {
return []key.PublicKey{pk1}
},
},
jwt: newJWT(iss, "XXX", "XXX", past12, future12, priv2.Signer()),
wantErr: true,
},
{
name: "verifier gets no keys from keysFunc, still not verifiable after sync",
verifier: JWTVerifier{
issuer: "example.com",
clientID: "XXX",
syncFunc: func() error { return nil },
keysFunc: func() []key.PublicKey {
return []key.PublicKey{}
},
},
jwt: newJWT(iss, "XXX", "XXX", past12, future12, priv1.Signer()),
wantErr: true,
},
{
name: "verifier gets no keys from keysFunc, verifiable after sync",
verifier: JWTVerifier{
issuer: "example.com",
clientID: "XXX",
syncFunc: func() error { return nil },
keysFunc: func() func() []key.PublicKey {
var i int
return func() []key.PublicKey {
defer func() { i++ }()
return [][]key.PublicKey{
[]key.PublicKey{},
[]key.PublicKey{pk2},
}[i]
}
}(),
},
jwt: newJWT(iss, "XXX", "XXX", past12, future12, priv2.Signer()),
wantErr: false,
},
{
name: "JWT signed with available key, 'aud' is a string array",
verifier: JWTVerifier{
issuer: "example.com",
clientID: "XXX",
syncFunc: func() error { return nil },
keysFunc: func() []key.PublicKey {
return []key.PublicKey{pk1}
},
},
jwt: newJWT(iss, "XXX", []string{"ZZZ", "XXX"}, past12, future12, priv1.Signer()),
wantErr: false,
},
{
name: "invalid issuer claim shouldn't trigger sync",
verifier: JWTVerifier{
issuer: "example.com",
clientID: "XXX",
syncFunc: func() error {
t.Errorf("invalid issuer claim shouldn't trigger a sync")
return nil
},
keysFunc: func() func() []key.PublicKey {
var i int
return func() []key.PublicKey {
defer func() { i++ }()
return [][]key.PublicKey{
[]key.PublicKey{},
[]key.PublicKey{pk2},
}[i]
}
}(),
},
jwt: newJWT("invalid-issuer", "XXX", []string{"ZZZ", "XXX"}, past12, future12, priv2.Signer()),
wantErr: true,
},
}
for _, tt := range tests {
err := tt.verifier.Verify(tt.jwt)
if tt.wantErr && (err == nil) {
t.Errorf("case %q: wanted non-nil error", tt.name)
} else if !tt.wantErr && (err != nil) {
t.Errorf("case %q: wanted nil error, got %v", tt.name, err)
}
}
}

View file

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

View file

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

View file

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

View file

@ -1,40 +0,0 @@
# 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=. $<

View file

@ -1,54 +0,0 @@
# 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

View file

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

View file

@ -1,832 +0,0 @@
// 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())
}

View file

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

View file

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

View file

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

View file

@ -1,57 +0,0 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2015 The Go Authors. All rights reserved.
// https://github.com/golang/protobuf
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package jsonpb;
message Simple3 {
double dub = 1;
}
enum Numeral {
UNKNOWN = 0;
ARABIC = 1;
ROMAN = 2;
}
message Mappy {
map<int64, int32> nummy = 1;
map<string, string> strry = 2;
map<int32, Simple3> objjy = 3;
map<int64, string> buggy = 4;
map<bool, bool> booly = 5;
map<string, Numeral> enumy = 6;
map<int32, bool> s32booly = 7;
map<int64, bool> s64booly = 8;
map<uint32, bool> u32booly = 9;
map<uint64, bool> u64booly = 10;
}

View file

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

View file

@ -1,134 +0,0 @@
// 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;
}

View file

@ -1,199 +0,0 @@
// Code generated by protoc-gen-go.
// source: proto3_proto/proto3.proto
// DO NOT EDIT!
/*
Package proto3_proto is a generated protocol buffer package.
It is generated from these files:
proto3_proto/proto3.proto
It has these top-level messages:
Message
Nested
MessageWithMap
*/
package proto3_proto
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_protobuf "github.com/golang/protobuf/ptypes/any"
import testdata "github.com/golang/protobuf/proto/testdata"
// 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.
const _ = proto.ProtoPackageIsVersion1
type Message_Humour int32
const (
Message_UNKNOWN Message_Humour = 0
Message_PUNS Message_Humour = 1
Message_SLAPSTICK Message_Humour = 2
Message_BILL_BAILEY Message_Humour = 3
)
var Message_Humour_name = map[int32]string{
0: "UNKNOWN",
1: "PUNS",
2: "SLAPSTICK",
3: "BILL_BAILEY",
}
var Message_Humour_value = map[string]int32{
"UNKNOWN": 0,
"PUNS": 1,
"SLAPSTICK": 2,
"BILL_BAILEY": 3,
}
func (x Message_Humour) String() string {
return proto.EnumName(Message_Humour_name, int32(x))
}
func (Message_Humour) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} }
type Message struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Hilarity Message_Humour `protobuf:"varint,2,opt,name=hilarity,enum=proto3_proto.Message_Humour" json:"hilarity,omitempty"`
HeightInCm uint32 `protobuf:"varint,3,opt,name=height_in_cm,json=heightInCm" json:"height_in_cm,omitempty"`
Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"`
ResultCount int64 `protobuf:"varint,7,opt,name=result_count,json=resultCount" json:"result_count,omitempty"`
TrueScotsman bool `protobuf:"varint,8,opt,name=true_scotsman,json=trueScotsman" json:"true_scotsman,omitempty"`
Score float32 `protobuf:"fixed32,9,opt,name=score" json:"score,omitempty"`
Key []uint64 `protobuf:"varint,5,rep,name=key" json:"key,omitempty"`
Nested *Nested `protobuf:"bytes,6,opt,name=nested" json:"nested,omitempty"`
RFunny []Message_Humour `protobuf:"varint,16,rep,name=r_funny,json=rFunny,enum=proto3_proto.Message_Humour" json:"r_funny,omitempty"`
Terrain map[string]*Nested `protobuf:"bytes,10,rep,name=terrain" json:"terrain,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Proto2Field *testdata.SubDefaults `protobuf:"bytes,11,opt,name=proto2_field,json=proto2Field" json:"proto2_field,omitempty"`
Proto2Value map[string]*testdata.SubDefaults `protobuf:"bytes,13,rep,name=proto2_value,json=proto2Value" json:"proto2_value,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Anything *google_protobuf.Any `protobuf:"bytes,14,opt,name=anything" json:"anything,omitempty"`
ManyThings []*google_protobuf.Any `protobuf:"bytes,15,rep,name=many_things,json=manyThings" json:"many_things,omitempty"`
}
func (m *Message) Reset() { *m = Message{} }
func (m *Message) String() string { return proto.CompactTextString(m) }
func (*Message) ProtoMessage() {}
func (*Message) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *Message) GetNested() *Nested {
if m != nil {
return m.Nested
}
return nil
}
func (m *Message) GetTerrain() map[string]*Nested {
if m != nil {
return m.Terrain
}
return nil
}
func (m *Message) GetProto2Field() *testdata.SubDefaults {
if m != nil {
return m.Proto2Field
}
return nil
}
func (m *Message) GetProto2Value() map[string]*testdata.SubDefaults {
if m != nil {
return m.Proto2Value
}
return nil
}
func (m *Message) GetAnything() *google_protobuf.Any {
if m != nil {
return m.Anything
}
return nil
}
func (m *Message) GetManyThings() []*google_protobuf.Any {
if m != nil {
return m.ManyThings
}
return nil
}
type Nested struct {
Bunny string `protobuf:"bytes,1,opt,name=bunny" json:"bunny,omitempty"`
Cute bool `protobuf:"varint,2,opt,name=cute" json:"cute,omitempty"`
}
func (m *Nested) Reset() { *m = Nested{} }
func (m *Nested) String() string { return proto.CompactTextString(m) }
func (*Nested) ProtoMessage() {}
func (*Nested) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
type MessageWithMap struct {
ByteMapping map[bool][]byte `protobuf:"bytes,1,rep,name=byte_mapping,json=byteMapping" json:"byte_mapping,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (m *MessageWithMap) Reset() { *m = MessageWithMap{} }
func (m *MessageWithMap) String() string { return proto.CompactTextString(m) }
func (*MessageWithMap) ProtoMessage() {}
func (*MessageWithMap) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
func (m *MessageWithMap) GetByteMapping() map[bool][]byte {
if m != nil {
return m.ByteMapping
}
return nil
}
func init() {
proto.RegisterType((*Message)(nil), "proto3_proto.Message")
proto.RegisterType((*Nested)(nil), "proto3_proto.Nested")
proto.RegisterType((*MessageWithMap)(nil), "proto3_proto.MessageWithMap")
proto.RegisterEnum("proto3_proto.Message_Humour", Message_Humour_name, Message_Humour_value)
}
var fileDescriptor0 = []byte{
// 621 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x53, 0x5b, 0x6f, 0xd3, 0x4c,
0x10, 0xfd, 0x72, 0xa9, 0xe3, 0x8e, 0x9d, 0xd6, 0xda, 0xaf, 0x48, 0xdb, 0x88, 0x87, 0x12, 0x24,
0x54, 0x71, 0x71, 0x51, 0x10, 0x52, 0x85, 0x10, 0xa8, 0x2d, 0xad, 0x88, 0x9a, 0x86, 0x68, 0xd3,
0x52, 0xf1, 0x64, 0xad, 0xd3, 0x4d, 0x62, 0x11, 0xaf, 0x23, 0x7b, 0x8d, 0xe4, 0xbf, 0xc3, 0xaf,
0xe4, 0x91, 0xbd, 0x38, 0xad, 0x5b, 0x05, 0x78, 0xf2, 0xee, 0xcc, 0x39, 0x33, 0xb3, 0xe7, 0x8c,
0x61, 0x77, 0x99, 0x26, 0x22, 0x79, 0x13, 0xe8, 0xcf, 0x81, 0xb9, 0xf8, 0xfa, 0x83, 0xdc, 0x6a,
0xaa, 0xb3, 0x3b, 0x4b, 0x92, 0xd9, 0x82, 0x19, 0x48, 0x98, 0x4f, 0x0f, 0x28, 0x2f, 0x0c, 0xb0,
0xf3, 0xbf, 0x60, 0x99, 0xb8, 0xa1, 0x82, 0x1e, 0xa8, 0x83, 0x09, 0x76, 0x7f, 0x59, 0xd0, 0xba,
0x60, 0x59, 0x46, 0x67, 0x0c, 0x21, 0x68, 0x72, 0x1a, 0x33, 0x5c, 0xdb, 0xab, 0xed, 0x6f, 0x12,
0x7d, 0x46, 0x87, 0x60, 0xcf, 0xa3, 0x05, 0x4d, 0x23, 0x51, 0xe0, 0xba, 0x8c, 0x6f, 0xf5, 0x1e,
0xfb, 0xd5, 0x86, 0x7e, 0x49, 0xf6, 0x3f, 0xe7, 0x71, 0x92, 0xa7, 0xe4, 0x16, 0x8d, 0xf6, 0xc0,
0x9d, 0xb3, 0x68, 0x36, 0x17, 0x41, 0xc4, 0x83, 0x49, 0x8c, 0x1b, 0x92, 0xdd, 0x26, 0x60, 0x62,
0x7d, 0x7e, 0x12, 0xab, 0x7e, 0x6a, 0x1c, 0xdc, 0x94, 0x19, 0x97, 0xe8, 0x33, 0x7a, 0x02, 0x6e,
0xca, 0xb2, 0x7c, 0x21, 0x82, 0x49, 0x92, 0x73, 0x81, 0x5b, 0x32, 0xd7, 0x20, 0x8e, 0x89, 0x9d,
0xa8, 0x10, 0x7a, 0x0a, 0x6d, 0x91, 0xe6, 0x2c, 0xc8, 0x26, 0x89, 0xc8, 0x62, 0xca, 0xb1, 0x2d,
0x31, 0x36, 0x71, 0x55, 0x70, 0x5c, 0xc6, 0xd0, 0x0e, 0x6c, 0xc8, 0x7c, 0xca, 0xf0, 0xa6, 0x4c,
0xd6, 0x89, 0xb9, 0x20, 0x0f, 0x1a, 0xdf, 0x59, 0x81, 0x37, 0xf6, 0x1a, 0xfb, 0x4d, 0xa2, 0x8e,
0xe8, 0x25, 0x58, 0x5c, 0xaa, 0xc1, 0x6e, 0xb0, 0x25, 0x81, 0x4e, 0x6f, 0xe7, 0xfe, 0xeb, 0x86,
0x3a, 0x47, 0x4a, 0x0c, 0x7a, 0x0b, 0xad, 0x34, 0x98, 0xe6, 0x9c, 0x17, 0xd8, 0x93, 0x35, 0xfe,
0x25, 0x86, 0x95, 0x9e, 0x29, 0x2c, 0x7a, 0x0f, 0x2d, 0xc1, 0xd2, 0x94, 0x46, 0x1c, 0x83, 0xa4,
0x39, 0xbd, 0xee, 0x7a, 0xda, 0xa5, 0x01, 0x9d, 0x72, 0x91, 0x16, 0x64, 0x45, 0x91, 0x16, 0x18,
0x8b, 0x7b, 0xc1, 0x34, 0x62, 0x8b, 0x1b, 0xec, 0xe8, 0x41, 0x1f, 0xf9, 0x2b, 0x3b, 0xfd, 0x71,
0x1e, 0x7e, 0x62, 0x53, 0x2a, 0x05, 0xca, 0x88, 0x63, 0xa0, 0x67, 0x0a, 0x89, 0xfa, 0xb7, 0xcc,
0x1f, 0x74, 0x91, 0x33, 0xdc, 0xd6, 0xcd, 0x9f, 0xad, 0x6f, 0x3e, 0xd2, 0xc8, 0xaf, 0x0a, 0x68,
0x06, 0x28, 0x4b, 0xe9, 0x08, 0x7a, 0x0d, 0xb6, 0xdc, 0x24, 0x31, 0x8f, 0xf8, 0x0c, 0x6f, 0x95,
0x4a, 0x99, 0x55, 0xf3, 0x57, 0xab, 0xe6, 0x1f, 0xf1, 0x82, 0xdc, 0xa2, 0xa4, 0x56, 0x8e, 0x34,
0xa2, 0x08, 0xf4, 0x2d, 0xc3, 0xdb, 0xba, 0xf7, 0x7a, 0x12, 0x28, 0xe0, 0xa5, 0xc6, 0x75, 0x46,
0xe0, 0x56, 0x65, 0x58, 0x59, 0x66, 0x76, 0x52, 0x5b, 0xf6, 0x1c, 0x36, 0xcc, 0x73, 0xea, 0x7f,
0x71, 0xcc, 0x40, 0xde, 0xd5, 0x0f, 0x6b, 0x9d, 0x2b, 0xf0, 0x1e, 0xbe, 0x6d, 0x4d, 0xd5, 0x17,
0xf7, 0xab, 0xfe, 0x41, 0xde, 0xbb, 0xb2, 0xdd, 0x8f, 0x60, 0x19, 0x9b, 0x91, 0x03, 0xad, 0xab,
0xe1, 0xf9, 0xf0, 0xcb, 0xf5, 0xd0, 0xfb, 0x0f, 0xd9, 0xd0, 0x1c, 0x5d, 0x0d, 0xc7, 0x5e, 0x0d,
0xb5, 0x61, 0x73, 0x3c, 0x38, 0x1a, 0x8d, 0x2f, 0xfb, 0x27, 0xe7, 0x5e, 0x1d, 0x6d, 0x83, 0x73,
0xdc, 0x1f, 0x0c, 0x82, 0xe3, 0xa3, 0xfe, 0xe0, 0xf4, 0x9b, 0xd7, 0xe8, 0xf6, 0xc0, 0x32, 0xc3,
0xaa, 0x65, 0x0d, 0xf5, 0x52, 0x99, 0x79, 0xcc, 0x45, 0xfd, 0x1e, 0x93, 0x5c, 0x98, 0x81, 0x6c,
0xa2, 0xcf, 0xdd, 0x9f, 0x35, 0xd8, 0x2a, 0x0d, 0xbb, 0x8e, 0xc4, 0xfc, 0x82, 0x2e, 0x91, 0x14,
0x2c, 0x2c, 0x04, 0x0b, 0x62, 0xba, 0x5c, 0x2a, 0x77, 0x6a, 0x5a, 0xe8, 0x57, 0x6b, 0x4d, 0x2e,
0x39, 0xfe, 0xb1, 0x24, 0x5c, 0x18, 0x7c, 0xe9, 0x75, 0x78, 0x17, 0xe9, 0x7c, 0x00, 0xef, 0x21,
0xa0, 0x2a, 0x98, 0x6d, 0x04, 0xdb, 0xa9, 0x0a, 0xe6, 0x56, 0x94, 0x09, 0x2d, 0xd3, 0xfa, 0x77,
0x00, 0x00, 0x00, 0xff, 0xff, 0x78, 0xc9, 0x75, 0x36, 0xb5, 0x04, 0x00, 0x00,
}

View file

@ -1,74 +0,0 @@
// 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.
syntax = "proto3";
import "google/protobuf/any.proto";
import "testdata/test.proto";
package proto3_proto;
message Message {
enum Humour {
UNKNOWN = 0;
PUNS = 1;
SLAPSTICK = 2;
BILL_BAILEY = 3;
}
string name = 1;
Humour hilarity = 2;
uint32 height_in_cm = 3;
bytes data = 4;
int64 result_count = 7;
bool true_scotsman = 8;
float score = 9;
repeated uint64 key = 5;
Nested nested = 6;
repeated Humour r_funny = 16;
map<string, Nested> terrain = 10;
testdata.SubDefaults proto2_field = 11;
map<string, testdata.SubDefaults> proto2_value = 13;
google.protobuf.Any anything = 14;
repeated google.protobuf.Any many_things = 15;
}
message Nested {
string bunny = 1;
bool cute = 2;
}
message MessageWithMap {
map<bool, bytes> byte_mapping = 1;
}

View file

@ -1,50 +0,0 @@
# 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.
include ../../Make.protobuf
all: regenerate
regenerate:
rm -f test.pb.go
make test.pb.go
# The following rules are just aids to development. Not needed for typical testing.
diff: regenerate
git diff test.pb.go
restore:
cp test.pb.go.golden test.pb.go
preserve:
cp test.pb.go test.pb.go.golden

View file

@ -1,86 +0,0 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2012 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.
// Verify that the compiler output for test.proto is unchanged.
package testdata
import (
"crypto/sha1"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"
)
// sum returns in string form (for easy comparison) the SHA-1 hash of the named file.
func sum(t *testing.T, name string) string {
data, err := ioutil.ReadFile(name)
if err != nil {
t.Fatal(err)
}
t.Logf("sum(%q): length is %d", name, len(data))
hash := sha1.New()
_, err = hash.Write(data)
if err != nil {
t.Fatal(err)
}
return fmt.Sprintf("% x", hash.Sum(nil))
}
func run(t *testing.T, name string, args ...string) {
cmd := exec.Command(name, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
t.Fatal(err)
}
}
func TestGolden(t *testing.T) {
// Compute the original checksum.
goldenSum := sum(t, "test.pb.go")
// Run the proto compiler.
run(t, "protoc", "--go_out="+os.TempDir(), "test.proto")
newFile := filepath.Join(os.TempDir(), "test.pb.go")
defer os.Remove(newFile)
// Compute the new checksum.
newSum := sum(t, newFile)
// Verify
if newSum != goldenSum {
run(t, "diff", "-u", "test.pb.go", newFile)
t.Fatal("Code generated by protoc-gen-go has changed; update test.pb.go")
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,548 +0,0 @@
// 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.
// A feature-rich test file for the protocol compiler and libraries.
syntax = "proto2";
package testdata;
enum FOO { FOO1 = 1; };
message GoEnum {
required FOO foo = 1;
}
message GoTestField {
required string Label = 1;
required string Type = 2;
}
message GoTest {
// An enum, for completeness.
enum KIND {
VOID = 0;
// Basic types
BOOL = 1;
BYTES = 2;
FINGERPRINT = 3;
FLOAT = 4;
INT = 5;
STRING = 6;
TIME = 7;
// Groupings
TUPLE = 8;
ARRAY = 9;
MAP = 10;
// Table types
TABLE = 11;
// Functions
FUNCTION = 12; // last tag
};
// Some typical parameters
required KIND Kind = 1;
optional string Table = 2;
optional int32 Param = 3;
// Required, repeated and optional foreign fields.
required GoTestField RequiredField = 4;
repeated GoTestField RepeatedField = 5;
optional GoTestField OptionalField = 6;
// Required fields of all basic types
required bool F_Bool_required = 10;
required int32 F_Int32_required = 11;
required int64 F_Int64_required = 12;
required fixed32 F_Fixed32_required = 13;
required fixed64 F_Fixed64_required = 14;
required uint32 F_Uint32_required = 15;
required uint64 F_Uint64_required = 16;
required float F_Float_required = 17;
required double F_Double_required = 18;
required string F_String_required = 19;
required bytes F_Bytes_required = 101;
required sint32 F_Sint32_required = 102;
required sint64 F_Sint64_required = 103;
// Repeated fields of all basic types
repeated bool F_Bool_repeated = 20;
repeated int32 F_Int32_repeated = 21;
repeated int64 F_Int64_repeated = 22;
repeated fixed32 F_Fixed32_repeated = 23;
repeated fixed64 F_Fixed64_repeated = 24;
repeated uint32 F_Uint32_repeated = 25;
repeated uint64 F_Uint64_repeated = 26;
repeated float F_Float_repeated = 27;
repeated double F_Double_repeated = 28;
repeated string F_String_repeated = 29;
repeated bytes F_Bytes_repeated = 201;
repeated sint32 F_Sint32_repeated = 202;
repeated sint64 F_Sint64_repeated = 203;
// Optional fields of all basic types
optional bool F_Bool_optional = 30;
optional int32 F_Int32_optional = 31;
optional int64 F_Int64_optional = 32;
optional fixed32 F_Fixed32_optional = 33;
optional fixed64 F_Fixed64_optional = 34;
optional uint32 F_Uint32_optional = 35;
optional uint64 F_Uint64_optional = 36;
optional float F_Float_optional = 37;
optional double F_Double_optional = 38;
optional string F_String_optional = 39;
optional bytes F_Bytes_optional = 301;
optional sint32 F_Sint32_optional = 302;
optional sint64 F_Sint64_optional = 303;
// Default-valued fields of all basic types
optional bool F_Bool_defaulted = 40 [default=true];
optional int32 F_Int32_defaulted = 41 [default=32];
optional int64 F_Int64_defaulted = 42 [default=64];
optional fixed32 F_Fixed32_defaulted = 43 [default=320];
optional fixed64 F_Fixed64_defaulted = 44 [default=640];
optional uint32 F_Uint32_defaulted = 45 [default=3200];
optional uint64 F_Uint64_defaulted = 46 [default=6400];
optional float F_Float_defaulted = 47 [default=314159.];
optional double F_Double_defaulted = 48 [default=271828.];
optional string F_String_defaulted = 49 [default="hello, \"world!\"\n"];
optional bytes F_Bytes_defaulted = 401 [default="Bignose"];
optional sint32 F_Sint32_defaulted = 402 [default = -32];
optional sint64 F_Sint64_defaulted = 403 [default = -64];
// Packed repeated fields (no string or bytes).
repeated bool F_Bool_repeated_packed = 50 [packed=true];
repeated int32 F_Int32_repeated_packed = 51 [packed=true];
repeated int64 F_Int64_repeated_packed = 52 [packed=true];
repeated fixed32 F_Fixed32_repeated_packed = 53 [packed=true];
repeated fixed64 F_Fixed64_repeated_packed = 54 [packed=true];
repeated uint32 F_Uint32_repeated_packed = 55 [packed=true];
repeated uint64 F_Uint64_repeated_packed = 56 [packed=true];
repeated float F_Float_repeated_packed = 57 [packed=true];
repeated double F_Double_repeated_packed = 58 [packed=true];
repeated sint32 F_Sint32_repeated_packed = 502 [packed=true];
repeated sint64 F_Sint64_repeated_packed = 503 [packed=true];
// Required, repeated, and optional groups.
required group RequiredGroup = 70 {
required string RequiredField = 71;
};
repeated group RepeatedGroup = 80 {
required string RequiredField = 81;
};
optional group OptionalGroup = 90 {
required string RequiredField = 91;
};
}
// For testing a group containing a required field.
message GoTestRequiredGroupField {
required group Group = 1 {
required int32 Field = 2;
};
}
// For testing skipping of unrecognized fields.
// Numbers are all big, larger than tag numbers in GoTestField,
// the message used in the corresponding test.
message GoSkipTest {
required int32 skip_int32 = 11;
required fixed32 skip_fixed32 = 12;
required fixed64 skip_fixed64 = 13;
required string skip_string = 14;
required group SkipGroup = 15 {
required int32 group_int32 = 16;
required string group_string = 17;
}
}
// For testing packed/non-packed decoder switching.
// A serialized instance of one should be deserializable as the other.
message NonPackedTest {
repeated int32 a = 1;
}
message PackedTest {
repeated int32 b = 1 [packed=true];
}
message MaxTag {
// Maximum possible tag number.
optional string last_field = 536870911;
}
message OldMessage {
message Nested {
optional string name = 1;
}
optional Nested nested = 1;
optional int32 num = 2;
}
// NewMessage is wire compatible with OldMessage;
// imagine it as a future version.
message NewMessage {
message Nested {
optional string name = 1;
optional string food_group = 2;
}
optional Nested nested = 1;
// This is an int32 in OldMessage.
optional int64 num = 2;
}
// Smaller tests for ASCII formatting.
message InnerMessage {
required string host = 1;
optional int32 port = 2 [default=4000];
optional bool connected = 3;
}
message OtherMessage {
optional int64 key = 1;
optional bytes value = 2;
optional float weight = 3;
optional InnerMessage inner = 4;
extensions 100 to max;
}
message RequiredInnerMessage {
required InnerMessage leo_finally_won_an_oscar = 1;
}
message MyMessage {
required int32 count = 1;
optional string name = 2;
optional string quote = 3;
repeated string pet = 4;
optional InnerMessage inner = 5;
repeated OtherMessage others = 6;
optional RequiredInnerMessage we_must_go_deeper = 13;
repeated InnerMessage rep_inner = 12;
enum Color {
RED = 0;
GREEN = 1;
BLUE = 2;
};
optional Color bikeshed = 7;
optional group SomeGroup = 8 {
optional int32 group_field = 9;
}
// This field becomes [][]byte in the generated code.
repeated bytes rep_bytes = 10;
optional double bigfloat = 11;
extensions 100 to max;
}
message Ext {
extend MyMessage {
optional Ext more = 103;
optional string text = 104;
optional int32 number = 105;
}
optional string data = 1;
}
extend MyMessage {
repeated string greeting = 106;
}
message ComplexExtension {
optional int32 first = 1;
optional int32 second = 2;
repeated int32 third = 3;
}
extend OtherMessage {
optional ComplexExtension complex = 200;
repeated ComplexExtension r_complex = 201;
}
message DefaultsMessage {
enum DefaultsEnum {
ZERO = 0;
ONE = 1;
TWO = 2;
};
extensions 100 to max;
}
extend DefaultsMessage {
optional double no_default_double = 101;
optional float no_default_float = 102;
optional int32 no_default_int32 = 103;
optional int64 no_default_int64 = 104;
optional uint32 no_default_uint32 = 105;
optional uint64 no_default_uint64 = 106;
optional sint32 no_default_sint32 = 107;
optional sint64 no_default_sint64 = 108;
optional fixed32 no_default_fixed32 = 109;
optional fixed64 no_default_fixed64 = 110;
optional sfixed32 no_default_sfixed32 = 111;
optional sfixed64 no_default_sfixed64 = 112;
optional bool no_default_bool = 113;
optional string no_default_string = 114;
optional bytes no_default_bytes = 115;
optional DefaultsMessage.DefaultsEnum no_default_enum = 116;
optional double default_double = 201 [default = 3.1415];
optional float default_float = 202 [default = 3.14];
optional int32 default_int32 = 203 [default = 42];
optional int64 default_int64 = 204 [default = 43];
optional uint32 default_uint32 = 205 [default = 44];
optional uint64 default_uint64 = 206 [default = 45];
optional sint32 default_sint32 = 207 [default = 46];
optional sint64 default_sint64 = 208 [default = 47];
optional fixed32 default_fixed32 = 209 [default = 48];
optional fixed64 default_fixed64 = 210 [default = 49];
optional sfixed32 default_sfixed32 = 211 [default = 50];
optional sfixed64 default_sfixed64 = 212 [default = 51];
optional bool default_bool = 213 [default = true];
optional string default_string = 214 [default = "Hello, string"];
optional bytes default_bytes = 215 [default = "Hello, bytes"];
optional DefaultsMessage.DefaultsEnum default_enum = 216 [default = ONE];
}
message MyMessageSet {
option message_set_wire_format = true;
extensions 100 to max;
}
message Empty {
}
extend MyMessageSet {
optional Empty x201 = 201;
optional Empty x202 = 202;
optional Empty x203 = 203;
optional Empty x204 = 204;
optional Empty x205 = 205;
optional Empty x206 = 206;
optional Empty x207 = 207;
optional Empty x208 = 208;
optional Empty x209 = 209;
optional Empty x210 = 210;
optional Empty x211 = 211;
optional Empty x212 = 212;
optional Empty x213 = 213;
optional Empty x214 = 214;
optional Empty x215 = 215;
optional Empty x216 = 216;
optional Empty x217 = 217;
optional Empty x218 = 218;
optional Empty x219 = 219;
optional Empty x220 = 220;
optional Empty x221 = 221;
optional Empty x222 = 222;
optional Empty x223 = 223;
optional Empty x224 = 224;
optional Empty x225 = 225;
optional Empty x226 = 226;
optional Empty x227 = 227;
optional Empty x228 = 228;
optional Empty x229 = 229;
optional Empty x230 = 230;
optional Empty x231 = 231;
optional Empty x232 = 232;
optional Empty x233 = 233;
optional Empty x234 = 234;
optional Empty x235 = 235;
optional Empty x236 = 236;
optional Empty x237 = 237;
optional Empty x238 = 238;
optional Empty x239 = 239;
optional Empty x240 = 240;
optional Empty x241 = 241;
optional Empty x242 = 242;
optional Empty x243 = 243;
optional Empty x244 = 244;
optional Empty x245 = 245;
optional Empty x246 = 246;
optional Empty x247 = 247;
optional Empty x248 = 248;
optional Empty x249 = 249;
optional Empty x250 = 250;
}
message MessageList {
repeated group Message = 1 {
required string name = 2;
required int32 count = 3;
}
}
message Strings {
optional string string_field = 1;
optional bytes bytes_field = 2;
}
message Defaults {
enum Color {
RED = 0;
GREEN = 1;
BLUE = 2;
}
// Default-valued fields of all basic types.
// Same as GoTest, but copied here to make testing easier.
optional bool F_Bool = 1 [default=true];
optional int32 F_Int32 = 2 [default=32];
optional int64 F_Int64 = 3 [default=64];
optional fixed32 F_Fixed32 = 4 [default=320];
optional fixed64 F_Fixed64 = 5 [default=640];
optional uint32 F_Uint32 = 6 [default=3200];
optional uint64 F_Uint64 = 7 [default=6400];
optional float F_Float = 8 [default=314159.];
optional double F_Double = 9 [default=271828.];
optional string F_String = 10 [default="hello, \"world!\"\n"];
optional bytes F_Bytes = 11 [default="Bignose"];
optional sint32 F_Sint32 = 12 [default=-32];
optional sint64 F_Sint64 = 13 [default=-64];
optional Color F_Enum = 14 [default=GREEN];
// More fields with crazy defaults.
optional float F_Pinf = 15 [default=inf];
optional float F_Ninf = 16 [default=-inf];
optional float F_Nan = 17 [default=nan];
// Sub-message.
optional SubDefaults sub = 18;
// Redundant but explicit defaults.
optional string str_zero = 19 [default=""];
}
message SubDefaults {
optional int64 n = 1 [default=7];
}
message RepeatedEnum {
enum Color {
RED = 1;
}
repeated Color color = 1;
}
message MoreRepeated {
repeated bool bools = 1;
repeated bool bools_packed = 2 [packed=true];
repeated int32 ints = 3;
repeated int32 ints_packed = 4 [packed=true];
repeated int64 int64s_packed = 7 [packed=true];
repeated string strings = 5;
repeated fixed32 fixeds = 6;
}
// GroupOld and GroupNew have the same wire format.
// GroupNew has a new field inside a group.
message GroupOld {
optional group G = 101 {
optional int32 x = 2;
}
}
message GroupNew {
optional group G = 101 {
optional int32 x = 2;
optional int32 y = 3;
}
}
message FloatingPoint {
required double f = 1;
optional bool exact = 2;
}
message MessageWithMap {
map<int32, string> name_mapping = 1;
map<sint64, FloatingPoint> msg_mapping = 2;
map<bool, bytes> byte_mapping = 3;
map<string, string> str_to_str = 4;
}
message Oneof {
oneof union {
bool F_Bool = 1;
int32 F_Int32 = 2;
int64 F_Int64 = 3;
fixed32 F_Fixed32 = 4;
fixed64 F_Fixed64 = 5;
uint32 F_Uint32 = 6;
uint64 F_Uint64 = 7;
float F_Float = 8;
double F_Double = 9;
string F_String = 10;
bytes F_Bytes = 11;
sint32 F_Sint32 = 12;
sint64 F_Sint64 = 13;
MyMessage.Color F_Enum = 14;
GoTestField F_Message = 15;
group F_Group = 16 {
optional int32 x = 17;
}
int32 F_Largest_Tag = 536870911;
}
oneof tormato {
int32 value = 100;
}
}
message Communique {
optional bool make_me_cry = 1;
// This is a oneof, called "union".
oneof union {
int32 number = 5;
string name = 6;
bytes data = 7;
double temp_c = 8;
MyMessage.Color col = 9;
Strings msg = 10;
}
}

View file

@ -1,33 +0,0 @@
# 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.
test:
cd testdata && make test

View file

@ -1,39 +0,0 @@
# 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.
# Not stored here, but descriptor.proto is in https://github.com/google/protobuf/
# at src/google/protobuf/descriptor.proto
regenerate:
echo WARNING! THIS RULE IS PROBABLY NOT RIGHT FOR YOUR INSTALLATION
protoc --go_out=. -I$(HOME)/src/protobuf/src $(HOME)/src/protobuf/src/google/protobuf/descriptor.proto && \
sed 's,^package google_protobuf,package descriptor,' google/protobuf/descriptor.pb.go > \
$(GOPATH)/src/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go && \
rm -f google/protobuf/descriptor.pb.go

File diff suppressed because it is too large Load diff

View file

@ -1,51 +0,0 @@
// 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.
/*
A plugin for the Google protocol buffer compiler to generate Go code.
Run it by building this program and putting it in your path with the name
protoc-gen-go
That word 'go' at the end becomes part of the option string set for the
protocol compiler, so once the protocol compiler (protoc) is installed
you can run
protoc --go_out=output_directory input_directory/file.proto
to generate Go bindings for the protocol defined by file.proto.
With that input, the output will be written to
output_directory/file.pb.go
The generated code is documented in the package comment for
the library.
See the README and documentation for protocol buffers to learn more:
https://developers.google.com/protocol-buffers/
*/
package documentation

View file

@ -1,40 +0,0 @@
# 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.
include $(GOROOT)/src/Make.inc
TARG=github.com/golang/protobuf/compiler/generator
GOFILES=\
generator.go\
DEPS=../descriptor ../plugin ../../proto
include $(GOROOT)/src/Make.pkg

File diff suppressed because it is too large Load diff

View file

@ -1,85 +0,0 @@
// Go support for Protocol Buffers - Google's data interchange format
//
// Copyright 2013 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 generator
import (
"testing"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
)
func TestCamelCase(t *testing.T) {
tests := []struct {
in, want string
}{
{"one", "One"},
{"one_two", "OneTwo"},
{"_my_field_name_2", "XMyFieldName_2"},
{"Something_Capped", "Something_Capped"},
{"my_Name", "My_Name"},
{"OneTwo", "OneTwo"},
{"_", "X"},
{"_a_", "XA_"},
}
for _, tc := range tests {
if got := CamelCase(tc.in); got != tc.want {
t.Errorf("CamelCase(%q) = %q, want %q", tc.in, got, tc.want)
}
}
}
func TestGoPackageOption(t *testing.T) {
tests := []struct {
in string
impPath, pkg string
ok bool
}{
{"", "", "", false},
{"foo", "", "foo", true},
{"github.com/golang/bar", "github.com/golang/bar", "bar", true},
{"github.com/golang/bar;baz", "github.com/golang/bar", "baz", true},
}
for _, tc := range tests {
d := &FileDescriptor{
FileDescriptorProto: &descriptor.FileDescriptorProto{
Options: &descriptor.FileOptions{
GoPackage: &tc.in,
},
},
}
impPath, pkg, ok := d.goPackageOption()
if impPath != tc.impPath || pkg != tc.pkg || ok != tc.ok {
t.Errorf("go_package = %q => (%q, %q, %t), want (%q, %q, %t)", tc.in,
impPath, pkg, ok, tc.impPath, tc.pkg, tc.ok)
}
}
}

View file

@ -1,463 +0,0 @@
// 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 grpc outputs gRPC service descriptions in Go code.
// It runs as a plugin for the Go protocol buffer compiler plugin.
// It is linked in to protoc-gen-go.
package grpc
import (
"fmt"
"path"
"strconv"
"strings"
pb "github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/golang/protobuf/protoc-gen-go/generator"
)
// generatedCodeVersion indicates a version of the generated code.
// It is incremented whenever an incompatibility between the generated code and
// the grpc package is introduced; the generated code references
// a constant, grpc.SupportPackageIsVersionN (where N is generatedCodeVersion).
const generatedCodeVersion = 3
// Paths for packages used by code generated in this file,
// relative to the import_prefix of the generator.Generator.
const (
contextPkgPath = "golang.org/x/net/context"
grpcPkgPath = "google.golang.org/grpc"
)
func init() {
generator.RegisterPlugin(new(grpc))
}
// grpc is an implementation of the Go protocol buffer compiler's
// plugin architecture. It generates bindings for gRPC support.
type grpc struct {
gen *generator.Generator
}
// Name returns the name of this plugin, "grpc".
func (g *grpc) Name() string {
return "grpc"
}
// The names for packages imported in the generated code.
// They may vary from the final path component of the import path
// if the name is used by other packages.
var (
contextPkg string
grpcPkg string
)
// Init initializes the plugin.
func (g *grpc) Init(gen *generator.Generator) {
g.gen = gen
contextPkg = generator.RegisterUniquePackageName("context", nil)
grpcPkg = generator.RegisterUniquePackageName("grpc", nil)
}
// Given a type name defined in a .proto, return its object.
// Also record that we're using it, to guarantee the associated import.
func (g *grpc) objectNamed(name string) generator.Object {
g.gen.RecordTypeUse(name)
return g.gen.ObjectNamed(name)
}
// Given a type name defined in a .proto, return its name as we will print it.
func (g *grpc) typeName(str string) string {
return g.gen.TypeName(g.objectNamed(str))
}
// P forwards to g.gen.P.
func (g *grpc) P(args ...interface{}) { g.gen.P(args...) }
// Generate generates code for the services in the given file.
func (g *grpc) Generate(file *generator.FileDescriptor) {
if len(file.FileDescriptorProto.Service) == 0 {
return
}
g.P("// Reference imports to suppress errors if they are not otherwise used.")
g.P("var _ ", contextPkg, ".Context")
g.P("var _ ", grpcPkg, ".ClientConn")
g.P()
// Assert version compatibility.
g.P("// This is a compile-time assertion to ensure that this generated file")
g.P("// is compatible with the grpc package it is being compiled against.")
g.P("const _ = ", grpcPkg, ".SupportPackageIsVersion", generatedCodeVersion)
g.P()
for i, service := range file.FileDescriptorProto.Service {
g.generateService(file, service, i)
}
}
// GenerateImports generates the import declaration for this file.
func (g *grpc) GenerateImports(file *generator.FileDescriptor) {
if len(file.FileDescriptorProto.Service) == 0 {
return
}
g.P("import (")
g.P(contextPkg, " ", strconv.Quote(path.Join(g.gen.ImportPrefix, contextPkgPath)))
g.P(grpcPkg, " ", strconv.Quote(path.Join(g.gen.ImportPrefix, grpcPkgPath)))
g.P(")")
g.P()
}
// reservedClientName records whether a client name is reserved on the client side.
var reservedClientName = map[string]bool{
// TODO: do we need any in gRPC?
}
func unexport(s string) string { return strings.ToLower(s[:1]) + s[1:] }
// generateService generates all the code for the named service.
func (g *grpc) generateService(file *generator.FileDescriptor, service *pb.ServiceDescriptorProto, index int) {
path := fmt.Sprintf("6,%d", index) // 6 means service.
origServName := service.GetName()
fullServName := origServName
if pkg := file.GetPackage(); pkg != "" {
fullServName = pkg + "." + fullServName
}
servName := generator.CamelCase(origServName)
g.P()
g.P("// Client API for ", servName, " service")
g.P()
// Client interface.
g.P("type ", servName, "Client interface {")
for i, method := range service.Method {
g.gen.PrintComments(fmt.Sprintf("%s,2,%d", path, i)) // 2 means method in a service.
g.P(g.generateClientSignature(servName, method))
}
g.P("}")
g.P()
// Client structure.
g.P("type ", unexport(servName), "Client struct {")
g.P("cc *", grpcPkg, ".ClientConn")
g.P("}")
g.P()
// NewClient factory.
g.P("func New", servName, "Client (cc *", grpcPkg, ".ClientConn) ", servName, "Client {")
g.P("return &", unexport(servName), "Client{cc}")
g.P("}")
g.P()
var methodIndex, streamIndex int
serviceDescVar := "_" + servName + "_serviceDesc"
// Client method implementations.
for _, method := range service.Method {
var descExpr string
if !method.GetServerStreaming() && !method.GetClientStreaming() {
// Unary RPC method
descExpr = fmt.Sprintf("&%s.Methods[%d]", serviceDescVar, methodIndex)
methodIndex++
} else {
// Streaming RPC method
descExpr = fmt.Sprintf("&%s.Streams[%d]", serviceDescVar, streamIndex)
streamIndex++
}
g.generateClientMethod(servName, fullServName, serviceDescVar, method, descExpr)
}
g.P("// Server API for ", servName, " service")
g.P()
// Server interface.
serverType := servName + "Server"
g.P("type ", serverType, " interface {")
for i, method := range service.Method {
g.gen.PrintComments(fmt.Sprintf("%s,2,%d", path, i)) // 2 means method in a service.
g.P(g.generateServerSignature(servName, method))
}
g.P("}")
g.P()
// Server registration.
g.P("func Register", servName, "Server(s *", grpcPkg, ".Server, srv ", serverType, ") {")
g.P("s.RegisterService(&", serviceDescVar, `, srv)`)
g.P("}")
g.P()
// Server handler implementations.
var handlerNames []string
for _, method := range service.Method {
hname := g.generateServerMethod(servName, fullServName, method)
handlerNames = append(handlerNames, hname)
}
// Service descriptor.
g.P("var ", serviceDescVar, " = ", grpcPkg, ".ServiceDesc {")
g.P("ServiceName: ", strconv.Quote(fullServName), ",")
g.P("HandlerType: (*", serverType, ")(nil),")
g.P("Methods: []", grpcPkg, ".MethodDesc{")
for i, method := range service.Method {
if method.GetServerStreaming() || method.GetClientStreaming() {
continue
}
g.P("{")
g.P("MethodName: ", strconv.Quote(method.GetName()), ",")
g.P("Handler: ", handlerNames[i], ",")
g.P("},")
}
g.P("},")
g.P("Streams: []", grpcPkg, ".StreamDesc{")
for i, method := range service.Method {
if !method.GetServerStreaming() && !method.GetClientStreaming() {
continue
}
g.P("{")
g.P("StreamName: ", strconv.Quote(method.GetName()), ",")
g.P("Handler: ", handlerNames[i], ",")
if method.GetServerStreaming() {
g.P("ServerStreams: true,")
}
if method.GetClientStreaming() {
g.P("ClientStreams: true,")
}
g.P("},")
}
g.P("},")
g.P("Metadata: ", file.VarName(), ",")
g.P("}")
g.P()
}
// generateClientSignature returns the client-side signature for a method.
func (g *grpc) generateClientSignature(servName string, method *pb.MethodDescriptorProto) string {
origMethName := method.GetName()
methName := generator.CamelCase(origMethName)
if reservedClientName[methName] {
methName += "_"
}
reqArg := ", in *" + g.typeName(method.GetInputType())
if method.GetClientStreaming() {
reqArg = ""
}
respName := "*" + g.typeName(method.GetOutputType())
if method.GetServerStreaming() || method.GetClientStreaming() {
respName = servName + "_" + generator.CamelCase(origMethName) + "Client"
}
return fmt.Sprintf("%s(ctx %s.Context%s, opts ...%s.CallOption) (%s, error)", methName, contextPkg, reqArg, grpcPkg, respName)
}
func (g *grpc) generateClientMethod(servName, fullServName, serviceDescVar string, method *pb.MethodDescriptorProto, descExpr string) {
sname := fmt.Sprintf("/%s/%s", fullServName, method.GetName())
methName := generator.CamelCase(method.GetName())
inType := g.typeName(method.GetInputType())
outType := g.typeName(method.GetOutputType())
g.P("func (c *", unexport(servName), "Client) ", g.generateClientSignature(servName, method), "{")
if !method.GetServerStreaming() && !method.GetClientStreaming() {
g.P("out := new(", outType, ")")
// TODO: Pass descExpr to Invoke.
g.P("err := ", grpcPkg, `.Invoke(ctx, "`, sname, `", in, out, c.cc, opts...)`)
g.P("if err != nil { return nil, err }")
g.P("return out, nil")
g.P("}")
g.P()
return
}
streamType := unexport(servName) + methName + "Client"
g.P("stream, err := ", grpcPkg, ".NewClientStream(ctx, ", descExpr, `, c.cc, "`, sname, `", opts...)`)
g.P("if err != nil { return nil, err }")
g.P("x := &", streamType, "{stream}")
if !method.GetClientStreaming() {
g.P("if err := x.ClientStream.SendMsg(in); err != nil { return nil, err }")
g.P("if err := x.ClientStream.CloseSend(); err != nil { return nil, err }")
}
g.P("return x, nil")
g.P("}")
g.P()
genSend := method.GetClientStreaming()
genRecv := method.GetServerStreaming()
genCloseAndRecv := !method.GetServerStreaming()
// Stream auxiliary types and methods.
g.P("type ", servName, "_", methName, "Client interface {")
if genSend {
g.P("Send(*", inType, ") error")
}
if genRecv {
g.P("Recv() (*", outType, ", error)")
}
if genCloseAndRecv {
g.P("CloseAndRecv() (*", outType, ", error)")
}
g.P(grpcPkg, ".ClientStream")
g.P("}")
g.P()
g.P("type ", streamType, " struct {")
g.P(grpcPkg, ".ClientStream")
g.P("}")
g.P()
if genSend {
g.P("func (x *", streamType, ") Send(m *", inType, ") error {")
g.P("return x.ClientStream.SendMsg(m)")
g.P("}")
g.P()
}
if genRecv {
g.P("func (x *", streamType, ") Recv() (*", outType, ", error) {")
g.P("m := new(", outType, ")")
g.P("if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err }")
g.P("return m, nil")
g.P("}")
g.P()
}
if genCloseAndRecv {
g.P("func (x *", streamType, ") CloseAndRecv() (*", outType, ", error) {")
g.P("if err := x.ClientStream.CloseSend(); err != nil { return nil, err }")
g.P("m := new(", outType, ")")
g.P("if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err }")
g.P("return m, nil")
g.P("}")
g.P()
}
}
// generateServerSignature returns the server-side signature for a method.
func (g *grpc) generateServerSignature(servName string, method *pb.MethodDescriptorProto) string {
origMethName := method.GetName()
methName := generator.CamelCase(origMethName)
if reservedClientName[methName] {
methName += "_"
}
var reqArgs []string
ret := "error"
if !method.GetServerStreaming() && !method.GetClientStreaming() {
reqArgs = append(reqArgs, contextPkg+".Context")
ret = "(*" + g.typeName(method.GetOutputType()) + ", error)"
}
if !method.GetClientStreaming() {
reqArgs = append(reqArgs, "*"+g.typeName(method.GetInputType()))
}
if method.GetServerStreaming() || method.GetClientStreaming() {
reqArgs = append(reqArgs, servName+"_"+generator.CamelCase(origMethName)+"Server")
}
return methName + "(" + strings.Join(reqArgs, ", ") + ") " + ret
}
func (g *grpc) generateServerMethod(servName, fullServName string, method *pb.MethodDescriptorProto) string {
methName := generator.CamelCase(method.GetName())
hname := fmt.Sprintf("_%s_%s_Handler", servName, methName)
inType := g.typeName(method.GetInputType())
outType := g.typeName(method.GetOutputType())
if !method.GetServerStreaming() && !method.GetClientStreaming() {
g.P("func ", hname, "(srv interface{}, ctx ", contextPkg, ".Context, dec func(interface{}) error, interceptor ", grpcPkg, ".UnaryServerInterceptor) (interface{}, error) {")
g.P("in := new(", inType, ")")
g.P("if err := dec(in); err != nil { return nil, err }")
g.P("if interceptor == nil { return srv.(", servName, "Server).", methName, "(ctx, in) }")
g.P("info := &", grpcPkg, ".UnaryServerInfo{")
g.P("Server: srv,")
g.P("FullMethod: ", strconv.Quote(fmt.Sprintf("/%s/%s", fullServName, methName)), ",")
g.P("}")
g.P("handler := func(ctx ", contextPkg, ".Context, req interface{}) (interface{}, error) {")
g.P("return srv.(", servName, "Server).", methName, "(ctx, req.(*", inType, "))")
g.P("}")
g.P("return interceptor(ctx, in, info, handler)")
g.P("}")
g.P()
return hname
}
streamType := unexport(servName) + methName + "Server"
g.P("func ", hname, "(srv interface{}, stream ", grpcPkg, ".ServerStream) error {")
if !method.GetClientStreaming() {
g.P("m := new(", inType, ")")
g.P("if err := stream.RecvMsg(m); err != nil { return err }")
g.P("return srv.(", servName, "Server).", methName, "(m, &", streamType, "{stream})")
} else {
g.P("return srv.(", servName, "Server).", methName, "(&", streamType, "{stream})")
}
g.P("}")
g.P()
genSend := method.GetServerStreaming()
genSendAndClose := !method.GetServerStreaming()
genRecv := method.GetClientStreaming()
// Stream auxiliary types and methods.
g.P("type ", servName, "_", methName, "Server interface {")
if genSend {
g.P("Send(*", outType, ") error")
}
if genSendAndClose {
g.P("SendAndClose(*", outType, ") error")
}
if genRecv {
g.P("Recv() (*", inType, ", error)")
}
g.P(grpcPkg, ".ServerStream")
g.P("}")
g.P()
g.P("type ", streamType, " struct {")
g.P(grpcPkg, ".ServerStream")
g.P("}")
g.P()
if genSend {
g.P("func (x *", streamType, ") Send(m *", outType, ") error {")
g.P("return x.ServerStream.SendMsg(m)")
g.P("}")
g.P()
}
if genSendAndClose {
g.P("func (x *", streamType, ") SendAndClose(m *", outType, ") error {")
g.P("return x.ServerStream.SendMsg(m)")
g.P("}")
g.P()
}
if genRecv {
g.P("func (x *", streamType, ") Recv() (*", inType, ", error) {")
g.P("m := new(", inType, ")")
g.P("if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err }")
g.P("return m, nil")
g.P("}")
g.P()
}
return hname
}

View file

@ -1,34 +0,0 @@
// 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 main
import _ "github.com/golang/protobuf/protoc-gen-go/grpc"

View file

@ -1,98 +0,0 @@
// 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.
// protoc-gen-go is a plugin for the Google protocol buffer compiler to generate
// Go code. Run it by building this program and putting it in your path with
// the name
// protoc-gen-go
// That word 'go' at the end becomes part of the option string set for the
// protocol compiler, so once the protocol compiler (protoc) is installed
// you can run
// protoc --go_out=output_directory input_directory/file.proto
// to generate Go bindings for the protocol defined by file.proto.
// With that input, the output will be written to
// output_directory/file.pb.go
//
// The generated code is documented in the package comment for
// the library.
//
// See the README and documentation for protocol buffers to learn more:
// https://developers.google.com/protocol-buffers/
package main
import (
"io/ioutil"
"os"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/generator"
)
func main() {
// Begin by allocating a generator. The request and response structures are stored there
// so we can do error handling easily - the response structure contains the field to
// report failure.
g := generator.New()
data, err := ioutil.ReadAll(os.Stdin)
if err != nil {
g.Error(err, "reading input")
}
if err := proto.Unmarshal(data, g.Request); err != nil {
g.Error(err, "parsing input proto")
}
if len(g.Request.FileToGenerate) == 0 {
g.Fail("no files to generate")
}
g.CommandLineParameters(g.Request.GetParameter())
// Create a wrapped version of the Descriptors and EnumDescriptors that
// point to the file that defines them.
g.WrapTypes()
g.SetPackageNames()
g.BuildTypeNameMap()
g.GenerateAllFiles()
// Send back the results.
data, err = proto.Marshal(g.Response)
if err != nil {
g.Error(err, "failed to marshal output proto")
}
_, err = os.Stdout.Write(data)
if err != nil {
g.Error(err, "failed to write output proto")
}
}

View file

@ -1,45 +0,0 @@
# 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.
# Not stored here, but plugin.proto is in https://github.com/google/protobuf/
# at src/google/protobuf/compiler/plugin.proto
# Also we need to fix an import.
regenerate:
echo WARNING! THIS RULE IS PROBABLY NOT RIGHT FOR YOUR INSTALLATION
protoc --go_out=Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor:. \
-I$(HOME)/src/protobuf/src $(HOME)/src/protobuf/src/google/protobuf/compiler/plugin.proto && \
mv google/protobuf/compiler/plugin.pb.go $(GOPATH)/src/github.com/golang/protobuf/protoc-gen-go/plugin
restore:
cp plugin.pb.golden plugin.pb.go
preserve:
cp plugin.pb.go plugin.pb.golden

View file

@ -1,225 +0,0 @@
// Code generated by protoc-gen-go.
// source: google/protobuf/compiler/plugin.proto
// DO NOT EDIT!
/*
Package plugin_go is a generated protocol buffer package.
It is generated from these files:
google/protobuf/compiler/plugin.proto
It has these top-level messages:
CodeGeneratorRequest
CodeGeneratorResponse
*/
package plugin_go
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import google_protobuf "github.com/golang/protobuf/protoc-gen-go/descriptor"
// 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.
const _ = proto.ProtoPackageIsVersion1
// An encoded CodeGeneratorRequest is written to the plugin's stdin.
type CodeGeneratorRequest struct {
// The .proto files that were explicitly listed on the command-line. The
// code generator should generate code only for these files. Each file's
// descriptor will be included in proto_file, below.
FileToGenerate []string `protobuf:"bytes,1,rep,name=file_to_generate,json=fileToGenerate" json:"file_to_generate,omitempty"`
// The generator parameter passed on the command-line.
Parameter *string `protobuf:"bytes,2,opt,name=parameter" json:"parameter,omitempty"`
// FileDescriptorProtos for all files in files_to_generate and everything
// they import. The files will appear in topological order, so each file
// appears before any file that imports it.
//
// protoc guarantees that all proto_files will be written after
// the fields above, even though this is not technically guaranteed by the
// protobuf wire format. This theoretically could allow a plugin to stream
// in the FileDescriptorProtos and handle them one by one rather than read
// the entire set into memory at once. However, as of this writing, this
// is not similarly optimized on protoc's end -- it will store all fields in
// memory at once before sending them to the plugin.
ProtoFile []*google_protobuf.FileDescriptorProto `protobuf:"bytes,15,rep,name=proto_file,json=protoFile" json:"proto_file,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *CodeGeneratorRequest) Reset() { *m = CodeGeneratorRequest{} }
func (m *CodeGeneratorRequest) String() string { return proto.CompactTextString(m) }
func (*CodeGeneratorRequest) ProtoMessage() {}
func (*CodeGeneratorRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *CodeGeneratorRequest) GetFileToGenerate() []string {
if m != nil {
return m.FileToGenerate
}
return nil
}
func (m *CodeGeneratorRequest) GetParameter() string {
if m != nil && m.Parameter != nil {
return *m.Parameter
}
return ""
}
func (m *CodeGeneratorRequest) GetProtoFile() []*google_protobuf.FileDescriptorProto {
if m != nil {
return m.ProtoFile
}
return nil
}
// The plugin writes an encoded CodeGeneratorResponse to stdout.
type CodeGeneratorResponse struct {
// Error message. If non-empty, code generation failed. The plugin process
// should exit with status code zero even if it reports an error in this way.
//
// This should be used to indicate errors in .proto files which prevent the
// code generator from generating correct code. Errors which indicate a
// problem in protoc itself -- such as the input CodeGeneratorRequest being
// unparseable -- should be reported by writing a message to stderr and
// exiting with a non-zero status code.
Error *string `protobuf:"bytes,1,opt,name=error" json:"error,omitempty"`
File []*CodeGeneratorResponse_File `protobuf:"bytes,15,rep,name=file" json:"file,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *CodeGeneratorResponse) Reset() { *m = CodeGeneratorResponse{} }
func (m *CodeGeneratorResponse) String() string { return proto.CompactTextString(m) }
func (*CodeGeneratorResponse) ProtoMessage() {}
func (*CodeGeneratorResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *CodeGeneratorResponse) GetError() string {
if m != nil && m.Error != nil {
return *m.Error
}
return ""
}
func (m *CodeGeneratorResponse) GetFile() []*CodeGeneratorResponse_File {
if m != nil {
return m.File
}
return nil
}
// Represents a single generated file.
type CodeGeneratorResponse_File struct {
// The file name, relative to the output directory. The name must not
// contain "." or ".." components and must be relative, not be absolute (so,
// the file cannot lie outside the output directory). "/" must be used as
// the path separator, not "\".
//
// If the name is omitted, the content will be appended to the previous
// file. This allows the generator to break large files into small chunks,
// and allows the generated text to be streamed back to protoc so that large
// files need not reside completely in memory at one time. Note that as of
// this writing protoc does not optimize for this -- it will read the entire
// CodeGeneratorResponse before writing files to disk.
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
// If non-empty, indicates that the named file should already exist, and the
// content here is to be inserted into that file at a defined insertion
// point. This feature allows a code generator to extend the output
// produced by another code generator. The original generator may provide
// insertion points by placing special annotations in the file that look
// like:
// @@protoc_insertion_point(NAME)
// The annotation can have arbitrary text before and after it on the line,
// which allows it to be placed in a comment. NAME should be replaced with
// an identifier naming the point -- this is what other generators will use
// as the insertion_point. Code inserted at this point will be placed
// immediately above the line containing the insertion point (thus multiple
// insertions to the same point will come out in the order they were added).
// The double-@ is intended to make it unlikely that the generated code
// could contain things that look like insertion points by accident.
//
// For example, the C++ code generator places the following line in the
// .pb.h files that it generates:
// // @@protoc_insertion_point(namespace_scope)
// This line appears within the scope of the file's package namespace, but
// outside of any particular class. Another plugin can then specify the
// insertion_point "namespace_scope" to generate additional classes or
// other declarations that should be placed in this scope.
//
// Note that if the line containing the insertion point begins with
// whitespace, the same whitespace will be added to every line of the
// inserted text. This is useful for languages like Python, where
// indentation matters. In these languages, the insertion point comment
// should be indented the same amount as any inserted code will need to be
// in order to work correctly in that context.
//
// The code generator that generates the initial file and the one which
// inserts into it must both run as part of a single invocation of protoc.
// Code generators are executed in the order in which they appear on the
// command line.
//
// If |insertion_point| is present, |name| must also be present.
InsertionPoint *string `protobuf:"bytes,2,opt,name=insertion_point,json=insertionPoint" json:"insertion_point,omitempty"`
// The file contents.
Content *string `protobuf:"bytes,15,opt,name=content" json:"content,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *CodeGeneratorResponse_File) Reset() { *m = CodeGeneratorResponse_File{} }
func (m *CodeGeneratorResponse_File) String() string { return proto.CompactTextString(m) }
func (*CodeGeneratorResponse_File) ProtoMessage() {}
func (*CodeGeneratorResponse_File) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 0} }
func (m *CodeGeneratorResponse_File) GetName() string {
if m != nil && m.Name != nil {
return *m.Name
}
return ""
}
func (m *CodeGeneratorResponse_File) GetInsertionPoint() string {
if m != nil && m.InsertionPoint != nil {
return *m.InsertionPoint
}
return ""
}
func (m *CodeGeneratorResponse_File) GetContent() string {
if m != nil && m.Content != nil {
return *m.Content
}
return ""
}
func init() {
proto.RegisterType((*CodeGeneratorRequest)(nil), "google.protobuf.compiler.CodeGeneratorRequest")
proto.RegisterType((*CodeGeneratorResponse)(nil), "google.protobuf.compiler.CodeGeneratorResponse")
proto.RegisterType((*CodeGeneratorResponse_File)(nil), "google.protobuf.compiler.CodeGeneratorResponse.File")
}
var fileDescriptor0 = []byte{
// 311 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x91, 0xd1, 0x4a, 0xfb, 0x30,
0x14, 0xc6, 0xe9, 0xff, 0x3f, 0x91, 0x1d, 0x65, 0x93, 0x30, 0xa1, 0x8c, 0x5d, 0x94, 0xa1, 0xb8,
0xab, 0x14, 0x44, 0xf0, 0x7e, 0x13, 0xf5, 0xb2, 0x14, 0xaf, 0x04, 0x29, 0xb5, 0x3b, 0x2b, 0x81,
0x2e, 0x27, 0xa6, 0xe9, 0x13, 0xf9, 0x4e, 0x3e, 0x8f, 0x49, 0xda, 0x4e, 0x29, 0xee, 0xaa, 0x3d,
0xdf, 0xf9, 0xe5, 0x3b, 0x5f, 0x72, 0xe0, 0xba, 0x24, 0x2a, 0x2b, 0x8c, 0x95, 0x26, 0x43, 0xef,
0xcd, 0x2e, 0x2e, 0x68, 0xaf, 0x44, 0x85, 0x3a, 0x56, 0x55, 0x53, 0x0a, 0xc9, 0x7d, 0x83, 0x85,
0x2d, 0xc6, 0x7b, 0x8c, 0xf7, 0xd8, 0x3c, 0x1a, 0x1a, 0x6c, 0xb1, 0x2e, 0xb4, 0x50, 0x86, 0x74,
0x4b, 0x2f, 0x3f, 0x03, 0x98, 0x6d, 0x68, 0x8b, 0x4f, 0x28, 0x51, 0xe7, 0x56, 0x4f, 0xf1, 0xa3,
0xc1, 0xda, 0xb0, 0x15, 0x5c, 0xec, 0xac, 0x47, 0x66, 0x28, 0x2b, 0xdb, 0x1e, 0x86, 0x41, 0xf4,
0x7f, 0x35, 0x4e, 0x27, 0x4e, 0x7f, 0xa1, 0xee, 0x04, 0xb2, 0x05, 0x8c, 0x55, 0xae, 0xf3, 0x3d,
0x1a, 0xd4, 0xe1, 0xbf, 0x28, 0xb0, 0xc8, 0x8f, 0xc0, 0x36, 0x00, 0x7e, 0x52, 0xe6, 0x4e, 0x85,
0x53, 0xeb, 0x70, 0x76, 0x7b, 0xc5, 0x87, 0x89, 0x1f, 0x6d, 0xf3, 0xe1, 0x90, 0x2d, 0x71, 0xb2,
0x35, 0x71, 0x1f, 0xd7, 0x59, 0x7e, 0x05, 0x70, 0x39, 0x48, 0x59, 0x2b, 0x92, 0x35, 0xb2, 0x19,
0x9c, 0xa0, 0xd6, 0xa4, 0x6d, 0x36, 0x37, 0xb8, 0x2d, 0xd8, 0x33, 0x8c, 0x7e, 0x8d, 0xbb, 0xe3,
0xc7, 0x1e, 0x88, 0xff, 0x69, 0xea, 0xd3, 0xa4, 0xde, 0x61, 0xfe, 0x06, 0x23, 0x57, 0x31, 0x06,
0x23, 0x69, 0x6f, 0xd4, 0x8d, 0xf1, 0xff, 0xec, 0x06, 0xa6, 0xc2, 0xe2, 0xda, 0x08, 0x92, 0x99,
0x22, 0x21, 0x4d, 0x77, 0xfd, 0xc9, 0x41, 0x4e, 0x9c, 0xca, 0x42, 0x38, 0x2d, 0x48, 0x1a, 0xb4,
0xc0, 0xd4, 0x03, 0x7d, 0xb9, 0xbe, 0x87, 0x85, 0xcd, 0x72, 0x34, 0xdf, 0xfa, 0x3c, 0xf1, 0x8b,
0xf6, 0x0f, 0x52, 0xbf, 0x8e, 0xdb, 0xb5, 0x67, 0x25, 0x7d, 0x07, 0x00, 0x00, 0xff, 0xff, 0x83,
0x7b, 0x5c, 0x7c, 0x1b, 0x02, 0x00, 0x00,
}

View file

@ -1,83 +0,0 @@
// Code generated by protoc-gen-go.
// source: google/protobuf/compiler/plugin.proto
// DO NOT EDIT!
package google_protobuf_compiler
import proto "github.com/golang/protobuf/proto"
import "math"
import google_protobuf "github.com/golang/protobuf/protoc-gen-go/descriptor"
// Reference proto and math imports to suppress error if they are not otherwise used.
var _ = proto.GetString
var _ = math.Inf
type CodeGeneratorRequest struct {
FileToGenerate []string `protobuf:"bytes,1,rep,name=file_to_generate" json:"file_to_generate,omitempty"`
Parameter *string `protobuf:"bytes,2,opt,name=parameter" json:"parameter,omitempty"`
ProtoFile []*google_protobuf.FileDescriptorProto `protobuf:"bytes,15,rep,name=proto_file" json:"proto_file,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (this *CodeGeneratorRequest) Reset() { *this = CodeGeneratorRequest{} }
func (this *CodeGeneratorRequest) String() string { return proto.CompactTextString(this) }
func (*CodeGeneratorRequest) ProtoMessage() {}
func (this *CodeGeneratorRequest) GetParameter() string {
if this != nil && this.Parameter != nil {
return *this.Parameter
}
return ""
}
type CodeGeneratorResponse struct {
Error *string `protobuf:"bytes,1,opt,name=error" json:"error,omitempty"`
File []*CodeGeneratorResponse_File `protobuf:"bytes,15,rep,name=file" json:"file,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (this *CodeGeneratorResponse) Reset() { *this = CodeGeneratorResponse{} }
func (this *CodeGeneratorResponse) String() string { return proto.CompactTextString(this) }
func (*CodeGeneratorResponse) ProtoMessage() {}
func (this *CodeGeneratorResponse) GetError() string {
if this != nil && this.Error != nil {
return *this.Error
}
return ""
}
type CodeGeneratorResponse_File struct {
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
InsertionPoint *string `protobuf:"bytes,2,opt,name=insertion_point" json:"insertion_point,omitempty"`
Content *string `protobuf:"bytes,15,opt,name=content" json:"content,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (this *CodeGeneratorResponse_File) Reset() { *this = CodeGeneratorResponse_File{} }
func (this *CodeGeneratorResponse_File) String() string { return proto.CompactTextString(this) }
func (*CodeGeneratorResponse_File) ProtoMessage() {}
func (this *CodeGeneratorResponse_File) GetName() string {
if this != nil && this.Name != nil {
return *this.Name
}
return ""
}
func (this *CodeGeneratorResponse_File) GetInsertionPoint() string {
if this != nil && this.InsertionPoint != nil {
return *this.InsertionPoint
}
return ""
}
func (this *CodeGeneratorResponse_File) GetContent() string {
if this != nil && this.Content != nil {
return *this.Content
}
return ""
}
func init() {
}

View file

@ -1,73 +0,0 @@
# 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:
@echo run make test
include ../../Make.protobuf
test: golden testbuild
#test: golden testbuild extension_test
# ./extension_test
# @echo PASS
my_test/test.pb.go: my_test/test.proto
protoc --go_out=Mmulti/multi1.proto=github.com/golang/protobuf/protoc-gen-go/testdata/multi:. $<
golden:
make -B my_test/test.pb.go
sed -i '/return.*fileDescriptor/d' my_test/test.pb.go
sed -i '/^var fileDescriptor/,/^}/d' my_test/test.pb.go
sed -i '/proto.RegisterFile.*fileDescriptor/d' my_test/test.pb.go
gofmt -w my_test/test.pb.go
diff -w my_test/test.pb.go my_test/test.pb.go.golden
nuke: clean
testbuild: regenerate
go test
regenerate:
# Invoke protoc once to generate three independent .pb.go files in the same package.
protoc --go_out=. multi/multi{1,2,3}.proto
#extension_test: extension_test.$O
# $(LD) -L. -o $@ $<
#multi.a: multi3.pb.$O multi2.pb.$O multi1.pb.$O
# rm -f multi.a
# $(QUOTED_GOBIN)/gopack grc $@ $<
#test.pb.go: imp.pb.go
#multi1.pb.go: multi2.pb.go multi3.pb.go
#main.$O: imp.pb.$O test.pb.$O multi.a
#extension_test.$O: extension_base.pb.$O extension_extra.pb.$O extension_user.pb.$O

View file

@ -1,46 +0,0 @@
// 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.
syntax = "proto2";
package extension_base;
message BaseMessage {
optional int32 height = 1;
extensions 4 to 9;
extensions 16 to max;
}
// Another message that may be extended, using message_set_wire_format.
message OldStyleMessage {
option message_set_wire_format = true;
extensions 100 to max;
}

View file

@ -1,38 +0,0 @@
// 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.
syntax = "proto2";
package extension_extra;
message ExtraMessage {
optional int32 width = 1;
}

View file

@ -1,210 +0,0 @@
// 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.
// Test that we can use protocol buffers that use extensions.
package testdata
/*
import (
"bytes"
"regexp"
"testing"
"github.com/golang/protobuf/proto"
base "extension_base.pb"
user "extension_user.pb"
)
func TestSingleFieldExtension(t *testing.T) {
bm := &base.BaseMessage{
Height: proto.Int32(178),
}
// Use extension within scope of another type.
vol := proto.Uint32(11)
err := proto.SetExtension(bm, user.E_LoudMessage_Volume, vol)
if err != nil {
t.Fatal("Failed setting extension:", err)
}
buf, err := proto.Marshal(bm)
if err != nil {
t.Fatal("Failed encoding message with extension:", err)
}
bm_new := new(base.BaseMessage)
if err := proto.Unmarshal(buf, bm_new); err != nil {
t.Fatal("Failed decoding message with extension:", err)
}
if !proto.HasExtension(bm_new, user.E_LoudMessage_Volume) {
t.Fatal("Decoded message didn't contain extension.")
}
vol_out, err := proto.GetExtension(bm_new, user.E_LoudMessage_Volume)
if err != nil {
t.Fatal("Failed getting extension:", err)
}
if v := vol_out.(*uint32); *v != *vol {
t.Errorf("vol_out = %v, expected %v", *v, *vol)
}
proto.ClearExtension(bm_new, user.E_LoudMessage_Volume)
if proto.HasExtension(bm_new, user.E_LoudMessage_Volume) {
t.Fatal("Failed clearing extension.")
}
}
func TestMessageExtension(t *testing.T) {
bm := &base.BaseMessage{
Height: proto.Int32(179),
}
// Use extension that is itself a message.
um := &user.UserMessage{
Name: proto.String("Dave"),
Rank: proto.String("Major"),
}
err := proto.SetExtension(bm, user.E_LoginMessage_UserMessage, um)
if err != nil {
t.Fatal("Failed setting extension:", err)
}
buf, err := proto.Marshal(bm)
if err != nil {
t.Fatal("Failed encoding message with extension:", err)
}
bm_new := new(base.BaseMessage)
if err := proto.Unmarshal(buf, bm_new); err != nil {
t.Fatal("Failed decoding message with extension:", err)
}
if !proto.HasExtension(bm_new, user.E_LoginMessage_UserMessage) {
t.Fatal("Decoded message didn't contain extension.")
}
um_out, err := proto.GetExtension(bm_new, user.E_LoginMessage_UserMessage)
if err != nil {
t.Fatal("Failed getting extension:", err)
}
if n := um_out.(*user.UserMessage).Name; *n != *um.Name {
t.Errorf("um_out.Name = %q, expected %q", *n, *um.Name)
}
if r := um_out.(*user.UserMessage).Rank; *r != *um.Rank {
t.Errorf("um_out.Rank = %q, expected %q", *r, *um.Rank)
}
proto.ClearExtension(bm_new, user.E_LoginMessage_UserMessage)
if proto.HasExtension(bm_new, user.E_LoginMessage_UserMessage) {
t.Fatal("Failed clearing extension.")
}
}
func TestTopLevelExtension(t *testing.T) {
bm := &base.BaseMessage{
Height: proto.Int32(179),
}
width := proto.Int32(17)
err := proto.SetExtension(bm, user.E_Width, width)
if err != nil {
t.Fatal("Failed setting extension:", err)
}
buf, err := proto.Marshal(bm)
if err != nil {
t.Fatal("Failed encoding message with extension:", err)
}
bm_new := new(base.BaseMessage)
if err := proto.Unmarshal(buf, bm_new); err != nil {
t.Fatal("Failed decoding message with extension:", err)
}
if !proto.HasExtension(bm_new, user.E_Width) {
t.Fatal("Decoded message didn't contain extension.")
}
width_out, err := proto.GetExtension(bm_new, user.E_Width)
if err != nil {
t.Fatal("Failed getting extension:", err)
}
if w := width_out.(*int32); *w != *width {
t.Errorf("width_out = %v, expected %v", *w, *width)
}
proto.ClearExtension(bm_new, user.E_Width)
if proto.HasExtension(bm_new, user.E_Width) {
t.Fatal("Failed clearing extension.")
}
}
func TestMessageSetWireFormat(t *testing.T) {
osm := new(base.OldStyleMessage)
osp := &user.OldStyleParcel{
Name: proto.String("Dave"),
Height: proto.Int32(178),
}
err := proto.SetExtension(osm, user.E_OldStyleParcel_MessageSetExtension, osp)
if err != nil {
t.Fatal("Failed setting extension:", err)
}
buf, err := proto.Marshal(osm)
if err != nil {
t.Fatal("Failed encoding message:", err)
}
// Data generated from Python implementation.
expected := []byte{
11, 16, 209, 15, 26, 9, 10, 4, 68, 97, 118, 101, 16, 178, 1, 12,
}
if !bytes.Equal(expected, buf) {
t.Errorf("Encoding mismatch.\nwant %+v\n got %+v", expected, buf)
}
// Check that it is restored correctly.
osm = new(base.OldStyleMessage)
if err := proto.Unmarshal(buf, osm); err != nil {
t.Fatal("Failed decoding message:", err)
}
osp_out, err := proto.GetExtension(osm, user.E_OldStyleParcel_MessageSetExtension)
if err != nil {
t.Fatal("Failed getting extension:", err)
}
osp = osp_out.(*user.OldStyleParcel)
if *osp.Name != "Dave" || *osp.Height != 178 {
t.Errorf("Retrieved extension from decoded message is not correct: %+v", osp)
}
}
func main() {
// simpler than rigging up gotest
testing.Main(regexp.MatchString, []testing.InternalTest{
{"TestSingleFieldExtension", TestSingleFieldExtension},
{"TestMessageExtension", TestMessageExtension},
{"TestTopLevelExtension", TestTopLevelExtension},
},
[]testing.InternalBenchmark{},
[]testing.InternalExample{})
}
*/

View file

@ -1,100 +0,0 @@
// 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.
syntax = "proto2";
import "extension_base.proto";
import "extension_extra.proto";
package extension_user;
message UserMessage {
optional string name = 1;
optional string rank = 2;
}
// Extend with a message
extend extension_base.BaseMessage {
optional UserMessage user_message = 5;
}
// Extend with a foreign message
extend extension_base.BaseMessage {
optional extension_extra.ExtraMessage extra_message = 9;
}
// Extend with some primitive types
extend extension_base.BaseMessage {
optional int32 width = 6;
optional int64 area = 7;
}
// Extend inside the scope of another type
message LoudMessage {
extend extension_base.BaseMessage {
optional uint32 volume = 8;
}
extensions 100 to max;
}
// Extend inside the scope of another type, using a message.
message LoginMessage {
extend extension_base.BaseMessage {
optional UserMessage user_message = 16;
}
}
// Extend with a repeated field
extend extension_base.BaseMessage {
repeated Detail detail = 17;
}
message Detail {
optional string color = 1;
}
// An extension of an extension
message Announcement {
optional string words = 1;
extend LoudMessage {
optional Announcement loud_ext = 100;
}
}
// Something that can be put in a message set.
message OldStyleParcel {
extend extension_base.OldStyleMessage {
optional OldStyleParcel message_set_extension = 2001;
}
required string name = 1;
optional int32 height = 2;
}

View file

@ -1,59 +0,0 @@
// 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 grpc.testing;
message SimpleRequest {
}
message SimpleResponse {
}
message StreamMsg {
}
message StreamMsg2 {
}
service Test {
rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
// This RPC streams from the server only.
rpc Downstream(SimpleRequest) returns (stream StreamMsg);
// This RPC streams from the client.
rpc Upstream(stream StreamMsg) returns (SimpleResponse);
// This one streams in both directions.
rpc Bidi(stream StreamMsg) returns (stream StreamMsg2);
}

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