mirror of
https://github.com/netbirdio/dex.git
synced 2026-05-22 18:43:53 -07:00
vendor: revendor
This commit is contained in:
Generated
+6
-10
@@ -1,12 +1,12 @@
|
||||
hash: bc7fa6bbfddcb39710064a9d6dab090d49bd88486571cae12c68c4b70093e716
|
||||
updated: 2016-11-03T14:31:37.323302694-07:00
|
||||
hash: c3530f2a60a64c2efc4c3ac499fcd15f79de2a532715ba2b9841c1d404942b2e
|
||||
updated: 2016-11-17T15:18:56.701287533-08:00
|
||||
imports:
|
||||
- name: github.com/cockroachdb/cockroach-go
|
||||
version: 31611c0501c812f437d4861d87d117053967c955
|
||||
subpackages:
|
||||
- crdb
|
||||
- name: github.com/ericchiang/oidc
|
||||
version: 1907f0e61549f9081f26bdf269f11603496c9dee
|
||||
- name: github.com/coreos/go-oidc
|
||||
version: 5a7f09ab5787e846efa7f56f4a08b6d6926d08c4
|
||||
- name: github.com/ghodss/yaml
|
||||
version: bea76d6a4713e18b7f5321a2b020738552def3ea
|
||||
- name: github.com/go-sql-driver/mysql
|
||||
@@ -52,6 +52,7 @@ imports:
|
||||
version: 6a513affb38dc9788b449d59ffed099b8de18fa0
|
||||
subpackages:
|
||||
- context
|
||||
- context/ctxhttp
|
||||
- http2
|
||||
- http2/hpack
|
||||
- internal/timeseries
|
||||
@@ -87,13 +88,8 @@ imports:
|
||||
version: 4e86f4367175e39f69d9358a5f17b4dda270378d
|
||||
- name: gopkg.in/ldap.v2
|
||||
version: 0e7db8eb77695b5a952f0e5d78df9ab160050c73
|
||||
- name: gopkg.in/square/go-jose.v1
|
||||
version: e3f973b66b91445ec816dd7411ad1b6495a5a2fc
|
||||
subpackages:
|
||||
- cipher
|
||||
- json
|
||||
- name: gopkg.in/square/go-jose.v2
|
||||
version: f209f41628247c56938cb20ef51d589ddad6c30b
|
||||
version: 8c5257b2f658f86d174ae68c6a592eaf6a9608d9
|
||||
subpackages:
|
||||
- cipher
|
||||
- json
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
/bin
|
||||
/gopath
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.7.3
|
||||
- 1.6.3
|
||||
|
||||
install:
|
||||
- go get -v -t github.com/coreos/go-oidc
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/golang/lint/golint
|
||||
|
||||
script:
|
||||
- ./test
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
# How to Contribute
|
||||
|
||||
CoreOS projects are [Apache 2.0 licensed](LICENSE) and accept contributions via
|
||||
GitHub pull requests. This document outlines some of the conventions on
|
||||
development workflow, commit message formatting, contact points and other
|
||||
resources to make it easier to get your contribution accepted.
|
||||
|
||||
# Certificate of Origin
|
||||
|
||||
By contributing to this project you agree to the Developer Certificate of
|
||||
Origin (DCO). This document was created by the Linux Kernel community and is a
|
||||
simple statement that you, as a contributor, have the legal right to make the
|
||||
contribution. See the [DCO](DCO) file for details.
|
||||
|
||||
# Email and Chat
|
||||
|
||||
The project currently uses the general CoreOS email list and IRC channel:
|
||||
- Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev)
|
||||
- IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org
|
||||
|
||||
Please avoid emailing maintainers found in the MAINTAINERS file directly. They
|
||||
are very busy and read the mailing lists.
|
||||
|
||||
## Getting Started
|
||||
|
||||
- Fork the repository on GitHub
|
||||
- Read the [README](README.md) for build and test instructions
|
||||
- Play with the project, submit bugs, submit patches!
|
||||
|
||||
## Contribution Flow
|
||||
|
||||
This is a rough outline of what a contributor's workflow looks like:
|
||||
|
||||
- Create a topic branch from where you want to base your work (usually master).
|
||||
- Make commits of logical units.
|
||||
- Make sure your commit messages are in the proper format (see below).
|
||||
- Push your changes to a topic branch in your fork of the repository.
|
||||
- Make sure the tests pass, and add any new tests as appropriate.
|
||||
- Submit a pull request to the original repository.
|
||||
|
||||
Thanks for your contributions!
|
||||
|
||||
### Format of the Commit Message
|
||||
|
||||
We follow a rough convention for commit messages that is designed to answer two
|
||||
questions: what changed and why. The subject line should feature the what and
|
||||
the body of the commit should describe the why.
|
||||
|
||||
```
|
||||
scripts: add the test-cluster command
|
||||
|
||||
this uses tmux to setup a test cluster that you can easily kill and
|
||||
start for debugging.
|
||||
|
||||
Fixes #38
|
||||
```
|
||||
|
||||
The format can be described more formally as follows:
|
||||
|
||||
```
|
||||
<subsystem>: <what changed>
|
||||
<BLANK LINE>
|
||||
<why this change was made>
|
||||
<BLANK LINE>
|
||||
<footer>
|
||||
```
|
||||
|
||||
The first line is the subject and should be no longer than 70 characters, the
|
||||
second line is always blank, and other lines should be wrapped at 80 characters.
|
||||
This allows the message to be easier to read on GitHub as well as in various
|
||||
git tools.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
660 York Street, Suite 102,
|
||||
San Francisco, CA 94110 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
Bobby Rullo <bobby.rullo@coreos.com> (@bobbyrullo)
|
||||
Ed Rooth <ed.rooth@coreos.com> (@sym3tri)
|
||||
Eric Chiang <eric.chiang@coreos.com> (@ericchiang)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
CoreOS Project
|
||||
Copyright 2014 CoreOS, Inc
|
||||
|
||||
This product includes software developed at CoreOS, Inc.
|
||||
(http://www.coreos.com/).
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
# go-oidc
|
||||
|
||||
[](https://godoc.org/github.com/coreos/go-oidc)
|
||||
[](https://travis-ci.org/coreos/go-oidc)
|
||||
|
||||
## OpenID Connect support for Go
|
||||
|
||||
This package enables OpenID Connect support for the [golang.org/x/oauth2](https://godoc.org/golang.org/x/oauth2) package.
|
||||
|
||||
```go
|
||||
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// Configure an OpenID Connect aware OAuth2 client.
|
||||
oauth2Config := oauth2.Config{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectURL,
|
||||
|
||||
// Discovery returns the OAuth2 endpoints.
|
||||
Endpoint: provider.Endpoint(),
|
||||
|
||||
// "openid" is a required scope for OpenID Connect flows.
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
}
|
||||
```
|
||||
|
||||
OAuth2 redirects are unchanged.
|
||||
|
||||
```go
|
||||
func handleRedirect(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusFound)
|
||||
}
|
||||
```
|
||||
|
||||
The on responses, the provider can be used to verify ID Tokens.
|
||||
|
||||
```go
|
||||
var verifier = provider.Verifier()
|
||||
|
||||
func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify state and errors.
|
||||
|
||||
oauth2Token, err := oauth2Config.Exchange(ctx, r.URL.Query().Get("code"))
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// Extract the ID Token from OAuth2 token.
|
||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
// handle missing token
|
||||
}
|
||||
|
||||
// Parse and verify ID Token payload.
|
||||
idToken, err := verifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// Extract custom claims
|
||||
var claims struct {
|
||||
Email string `json:"email"`
|
||||
Verified bool `json:"email_verified"`
|
||||
}
|
||||
if err := idToken.Claims(&claims); err != nil {
|
||||
// handle error
|
||||
}
|
||||
}
|
||||
```
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# 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
|
||||
Generated
Vendored
+3
-3
@@ -9,7 +9,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/ericchiang/oidc"
|
||||
oidc "github.com/coreos/go-oidc"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
@@ -27,7 +27,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
verifier := provider.NewVerifier(ctx)
|
||||
verifier := provider.Verifier()
|
||||
|
||||
config := oauth2.Config{
|
||||
ClientID: clientID,
|
||||
@@ -59,7 +59,7 @@ func main() {
|
||||
http.Error(w, "No id_token field in oauth2 token.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
idToken, err := verifier.Verify(rawIDToken)
|
||||
idToken, err := verifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
Generated
Vendored
+3
-3
@@ -10,7 +10,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/ericchiang/oidc"
|
||||
oidc "github.com/coreos/go-oidc"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
@@ -42,7 +42,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Use the nonce source to create a custom ID Token verifier.
|
||||
nonceEnabledVerifier := provider.NewVerifier(ctx, oidc.VerifyNonce(nonceSource{}))
|
||||
nonceEnabledVerifier := provider.Verifier(oidc.VerifyNonce(nonceSource{}))
|
||||
|
||||
config := oauth2.Config{
|
||||
ClientID: clientID,
|
||||
@@ -76,7 +76,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
// Verify the ID Token signature and nonce.
|
||||
idToken, err := nonceEnabledVerifier.Verify(rawIDToken)
|
||||
idToken, err := nonceEnabledVerifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
Generated
Vendored
+1
-1
@@ -9,7 +9,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/ericchiang/oidc"
|
||||
oidc "github.com/coreos/go-oidc"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
// +build ignore
|
||||
|
||||
// This file is used to generate keys for tests.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"text/template"
|
||||
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
type key struct {
|
||||
name string
|
||||
new func() (crypto.Signer, error)
|
||||
}
|
||||
|
||||
var keys = []key{
|
||||
{
|
||||
"ECDSA_256", func() (crypto.Signer, error) {
|
||||
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
},
|
||||
},
|
||||
{
|
||||
"ECDSA_384", func() (crypto.Signer, error) {
|
||||
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
},
|
||||
},
|
||||
{
|
||||
"ECDSA_521", func() (crypto.Signer, error) {
|
||||
return ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||
},
|
||||
},
|
||||
{
|
||||
"RSA_1024", func() (crypto.Signer, error) {
|
||||
return rsa.GenerateKey(rand.Reader, 1024)
|
||||
},
|
||||
},
|
||||
{
|
||||
"RSA_2048", func() (crypto.Signer, error) {
|
||||
return rsa.GenerateKey(rand.Reader, 2048)
|
||||
},
|
||||
},
|
||||
{
|
||||
"RSA_4096", func() (crypto.Signer, error) {
|
||||
return rsa.GenerateKey(rand.Reader, 4096)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func newJWK(k key, prefix, ident string) (privBytes, pubBytes []byte, err error) {
|
||||
priv, err := k.new()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("generate %s: %v", k.name, err)
|
||||
}
|
||||
pub := priv.Public()
|
||||
|
||||
privKey := &jose.JSONWebKey{Key: priv}
|
||||
thumbprint, err := privKey.Thumbprint(crypto.SHA256)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("computing thumbprint: %v", err)
|
||||
}
|
||||
|
||||
keyID := hex.EncodeToString(thumbprint)
|
||||
privKey.KeyID = keyID
|
||||
pubKey := &jose.JSONWebKey{Key: pub, KeyID: keyID}
|
||||
|
||||
privBytes, err = json.MarshalIndent(privKey, prefix, ident)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pubBytes, err = json.MarshalIndent(pubKey, prefix, ident)
|
||||
return
|
||||
}
|
||||
|
||||
type keyData struct {
|
||||
Name string
|
||||
Priv string
|
||||
Pub string
|
||||
}
|
||||
|
||||
var tmpl = template.Must(template.New("").Parse(`// +build !golint
|
||||
|
||||
// This file contains statically created JWKs for tests created by gen.go
|
||||
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
jose "gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
func mustLoadJWK(s string) jose.JSONWebKey {
|
||||
var jwk jose.JSONWebKey
|
||||
if err := json.Unmarshal([]byte(s), &jwk); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return jwk
|
||||
}
|
||||
|
||||
var (
|
||||
{{- range $i, $key := .Keys }}
|
||||
testKey{{ $key.Name }} = mustLoadJWK(` + "`" + `{{ $key.Pub }}` + "`" + `)
|
||||
testKey{{ $key.Name }}_Priv = mustLoadJWK(` + "`" + `{{ $key.Priv }}` + "`" + `)
|
||||
{{ end -}}
|
||||
)
|
||||
`))
|
||||
|
||||
func main() {
|
||||
var tmplData struct {
|
||||
Keys []keyData
|
||||
}
|
||||
for _, k := range keys {
|
||||
for i := 0; i < 4; i++ {
|
||||
log.Printf("generating %s", k.name)
|
||||
priv, pub, err := newJWK(k, "\t", "\t")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
name := fmt.Sprintf("%s_%d", k.name, i)
|
||||
|
||||
tmplData.Keys = append(tmplData.Keys, keyData{
|
||||
Name: name,
|
||||
Priv: string(priv),
|
||||
Pub: string(pub),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
buff := new(bytes.Buffer)
|
||||
if err := tmpl.Execute(buff, tmplData); err != nil {
|
||||
log.Fatalf("excuting template: %v", err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile("jose_test.go", buff.Bytes(), 0644); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package http
|
||||
|
||||
import "net/http"
|
||||
|
||||
type Client interface {
|
||||
Do(*http.Request) (*http.Response, error)
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package http is DEPRECATED. Use net/http instead.
|
||||
package http
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
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
|
||||
}
|
||||
+380
@@ -0,0 +1,380 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
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
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user