diff --git a/.gitignore b/.gitignore index f1c181e..e413f46 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out + +**/__debug_bin +.vscode diff --git a/docs.go b/docs.go deleted file mode 100644 index 1df929b..0000000 --- a/docs.go +++ /dev/null @@ -1 +0,0 @@ -package oidc diff --git a/example/client/api/api.go b/example/client/api/api.go new file mode 100644 index 0000000..0f6ace6 --- /dev/null +++ b/example/client/api/api.go @@ -0,0 +1,90 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" + + "github.com/caos/go-oidc/pkg/oidc" + "github.com/caos/go-oidc/pkg/oidc/defaults" + "github.com/caos/utils/logging" +) + +const ( + publicURL string = "/public" + protectedURL string = "/protected" + protectedExchangeURL string = "/protected/exchange" +) + +func main() { + clientID := os.Getenv("CLIENT_ID") + clientSecret := os.Getenv("CLIENT_SECRET") + issuer := os.Getenv("ISSUER") + port := os.Getenv("PORT") + + // ctx := context.Background() + + providerConfig := &oidc.ProviderConfig{ + ClientID: clientID, + ClientSecret: clientSecret, + Issuer: issuer, + } + provider, err := defaults.NewDefaultProvider(providerConfig) + logging.Log("APP-nx6PeF").OnError(err).Panic("error creating provider") + + http.HandleFunc(publicURL, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("OK")) + }) + + http.HandleFunc(protectedURL, func(w http.ResponseWriter, r *http.Request) { + ok, token := checkToken(w, r) + if !ok { + return + } + resp, err := provider.Introspect(r.Context(), token) + if err != nil { + http.Error(w, err.Error(), http.StatusForbidden) + return + } + data, err := json.Marshal(resp) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Write(data) + }) + + http.HandleFunc(protectedExchangeURL, func(w http.ResponseWriter, r *http.Request) { + ok, token := checkToken(w, r) + if !ok { + return + } + tokens, err := provider.DelegationTokenExchange(r.Context(), token, oidc.WithResource([]string{"Test"})) + if err != nil { + http.Error(w, "failed to exchange token: "+err.Error(), http.StatusUnauthorized) + return + } + + data, err := json.Marshal(tokens) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Write(data) + }) + + lis := fmt.Sprintf("127.0.0.1:%s", port) + log.Printf("listening on http://%s/", lis) + log.Fatal(http.ListenAndServe(lis, nil)) +} + +func checkToken(w http.ResponseWriter, r *http.Request) (bool, string) { + token := r.Header.Get("authorization") + if token == "" { + http.Error(w, "Auth header missing", http.StatusUnauthorized) + return false, "" + } + return true, token +} diff --git a/example/client/app/app.go b/example/client/app/app.go new file mode 100644 index 0000000..137d015 --- /dev/null +++ b/example/client/app/app.go @@ -0,0 +1,95 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + + "github.com/google/uuid" + + "github.com/caos/oidc/pkg/client" + "github.com/caos/oidc/pkg/client/defaults" + "github.com/caos/oidc/pkg/oidc" + "github.com/caos/oidc/pkg/utils" + "github.com/caos/utils/logging" +) + +var ( + callbackPath string = "/auth/callback" + hashKey []byte = []byte("test") +) + +func main() { + clientID := os.Getenv("CLIENT_ID") + clientSecret := os.Getenv("CLIENT_SECRET") + issuer := os.Getenv("ISSUER") + port := os.Getenv("PORT") + + ctx := context.Background() + + rpConfig := &client.RelayingPartyConfig{ + ClientID: clientID, + ClientSecret: clientSecret, + Issuer: issuer, + CallbackURL: fmt.Sprintf("http://localhost:%v%v", port, callbackPath), + Scopes: []string{"openid", "profile", "email"}, + } + cookieHandler := utils.NewCookieHandler(hashKey, nil, utils.WithUnsecure()) + provider, err := defaults.NewDefaultRelayingParty(rpConfig, defaults.WithCookieHandler(cookieHandler)) + logging.Log("APP-nx6PeF").OnError(err).Panic("error creating provider") + + // state := "foobar" + state := uuid.New().String() + + http.Handle("/login", provider.AuthURLHandler(state)) + // http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { + // http.Redirect(w, r, provider.AuthURL(state), http.StatusFound) + // }) + + // http.HandleFunc(callbackPath, func(w http.ResponseWriter, r *http.Request) { + // tokens, err := provider.CodeExchange(ctx, r.URL.Query().Get("code")) + // if err != nil { + // http.Error(w, "failed to exchange token: "+err.Error(), http.StatusUnauthorized) + // return + // } + // data, err := json.Marshal(tokens) + // if err != nil { + // http.Error(w, err.Error(), http.StatusInternalServerError) + // return + // } + // w.Write(data) + // }) + + var marshal = func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens, state string) { + _ = state + data, err := json.Marshal(tokens) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Write(data) + } + + http.Handle(callbackPath, provider.CodeExchangeHandler(marshal)) + + http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) { + tokens, err := provider.ClientCredentials(ctx, "urn:abraxas:iam:audience_client_id:TM-V3") + if err != nil { + http.Error(w, "failed to exchange token: "+err.Error(), http.StatusUnauthorized) + return + } + + data, err := json.Marshal(tokens) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Write(data) + }) + lis := fmt.Sprintf("127.0.0.1:%s", port) + logging.Log("APP-upQxIG").Infof("listening on http://%s/", lis) + log.Fatal(http.ListenAndServe("127.0.0.1:5556", nil)) +} diff --git a/example/doc.go b/example/doc.go new file mode 100644 index 0000000..f7ec372 --- /dev/null +++ b/example/doc.go @@ -0,0 +1 @@ +package example diff --git a/example/go.mod b/example/go.mod new file mode 100644 index 0000000..8fc9e66 --- /dev/null +++ b/example/go.mod @@ -0,0 +1,22 @@ +module github.com/caos/oidc/example + +go 1.13 + +replace github.com/caos/oidc => /Users/livio/workspaces/go/src/github.com/caos/oidc + +replace github.com/caos/oidc/pkg/oidc => /Users/livio/workspaces/go/src/github.com/caos/oidc/pkg/oidc + +replace github.com/caos/oidc/pkg/server => /Users/livio/workspaces/go/src/github.com/caos/oidc/pkg/server + +replace github.com/caos/oidc/pkg/client => /Users/livio/workspaces/go/src/github.com/caos/oidc/pkg/client + +replace github.com/caos/oidc/pkg/utils => /Users/livio/workspaces/go/src/github.com/caos/oidc/pkg/utils + +require ( + github.com/caos/oidc/pkg/client v0.0.0-00010101000000-000000000000 + github.com/caos/oidc/pkg/oidc v0.0.0-00010101000000-000000000000 + github.com/caos/oidc/pkg/server v0.0.0-00010101000000-000000000000 + github.com/caos/oidc/pkg/utils v0.0.0-00010101000000-000000000000 + github.com/caos/utils/logging v0.0.0-20191104132131-b318678afbef + github.com/google/uuid v1.1.1 +) diff --git a/example/go.sum b/example/go.sum new file mode 100644 index 0000000..4f9df56 --- /dev/null +++ b/example/go.sum @@ -0,0 +1,128 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/caos/go-oidc v2.0.0+incompatible h1:/DthUByy4DOIcENkqpCoFvz6Bv9N3+EwzlKa5fpDRXA= +github.com/caos/utils v0.0.0-20191104132131-b318678afbef h1:/a781PnuLvuTOj0PEJ7ByhgqjpC30Jsk+11uGcuxjjA= +github.com/caos/utils v0.0.0-20191104132131-b318678afbef/go.mod h1:m66FVMc4qkzUaWkRP1acVnYKagyTr9uGpvhCWvnlJoE= +github.com/caos/utils/config v0.0.0-20191002113340-78986eef31d3/go.mod h1:4iI2a+qaiRFiLV1RAPG5dLp67M+NP2832toQbG9Uu74= +github.com/caos/utils/logging v0.0.0-20191104132131-b318678afbef h1:ZkyR2deIvTjvULYw6bInjRR4YNfktQ9F6/z0VijtfmU= +github.com/caos/utils/logging v0.0.0-20191104132131-b318678afbef/go.mod h1:1QHTbh4VS/6qN5fOApyEa70ESW+nDri3XE7j3oIzbyU= +github.com/caos/utils/pairs v0.0.0-20191002113340-78986eef31d3/go.mod h1:UZHeoVF6vhET4wTA/alcu0KOy65P2WI3AFlMvThGDcE= +github.com/caos/utils/pairs v0.0.0-20191104132131-b318678afbef h1:wpkm8hj2qNBOchILsfjLAqMWS1a4C+/7E4RM/NQ7T2U= +github.com/caos/utils/pairs v0.0.0-20191104132131-b318678afbef/go.mod h1:UZHeoVF6vhET4wTA/alcu0KOy65P2WI3AFlMvThGDcE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= +github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/grpc-ecosystem/grpc-gateway v1.11.3/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.12.1 h1:zCy2xE9ablevUOrUZc3Dl72Dt+ya2FNAvC2yLYMHzi4= +github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f h1:kz4KIr+xcPUsI3VMoqWfPMvtnJ6MGfiVwsWSVzphMO4= +golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 h1:MlY3mEfbnWGmUi4rtHOtNnnnN4UJRGSyLPx+DXA5Sq4= +golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2 h1:wAW1U21MfVN0sUipAD8952TBjGXMRHFKQugDlQ9RwwE= +golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 h1:51D++eCgOHufw5VfDE9Uzqyyc+OyQIjb9hkYy9LN5Fk= +google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A= +gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/go.mod b/go.mod deleted file mode 100644 index 08b24b6..0000000 --- a/go.mod +++ /dev/null @@ -1,12 +0,0 @@ -module github.com/caos/oidc - -go 1.13 - -require ( - github.com/caos/utils/logging v0.0.0-20191104132131-b318678afbef - github.com/gorilla/mux v1.7.3 - github.com/stretchr/testify v1.4.0 - golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 - golang.org/x/text v0.3.2 - gopkg.in/square/go-jose.v2 v2.4.0 -) diff --git a/pkg/oidc/go.mod b/pkg/oidc/go.mod new file mode 100644 index 0000000..8508ee6 --- /dev/null +++ b/pkg/oidc/go.mod @@ -0,0 +1,14 @@ +module github.com/caos/oidc/pkg/oidc + +go 1.13 + +require ( + github.com/golang/protobuf v1.3.2 // indirect + github.com/stretchr/testify v1.4.0 // indirect + golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f // indirect + golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 // indirect + golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 + golang.org/x/text v0.3.2 + google.golang.org/appengine v1.6.5 // indirect + gopkg.in/square/go-jose.v2 v2.4.0 +) diff --git a/pkg/oidc/go.sum b/pkg/oidc/go.sum new file mode 100644 index 0000000..2ca842b --- /dev/null +++ b/pkg/oidc/go.sum @@ -0,0 +1,43 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f h1:kz4KIr+xcPUsI3VMoqWfPMvtnJ6MGfiVwsWSVzphMO4= +golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 h1:MlY3mEfbnWGmUi4rtHOtNnnnN4UJRGSyLPx+DXA5Sq4= +golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A= +gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/oidc/client_credentials.go b/pkg/oidc/grants/client_credentials.go similarity index 98% rename from pkg/oidc/client_credentials.go rename to pkg/oidc/grants/client_credentials.go index 7a9b074..998dda1 100644 --- a/pkg/oidc/client_credentials.go +++ b/pkg/oidc/grants/client_credentials.go @@ -1,4 +1,4 @@ -package oidc +package grants import "strings" diff --git a/pkg/oidc/grants/tokenexchange/tokenexchange.go b/pkg/oidc/grants/tokenexchange/tokenexchange.go new file mode 100644 index 0000000..02a9808 --- /dev/null +++ b/pkg/oidc/grants/tokenexchange/tokenexchange.go @@ -0,0 +1,75 @@ +package tokenexchange + +const ( + AccessTokenType = "urn:ietf:params:oauth:token-type:access_token" + RefreshTokenType = "urn:ietf:params:oauth:token-type:refresh_token" + IDTokenType = "urn:ietf:params:oauth:token-type:id_token" + JWTTokenType = "urn:ietf:params:oauth:token-type:jwt" + DelegationTokenType = AccessTokenType + + TokenExchangeGrantType = "urn:ietf:params:oauth:grant-type:token-exchange" +) + +type TokenExchangeRequest struct { + grantType string `schema:"grant_type"` + subjectToken string `schema:"subject_token"` + subjectTokenType string `schema:"subject_token_type"` + actorToken string `schema:"actor_token"` + actorTokenType string `schema:"actor_token_type"` + resource []string `schema:"resource"` + audience []string `schema:"audience"` + scope []string `schema:"scope"` + requestedTokenType string `schema:"requested_token_type"` +} + +func NewTokenExchangeRequest(subjectToken, subjectTokenType string, opts ...TokenExchangeOption) *TokenExchangeRequest { + t := &TokenExchangeRequest{ + grantType: TokenExchangeGrantType, + subjectToken: subjectToken, + subjectTokenType: subjectTokenType, + requestedTokenType: AccessTokenType, + } + for _, opt := range opts { + opt(t) + } + return t +} + +type TokenExchangeOption func(*TokenExchangeRequest) + +func WithActorToken(token, tokenType string) func(*TokenExchangeRequest) { + return func(req *TokenExchangeRequest) { + req.actorToken = token + req.actorTokenType = tokenType + } +} + +func WithAudience(audience []string) func(*TokenExchangeRequest) { + return func(req *TokenExchangeRequest) { + req.audience = audience + } +} + +func WithGrantType(grantType string) TokenExchangeOption { + return func(req *TokenExchangeRequest) { + req.grantType = grantType + } +} + +func WithRequestedTokenType(tokenType string) func(*TokenExchangeRequest) { + return func(req *TokenExchangeRequest) { + req.requestedTokenType = tokenType + } +} + +func WithResource(resource []string) func(*TokenExchangeRequest) { + return func(req *TokenExchangeRequest) { + req.resource = resource + } +} + +func WithScope(scope []string) func(*TokenExchangeRequest) { + return func(req *TokenExchangeRequest) { + req.scope = scope + } +} diff --git a/pkg/oidc/identity_provider.go b/pkg/oidc/identity_provider.go new file mode 100644 index 0000000..6ebfff0 --- /dev/null +++ b/pkg/oidc/identity_provider.go @@ -0,0 +1,13 @@ +package oidc + +import "net/http" + +type IdentityProvider interface { + // Configuration + // Storage() Storage + HandleDiscovery(w http.ResponseWriter, r *http.Request) + HandleAuthorize(w http.ResponseWriter, r *http.Request) + HandleExchange(w http.ResponseWriter, r *http.Request) + HandleUserinfo(w http.ResponseWriter, r *http.Request) + HttpHandler() *http.Server +} diff --git a/pkg/oidc/keyset.go b/pkg/oidc/keyset.go new file mode 100644 index 0000000..f9bed2f --- /dev/null +++ b/pkg/oidc/keyset.go @@ -0,0 +1,22 @@ +package oidc + +import ( + "context" + + "gopkg.in/square/go-jose.v2" +) + +// KeySet is a set of publc JSON Web Keys that can be used to validate the signature +// of JSON web tokens. This is expected to be backed by a remote key set through +// provider metadata discovery or an in-memory set of keys delivered out-of-band. +type KeySet interface { + // VerifySignature parses the JSON web token, verifies the signature, and returns + // the raw payload. Header and claim fields are validated by other parts of the + // package. For example, the KeySet does not need to check values such as signature + // algorithm, issuer, and audience since the IDTokenVerifier validates these values + // independently. + // + // If VerifySignature makes HTTP requests to verify the token, it's expected to + // use any HTTP client associated with the context through ClientContext. + VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) (payload []byte, err error) +} diff --git a/pkg/server/authrequest.go b/pkg/op/authrequest.go similarity index 100% rename from pkg/server/authrequest.go rename to pkg/op/authrequest.go diff --git a/pkg/server/config.go b/pkg/op/config.go similarity index 100% rename from pkg/server/config.go rename to pkg/op/config.go diff --git a/pkg/server/default_handler.go b/pkg/op/default_handler.go similarity index 100% rename from pkg/server/default_handler.go rename to pkg/op/default_handler.go diff --git a/pkg/server/default_handler_test.go b/pkg/op/default_handler_test.go similarity index 100% rename from pkg/server/default_handler_test.go rename to pkg/op/default_handler_test.go diff --git a/pkg/op/go.mod b/pkg/op/go.mod new file mode 100644 index 0000000..95e41cf --- /dev/null +++ b/pkg/op/go.mod @@ -0,0 +1,16 @@ +module github.com/caos/oidc/pkg/server + +go 1.13 + +replace github.com/caos/oidc => /Users/livio/workspaces/go/src/github.com/caos/oidc + +require ( + github.com/caos/oidc v0.0.0-20191119072320-6412f213450c + github.com/caos/utils/logging v0.0.0-20191104132131-b318678afbef + github.com/caos/utils/pairs v0.0.0-20191104132131-b318678afbef // indirect + github.com/gorilla/mux v1.7.3 + github.com/grpc-ecosystem/grpc-gateway v1.12.1 // indirect + golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2 // indirect + google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 // indirect + google.golang.org/grpc v1.25.1 // indirect +) diff --git a/go.sum b/pkg/op/go.sum similarity index 75% rename from go.sum rename to pkg/op/go.sum index c5e8278..cdeb3f5 100644 --- a/go.sum +++ b/pkg/op/go.sum @@ -1,16 +1,22 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/caos/utils v0.0.0-20191104132131-b318678afbef h1:/a781PnuLvuTOj0PEJ7ByhgqjpC30Jsk+11uGcuxjjA= +github.com/caos/utils v0.0.0-20191104132131-b318678afbef/go.mod h1:m66FVMc4qkzUaWkRP1acVnYKagyTr9uGpvhCWvnlJoE= github.com/caos/utils/config v0.0.0-20191002113340-78986eef31d3/go.mod h1:4iI2a+qaiRFiLV1RAPG5dLp67M+NP2832toQbG9Uu74= github.com/caos/utils/logging v0.0.0-20191104132131-b318678afbef h1:ZkyR2deIvTjvULYw6bInjRR4YNfktQ9F6/z0VijtfmU= github.com/caos/utils/logging v0.0.0-20191104132131-b318678afbef/go.mod h1:1QHTbh4VS/6qN5fOApyEa70ESW+nDri3XE7j3oIzbyU= github.com/caos/utils/pairs v0.0.0-20191002113340-78986eef31d3 h1:HYlIp17vhqqUc9YOURRHOX948dSNfGYMo+aC+oaJvb0= github.com/caos/utils/pairs v0.0.0-20191002113340-78986eef31d3/go.mod h1:UZHeoVF6vhET4wTA/alcu0KOy65P2WI3AFlMvThGDcE= +github.com/caos/utils/pairs v0.0.0-20191104132131-b318678afbef h1:wpkm8hj2qNBOchILsfjLAqMWS1a4C+/7E4RM/NQ7T2U= +github.com/caos/utils/pairs v0.0.0-20191104132131-b318678afbef/go.mod h1:UZHeoVF6vhET4wTA/alcu0KOy65P2WI3AFlMvThGDcE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -20,24 +26,30 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= +github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/grpc-ecosystem/grpc-gateway v1.11.3 h1:h8+NsYENhxNTuq+dobk3+ODoJtwY4Fu0WQXsxJfL8aM= github.com/grpc-ecosystem/grpc-gateway v1.11.3/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.12.1 h1:zCy2xE9ablevUOrUZc3Dl72Dt+ya2FNAvC2yLYMHzi4= +github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -48,10 +60,9 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -63,8 +74,9 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed h1:5TJcLJn2a55mJjzYk0yOoqN8X1OdvBDUnaZaKKyQtkY= golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2 h1:wAW1U21MfVN0sUipAD8952TBjGXMRHFKQugDlQ9RwwE= +golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -74,19 +86,24 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c h1:hrpEMCZ2O7DR5gC1n2AJGVhrwiEjOi35+jxtIuZpTMo= google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 h1:51D++eCgOHufw5VfDE9Uzqyyc+OyQIjb9hkYy9LN5Fk= +google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A= gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/server/handler.go b/pkg/op/handler.go similarity index 100% rename from pkg/server/handler.go rename to pkg/op/handler.go diff --git a/pkg/server/session.go b/pkg/op/session.go similarity index 100% rename from pkg/server/session.go rename to pkg/op/session.go diff --git a/pkg/server/storage.go b/pkg/op/storage.go similarity index 100% rename from pkg/server/storage.go rename to pkg/op/storage.go diff --git a/pkg/rp/defaults/default_rp.go b/pkg/rp/defaults/default_rp.go new file mode 100644 index 0000000..d9204ae --- /dev/null +++ b/pkg/rp/defaults/default_rp.go @@ -0,0 +1,202 @@ +package defaults + +import ( + "context" + "encoding/base64" + "net/http" + "strings" + + "github.com/caos/oidc/pkg/oidc/grants" + + "golang.org/x/oauth2" + + "github.com/caos/oidc/pkg/oidc" + grants_tx "github.com/caos/oidc/pkg/oidc/grants/tokenexchange" + "github.com/caos/oidc/pkg/rp" + "github.com/caos/oidc/pkg/rp/tokenexchange" + "github.com/caos/oidc/pkg/utils" +) + +const ( + idTokenKey = "id_token" + stateParam = "state" +) + +type DefaultRP struct { + endpoints rp.Endpoints + + oauthConfig oauth2.Config + config *rp.Config + + httpClient *http.Client + cookieHandler *utils.CookieHandler + + verifier rp.Verifier +} + +func NewDefaultRelayingParty(rpConfig *rp.Config, rpOpts ...DefaultReplayingPartyOpts) (tokenexchange.DelegationTokenExchangeRP, error) { + p := &DefaultRP{ + config: rpConfig, + httpClient: utils.DefaultHTTPClient, + } + + for _, optFunc := range rpOpts { + optFunc(p) + } + + if err := p.discover(); err != nil { + return nil, err + } + + if p.verifier == nil { + // p.verifier = NewVerifier(rpConfig.Issuer, rpConfig.ClientID, utils.NewRemoteKeySet(p.httpClient, p.endpoints.JKWsURL)) //TODO: keys endpoint + } + + return p, nil +} + +type DefaultReplayingPartyOpts func(p *DefaultRP) + +func WithCookieHandler(cookieHandler *utils.CookieHandler) DefaultReplayingPartyOpts { + return func(p *DefaultRP) { + p.cookieHandler = cookieHandler + } +} + +func WithHTTPClient(client *http.Client) DefaultReplayingPartyOpts { + return func(p *DefaultRP) { + p.httpClient = client + } +} + +func (p *DefaultRP) AuthURL(state string) string { + return p.oauthConfig.AuthCodeURL(state) +} + +func (p *DefaultRP) AuthURLHandler(state string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := p.trySetStateCookie(w, state); err != nil { + http.Error(w, "failed to create state cookie: "+err.Error(), http.StatusUnauthorized) + return + } + http.Redirect(w, r, p.AuthURL(state), http.StatusFound) + } +} + +func (p *DefaultRP) CodeExchange(ctx context.Context, code string) (tokens *oidc.Tokens, err error) { + token, err := p.oauthConfig.Exchange(ctx, code) + if err != nil { + return nil, err //TODO: our error + } + idTokenString, ok := token.Extra(idTokenKey).(string) + if !ok { + //TODO: implement + } + + idToken, err := p.verifier.Verify(ctx, token.AccessToken, idTokenString) + if err != nil { + return nil, err //TODO: err + } + + return &oidc.Tokens{Token: token, IDTokenClaims: idToken}, nil +} + +func (p *DefaultRP) CodeExchangeHandler(callback func(http.ResponseWriter, *http.Request, *oidc.Tokens, string)) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + state, err := p.tryReadStateCookie(w, r) + if err != nil { + http.Error(w, "failed to get state: "+err.Error(), http.StatusUnauthorized) + return + } + tokens, err := p.CodeExchange(r.Context(), r.URL.Query().Get("code")) + if err != nil { + http.Error(w, "failed to exchange token: "+err.Error(), http.StatusUnauthorized) + return + } + callback(w, r, tokens, state) + } +} + +// func (p *DefaultRP) Introspect(ctx context.Context, accessToken string) (oidc.TokenIntrospectResponse, error) { +// // req := &http.Request{} +// // resp, err := p.httpClient.Do(req) +// // if err != nil { + +// // } +// // p.endpoints.IntrospectURL +// return nil, nil +// } + +func (p *DefaultRP) Userinfo() {} + +func (p *DefaultRP) TokenExchange(ctx context.Context, request *grants_tx.TokenExchangeRequest) (newToken *oauth2.Token, err error) { + return p.callTokenEndpoint(request) +} + +func (p *DefaultRP) callTokenEndpoint(request interface{}) (newToken *oauth2.Token, err error) { + req, err := utils.FormRequest(p.endpoints.TokenURL, request) + if err != nil { + return nil, err + } + auth := base64.StdEncoding.EncodeToString([]byte(p.config.ClientID + ":" + p.config.ClientSecret)) + req.Header.Set("Authorization", "Basic "+auth) + token := new(oauth2.Token) + if err := utils.HttpRequest(p.httpClient, req, token); err != nil { + return nil, err + } + return token, nil +} + +func (p *DefaultRP) ClientCredentials(ctx context.Context, scopes ...string) (newToken *oauth2.Token, err error) { + return p.callTokenEndpoint(grants.ClientCredentialsGrantBasic(scopes...)) +} + +func (p *DefaultRP) DelegationTokenExchange(ctx context.Context, subjectToken string, reqOpts ...grants_tx.TokenExchangeOption) (newToken *oauth2.Token, err error) { + return p.TokenExchange(ctx, DelegationTokenRequest(subjectToken, reqOpts...)) +} + +func (p *DefaultRP) discover() error { + wellKnown := strings.TrimSuffix(p.config.Issuer, "/") + oidc.DiscoveryEndpoint + + req, err := http.NewRequest("GET", wellKnown, nil) + if err != nil { + return err + } + discoveryConfig := new(oidc.DiscoveryConfiguration) + + err = utils.HttpRequest(p.httpClient, req, &discoveryConfig) + if err != nil { + return err + } + + p.endpoints = rp.GetEndpoints(discoveryConfig) + p.oauthConfig = oauth2.Config{ + ClientID: p.config.ClientID, + ClientSecret: p.config.ClientSecret, + Endpoint: p.endpoints.Endpoint, + RedirectURL: p.config.CallbackURL, + Scopes: p.config.Scopes, + } + return nil +} + +func (p *DefaultRP) trySetStateCookie(w http.ResponseWriter, state string) error { + if p.cookieHandler != nil { + if err := p.cookieHandler.SetQueryCookie(w, stateParam, state); err != nil { + return err + } + } + return nil +} + +func (p *DefaultRP) tryReadStateCookie(w http.ResponseWriter, r *http.Request) (state string, err error) { + if p.cookieHandler == nil { + return r.FormValue(stateParam), nil + } + state, err = p.cookieHandler.CheckQueryCookie(r, stateParam) + if err != nil { + return "", err + } + p.cookieHandler.DeleteCookie(w, stateParam) + return state, nil +} diff --git a/pkg/rp/defaults/delegation.go b/pkg/rp/defaults/delegation.go new file mode 100644 index 0000000..9ae0e93 --- /dev/null +++ b/pkg/rp/defaults/delegation.go @@ -0,0 +1,13 @@ +package defaults + +import ( + "github.com/caos/oidc/pkg/oidc/grants/tokenexchange" +) + +//DelegationTokenRequest is an implementation of TokenExchangeRequest +//it exchanges a "urn:ietf:params:oauth:token-type:access_token" with an optional +//"urn:ietf:params:oauth:token-type:access_token" actor token for a +//"urn:ietf:params:oauth:token-type:access_token" delegation token +func DelegationTokenRequest(subjectToken string, opts ...tokenexchange.TokenExchangeOption) *tokenexchange.TokenExchangeRequest { + return tokenexchange.NewTokenExchangeRequest(subjectToken, tokenexchange.AccessTokenType, opts...) +} diff --git a/pkg/rp/defaults/error.go b/pkg/rp/defaults/error.go new file mode 100644 index 0000000..f4da915 --- /dev/null +++ b/pkg/rp/defaults/error.go @@ -0,0 +1,58 @@ +package defaults + +import ( + "fmt" + "time" +) + +var ( + ErrIssuerInvalid = func(expected, actual string) *validationError { + return ValidationError("Issuer does not match. Expected: %s, got: %s", expected, actual) + } + ErrAudienceMissingClientID = func(clientID string) *validationError { + return ValidationError("Audience is not valid. Audience must contain client_id (%s)", clientID) + } + ErrAzpMissing = func() *validationError { + return ValidationError("Authorized Party is not set. If Token is valid for multiple audiences, azp must not be empty") + } + ErrAzpInvalid = func(azp, clientID string) *validationError { + return ValidationError("Authorized Party is not valid. azp (%s) must be equal to client_id (%s)", azp, clientID) + } + ErrExpInvalid = func(exp time.Time) *validationError { + return ValidationError("Token has expired %v", exp) + } + ErrIatInFuture = func(exp, now time.Time) *validationError { + return ValidationError("IssuedAt of token is in the future (%v, now with offset: %v)", exp, now) + } + ErrIatToOld = func(maxAge, iat time.Time) *validationError { + return ValidationError("IssuedAt of token must not be older than %v, but was %v (%v to old)", maxAge, iat, maxAge.Sub(iat)) + } + ErrNonceInvalid = func(expected, actual string) *validationError { + return ValidationError("nonce does not match. Expected: %s, got: %s", expected, actual) + } + ErrAcrInvalid = func(expected []string, actual string) *validationError { + return ValidationError("acr is invalid. Expected one of: %v, got: %s", expected, actual) + } + + ErrAuthTimeNotPresent = func() *validationError { + return ValidationError("claim `auth_time` of token is missing") + } + ErrAuthTimeToOld = func(maxAge, authTime time.Time) *validationError { + return ValidationError("Auth Time of token must not be older than %v, but was %v (%v to old)", maxAge, authTime, maxAge.Sub(authTime)) + } + ErrSignatureInvalidPayload = func() *validationError { + return ValidationError("Signature does not match Payload") + } +) + +func ValidationError(message string, args ...interface{}) *validationError { + return &validationError{fmt.Sprintf(message, args...)} //TODO: impl +} + +type validationError struct { + message string +} + +func (v *validationError) Error() string { + return v.message +} diff --git a/pkg/rp/defaults/verifier.go b/pkg/rp/defaults/verifier.go new file mode 100644 index 0000000..45df7a9 --- /dev/null +++ b/pkg/rp/defaults/verifier.go @@ -0,0 +1,456 @@ +package defaults + +import ( + "bytes" + "context" + "crypto/sha256" + "crypto/sha512" + "encoding/base64" + "encoding/json" + "fmt" + "hash" + "strings" + "time" + + "gopkg.in/square/go-jose.v2" + + "github.com/caos/oidc/pkg/oidc" + "github.com/caos/oidc/pkg/rp" + str_utils "github.com/caos/utils/strings" +) + +func NewVerifier(issuer, clientID string, keySet oidc.KeySet, confOpts ...ConfFunc) rp.Verifier { + conf := &verifierConfig{ + issuer: issuer, + clientID: clientID, + iat: &iatConfig{ + // offset: time.Duration(500 * time.Millisecond), + }, + } + + for _, opt := range confOpts { + if opt != nil { + opt(conf) + } + } + return &Verifier{config: conf, keySet: keySet} +} + +type Verifier struct { + config *verifierConfig + keySet oidc.KeySet +} + +type ConfFunc func(*verifierConfig) + +func WithIgnoreIssuedAt() func(*verifierConfig) { + return func(conf *verifierConfig) { + conf.iat.ignore = true + } +} + +func WithIssuedAtOffset(offset time.Duration) func(*verifierConfig) { + return func(conf *verifierConfig) { + conf.iat.offset = offset + } +} + +func WithIssuedAtMaxAge(maxAge time.Duration) func(*verifierConfig) { + return func(conf *verifierConfig) { + conf.iat.maxAge = maxAge + } +} + +func WithNonce(nonce string) func(*verifierConfig) { + return func(conf *verifierConfig) { + conf.nonce = nonce + } +} + +func WithACRVerifier(verifier ACRVerifier) func(*verifierConfig) { + return func(conf *verifierConfig) { + conf.acr = verifier + } +} + +func WithAuthTimeMaxAge(maxAge time.Duration) func(*verifierConfig) { + return func(conf *verifierConfig) { + conf.maxAge = maxAge + } +} + +func WithSupportedSigningAlgorithms(algs ...string) func(*verifierConfig) { + return func(conf *verifierConfig) { + conf.supportedSignAlgs = algs + } +} + +// func WithVerifierHTTPClient(client *http.Client) func(*verifierConfig) { +// return func(conf *verifierConfig) { +// conf.httpClient = client +// } +// } + +type verifierConfig struct { + issuer string + clientID string + nonce string + iat *iatConfig + acr ACRVerifier + maxAge time.Duration + supportedSignAlgs []string + + // httpClient *http.Client + + now time.Time +} + +type iatConfig struct { + ignore bool + offset time.Duration + maxAge time.Duration +} + +type ACRVerifier func(string) error + +func DefaultACRVerifier(possibleValues []string) func(string) error { + return func(acr string) error { + if !str_utils.Contains(possibleValues, acr) { + return ErrAcrInvalid(possibleValues, acr) + } + return nil + } +} + +func (v *Verifier) Verify(ctx context.Context, accessToken, idTokenString string) (*oidc.IDTokenClaims, error) { + v.config.now = time.Now().UTC() + idToken, err := v.VerifyIDToken(ctx, idTokenString) + if err != nil { + return nil, err + } + if err := v.verifyAccessToken(accessToken, idToken.AccessTokenHash, idToken.Signature); err != nil { //TODO: sig from token + return nil, err + } + return idToken, nil +} + +func (v *Verifier) now() time.Time { + if v.config.now.IsZero() { + v.config.now = time.Now().UTC().Round(time.Second) + } + return v.config.now +} + +func (v *Verifier) VerifyIDToken(ctx context.Context, idTokenString string) (*oidc.IDTokenClaims, error) { + //1. if encrypted --> decrypt + decrypted, err := v.decryptToken(idTokenString) + if err != nil { + return nil, err + } + claims, payload, err := v.parseToken(decrypted) + if err != nil { + return nil, err + } + // token, err := jwt.ParseWithClaims(decrypted, claims, func(token *jwt.Token) (interface{}, error) { + //2, check issuer (exact match) + if err := v.checkIssuer(claims.Issuer); err != nil { + return nil, err + } + + //3. check aud (aud must contain client_id, all aud strings must be allowed) + if err = v.checkAudience(claims.Audiences); err != nil { + return nil, err + } + + if err = v.checkAuthorizedParty(claims.Audiences, claims.AuthorizedParty); err != nil { + return nil, err + } + + //6. check signature by keys + //7. check alg default is rs256 + //8. check if alg is mac based (hs...) -> audience contains client_id. for validation use utf-8 representation of your client_secret + claims.Signature, err = v.checkSignature(ctx, decrypted, payload) + if err != nil { + return nil, err + } + + //9. check exp before now + if err = v.checkExpiration(claims.Expiration); err != nil { + return nil, err + } + + //10. check iat duration is optional (can be checked) + if err = v.checkIssuedAt(claims.IssuedAt); err != nil { + return nil, err + } + + //11. check nonce (check if optional possible) id_token.nonce == sentNonce + if err = v.checkNonce(claims.Nonce); err != nil { + return nil, err + } + + //12. if acr requested check acr + if err = v.checkAuthorizationContextClassReference(claims.AuthenticationContextClassReference); err != nil { + return nil, err + } + + //13. if auth_time requested check if auth_time is less than max age + if err = v.checkAuthTime(claims.AuthTime); err != nil { + return nil, err + } + //return idtoken struct, err + + return claims, nil + // }) + // _ = token + // return err +} + +func (v *Verifier) parseToken(tokenString string) (*oidc.IDTokenClaims, []byte, error) { + parts := strings.Split(tokenString, ".") + if len(parts) != 3 { + return nil, nil, ValidationError("token contains an invalid number of segments") //TODO: err NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed) + } + payload, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return nil, nil, fmt.Errorf("oidc: malformed jwt payload: %v", err) + } + idToken := new(oidc.IDTokenClaims) + err = json.Unmarshal(payload, idToken) + return idToken, payload, err +} + +func (v *Verifier) checkIssuer(issuer string) error { + if v.config.issuer != issuer { + return ErrIssuerInvalid(v.config.issuer, issuer) + } + return nil +} + +func (v *Verifier) checkAudience(audiences []string) error { + if !str_utils.Contains(audiences, v.config.clientID) { + return ErrAudienceMissingClientID(v.config.clientID) + } + + //TODO: check aud trusted + return nil +} + +//4. if multiple aud strings --> check if azp +//5. if azp --> check azp == client_id +func (v *Verifier) checkAuthorizedParty(audiences []string, authorizedParty string) error { + if len(audiences) > 1 { + if authorizedParty == "" { + return ErrAzpMissing() + } + } + if authorizedParty != "" && authorizedParty != v.config.clientID { + return ErrAzpInvalid(authorizedParty, v.config.clientID) + } + return nil +} + +func (v *Verifier) checkSignature(ctx context.Context, idTokenString string, payload []byte) (jose.SignatureAlgorithm, error) { + jws, err := jose.ParseSigned(idTokenString) + if err != nil { + return "", err + } + if len(jws.Signatures) == 0 { + return "", nil //TODO: error + } + if len(jws.Signatures) > 1 { + return "", nil //TODO: error + } + sig := jws.Signatures[0] + supportedSigAlgs := v.config.supportedSignAlgs + if len(supportedSigAlgs) == 0 { + supportedSigAlgs = []string{"RS256"} + } + if !str_utils.Contains(supportedSigAlgs, sig.Header.Algorithm) { + return "", fmt.Errorf("oidc: id token signed with unsupported algorithm, expected %q got %q", supportedSigAlgs, sig.Header.Algorithm) + } + + signedPayload, err := v.keySet.VerifySignature(ctx, jws) + if err != nil { + return "", err + //TODO: + } + + if !bytes.Equal(signedPayload, payload) { + return "", ErrSignatureInvalidPayload() //TODO: err + } + return jose.SignatureAlgorithm(sig.Header.Algorithm), nil +} + +// type KeySet struct { +// remoteURL url.URL +// httpClient *http.Client +// keys []jose.JSONWebKey + +// m sync.Mutex +// inflight *inflight +// } + +// func (k *KeySet) GetKey(ctx context.Context, keyID string) (*jose.JSONWebKey, error) { +// key, err := k.getKey(keyID) +// if err != nil { +// //lock +// k.updateKeys(ctx) +// //unlock +// return k.getKey(keyID) +// } +// return key, nil +// } + +// func (k *KeySet) getKey(keyID string) (*jose.JSONWebKey, error) { +// k.m.Lock() +// keys := k.keys +// k.m.Unlock() +// for _, key := range keys { +// if key.KeyID == keyID { +// return &key, nil +// } +// } +// return nil, nil //TODO: err +// } + +// func (k *KeySet) retrieveNewKeys(ctx context.Context) ([]jose.JSONWebKey, error) { +// resp, err := k.httpClient.Get(k.remoteURL.String()) +// if err != nil { +// return nil, err +// } +// if resp.StatusCode != http.StatusOK { +// return nil, nil //TODO: errs +// } + +// defer resp.Body.Close() +// body, err := ioutil.ReadAll(resp.Body) +// if err != nil { +// return nil, err +// } + +// var keySet jose.JSONWebKeySet +// err = json.Unmarshal(body, keySet) +// if err != nil { +// return nil, err +// } +// return keySet.Keys, nil +// } + +// func (k *KeySet) updateKeys(ctx context.Context) error { +// k.inflight +// k.m.Lock() +// k.keys = keySet.Keys +// return nil +// } + +func (v *Verifier) checkExpiration(expiration time.Time) error { + expiration = expiration.Round(time.Second) + if !v.now().Before(expiration) { + return ErrExpInvalid(expiration) + } + return nil +} + +func (v *Verifier) checkIssuedAt(issuedAt time.Time) error { + if v.config.iat.ignore { + return nil + } + issuedAt = issuedAt.Round(time.Second) + offset := v.now().Add(v.config.iat.offset).Round(time.Second) + if issuedAt.After(offset) { + return ErrIatInFuture(issuedAt, offset) + } + if v.config.iat.maxAge == 0 { + return nil + } + maxAge := v.now().Add(-v.config.iat.maxAge).Round(time.Second) + if issuedAt.Before(maxAge) { + return ErrIatToOld(maxAge, issuedAt) + } + return nil +} +func (v *Verifier) checkNonce(nonce string) error { + if v.config.nonce == "" { + return nil + } + if v.config.nonce != nonce { + return ErrNonceInvalid(v.config.nonce, nonce) + } + return nil +} +func (v *Verifier) checkAuthorizationContextClassReference(acr string) error { + if v.config.acr != nil { + return v.config.acr(acr) + } + return nil +} +func (v *Verifier) checkAuthTime(authTime time.Time) error { + if v.config.maxAge == 0 { + return nil + } + if authTime.IsZero() { + return ErrAuthTimeNotPresent() + } + authTime = authTime.Round(time.Second) + maxAge := v.now().Add(-v.config.maxAge).Round(time.Second) + if authTime.Before(maxAge) { + return ErrAuthTimeToOld(maxAge, authTime) + } + return nil +} + +func (v *Verifier) decryptToken(tokenString string) (string, error) { + return tokenString, nil //TODO: impl +} + +// func (v *Verifier) parseIDToken(tokenString string) (IDToken, error) { +// var claims jwt.StandardClaims +// token, err := jwt.ParseWithClaims(tokenString, &claims, func(token *jwt.Token) (interface{}, error) { +// claims.VerifyIssuer(v.config.Issuer, true) + +// // return token.Header["alg"] +// }) + +// payload, err := parseJWT(rawIDToken) +// if err != nil { +// return nil, fmt.Errorf("oidc: malformed jwt: %v", err) +// } +// var token IDToken +// if err := json.Unmarshal(payload, &token); err != nil { +// return nil, fmt.Errorf("oidc: failed to unmarshal claims: %v", err) +// } +// return token, nil //TODO: impl +// } + +func (v *Verifier) verifyAccessToken(accessToken, atHash string, sigAlgorithm jose.SignatureAlgorithm) error { + if atHash == "" { + return nil //TODO: return error + } + + tokenHash, err := getHashAlgorithm(sigAlgorithm) + if err != nil { + return err + } + + tokenHash.Write([]byte(accessToken)) // hash documents that Write will never return an error + sum := tokenHash.Sum(nil)[:tokenHash.Size()/2] + actual := base64.RawURLEncoding.EncodeToString(sum) + if actual != atHash { + return nil //TODO: error + } + return nil +} + +func getHashAlgorithm(sigAlgorithm jose.SignatureAlgorithm) (hash.Hash, error) { + switch sigAlgorithm { + case jose.RS256, jose.ES256, jose.PS256: + return sha256.New(), nil + case jose.RS384, jose.ES384, jose.PS384: + return sha512.New384(), nil + case jose.RS512, jose.ES512, jose.PS512: + return sha512.New(), nil + default: + return nil, fmt.Errorf("oidc: unsupported signing algorithm %q", sigAlgorithm) + } +} diff --git a/pkg/rp/go.mod b/pkg/rp/go.mod new file mode 100644 index 0000000..bbae794 --- /dev/null +++ b/pkg/rp/go.mod @@ -0,0 +1,18 @@ +module github.com/caos/oidc/pkg/client + +go 1.13 + +require ( + github.com/caos/oidc/pkg/oidc v0.0.0-00010101000000-000000000000 + github.com/caos/oidc/pkg/rp v0.0.0-00010101000000-000000000000 + github.com/caos/oidc/pkg/utils v0.0.0-00010101000000-000000000000 + github.com/caos/utils v0.0.0-20191104132131-b318678afbef + golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 + gopkg.in/square/go-jose.v2 v2.4.0 +) + +replace github.com/caos/oidc/pkg/oidc => /Users/livio/workspaces/go/src/github.com/caos/oidc/pkg/oidc + +replace github.com/caos/oidc/pkg/rp => /Users/livio/workspaces/go/src/github.com/caos/oidc/pkg/rp + +replace github.com/caos/oidc/pkg/utils => /Users/livio/workspaces/go/src/github.com/caos/oidc/pkg/utils diff --git a/pkg/rp/go.sum b/pkg/rp/go.sum new file mode 100644 index 0000000..7fdb44f --- /dev/null +++ b/pkg/rp/go.sum @@ -0,0 +1,117 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/caos/utils v0.0.0-20191104132131-b318678afbef h1:/a781PnuLvuTOj0PEJ7ByhgqjpC30Jsk+11uGcuxjjA= +github.com/caos/utils v0.0.0-20191104132131-b318678afbef/go.mod h1:m66FVMc4qkzUaWkRP1acVnYKagyTr9uGpvhCWvnlJoE= +github.com/caos/utils/config v0.0.0-20191002113340-78986eef31d3/go.mod h1:4iI2a+qaiRFiLV1RAPG5dLp67M+NP2832toQbG9Uu74= +github.com/caos/utils/logging v0.0.0-20191104132131-b318678afbef h1:ZkyR2deIvTjvULYw6bInjRR4YNfktQ9F6/z0VijtfmU= +github.com/caos/utils/logging v0.0.0-20191104132131-b318678afbef/go.mod h1:1QHTbh4VS/6qN5fOApyEa70ESW+nDri3XE7j3oIzbyU= +github.com/caos/utils/pairs v0.0.0-20191002113340-78986eef31d3/go.mod h1:UZHeoVF6vhET4wTA/alcu0KOy65P2WI3AFlMvThGDcE= +github.com/caos/utils/pairs v0.0.0-20191104132131-b318678afbef h1:wpkm8hj2qNBOchILsfjLAqMWS1a4C+/7E4RM/NQ7T2U= +github.com/caos/utils/pairs v0.0.0-20191104132131-b318678afbef/go.mod h1:UZHeoVF6vhET4wTA/alcu0KOy65P2WI3AFlMvThGDcE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= +github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/grpc-ecosystem/grpc-gateway v1.11.3/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.12.1 h1:zCy2xE9ablevUOrUZc3Dl72Dt+ya2FNAvC2yLYMHzi4= +github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f h1:kz4KIr+xcPUsI3VMoqWfPMvtnJ6MGfiVwsWSVzphMO4= +golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 h1:MlY3mEfbnWGmUi4rtHOtNnnnN4UJRGSyLPx+DXA5Sq4= +golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2 h1:wAW1U21MfVN0sUipAD8952TBjGXMRHFKQugDlQ9RwwE= +golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 h1:51D++eCgOHufw5VfDE9Uzqyyc+OyQIjb9hkYy9LN5Fk= +google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A= +gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/rp/relaying_party.go b/pkg/rp/relaying_party.go new file mode 100644 index 0000000..df88ecd --- /dev/null +++ b/pkg/rp/relaying_party.go @@ -0,0 +1,84 @@ +package rp + +import ( + "context" + "net/http" + + "github.com/caos/oidc/pkg/oidc" + + "golang.org/x/oauth2" +) + +//RelayingParty declares the minimal interface for oidc clients +type RelayingParty interface { + + //AuthURL returns the authorization endpoint with a given state + AuthURL(state string) string + + //AuthURLHandler should implement the AuthURL func as http.HandlerFunc + //(redirecting to the auth endpoint) + AuthURLHandler(state string) http.HandlerFunc + + //CodeExchange implements the OIDC Token Request (oauth2 Authorization Code Grant) + //returning an `Access Token` and `ID Token Claims` + CodeExchange(ctx context.Context, code string) (*oidc.Tokens, error) + + //CodeExchangeHandler extends the CodeExchange func, + //calling the provided callback func on success with additional returned `state` + CodeExchangeHandler(callback func(http.ResponseWriter, *http.Request, *oidc.Tokens, string)) http.HandlerFunc + + //ClientCredentials implements the oauth2 Client Credentials Grant + //requesting an `Access Token` for the client itself, without user context + ClientCredentials(ctx context.Context, scopes ...string) (*oauth2.Token, error) + + //Introspects calls the Introspect Endpoint + //for validating an (access) token + // Introspect(ctx context.Context, token string) (TokenIntrospectResponse, error) + + //Userinfo implements the OIDC Userinfo call + //returning the info of the user for the requested scopes of an access token + Userinfo() +} + +//PasswortGrantRP extends the `RelayingParty` interface with the oauth2 `Password Grant` +// +//This interface is separated from the standard `RelayingParty` interface as the `password grant` +//is part of the oauth2 and therefore OIDC specification, but should only be used when there's no +//other possibility, so IMHO never ever. Ever. +type PasswortGrantRP interface { + RelayingParty + + //PasswordGrant implements the oauth2 `Password Grant`, + //requesting an access token with the users `username` and `password` + PasswordGrant(context.Context, string, string) (*oauth2.Token, error) +} + +type Config struct { + ClientID string + ClientSecret string + CallbackURL string + Issuer string + Scopes []string +} + +type OptionFunc func(RelayingParty) + +type Endpoints struct { + oauth2.Endpoint + IntrospectURL string + UserinfoURL string + JKWsURL string +} + +func GetEndpoints(discoveryConfig *oidc.DiscoveryConfiguration) Endpoints { + return Endpoints{ + Endpoint: oauth2.Endpoint{ + AuthURL: discoveryConfig.AuthorizationEndpoint, + AuthStyle: oauth2.AuthStyleAutoDetect, + TokenURL: discoveryConfig.TokenEndpoint, + }, + IntrospectURL: discoveryConfig.IntrospectionEndpoint, + UserinfoURL: discoveryConfig.UserinfoEndpoint, + JKWsURL: discoveryConfig.JwksURI, + } +} diff --git a/pkg/rp/tokenexchange/relaying_party.go b/pkg/rp/tokenexchange/relaying_party.go new file mode 100644 index 0000000..8b1eb22 --- /dev/null +++ b/pkg/rp/tokenexchange/relaying_party.go @@ -0,0 +1,28 @@ +package tokenexchange + +import ( + "context" + + "golang.org/x/oauth2" + + "github.com/caos/oidc/pkg/oidc/grants/tokenexchange" + "github.com/caos/oidc/pkg/rp" +) + +//TokenExchangeRP extends the `RelayingParty` interface for the *draft* oauth2 `Token Exchange` +type TokenExchangeRP interface { + rp.RelayingParty + + //TokenExchange implement the `Token Echange Grant` exchanging some token for an other + TokenExchange(context.Context, *tokenexchange.TokenExchangeRequest) (*oauth2.Token, error) +} + +//DelegationTokenExchangeRP extends the `TokenExchangeRP` interface +//for the specific `delegation token` request +type DelegationTokenExchangeRP interface { + TokenExchangeRP + + //DelegationTokenExchange implement the `Token Exchange Grant` + //providing an access token in request for a `delegation` token for a given resource / audience + DelegationTokenExchange(context.Context, string, ...tokenexchange.TokenExchangeOption) (*oauth2.Token, error) +} diff --git a/pkg/rp/verifier.go b/pkg/rp/verifier.go new file mode 100644 index 0000000..b82e6c2 --- /dev/null +++ b/pkg/rp/verifier.go @@ -0,0 +1,15 @@ +package rp + +import ( + "context" + + "github.com/caos/oidc/pkg/oidc" +) + +//Verifier implement the Token Response Validation as defined in OIDC specification +//https://openid.net/specs/openid-connect-core-1_0.html#TokenResponseValidation +type Verifier interface { + + //Verify checks the access_token and id_token and returns the `id token claims` + Verify(ctx context.Context, accessToken, idTokenString string) (*oidc.IDTokenClaims, error) +} diff --git a/pkg/utils/cookie.go b/pkg/utils/cookie.go new file mode 100644 index 0000000..52002d7 --- /dev/null +++ b/pkg/utils/cookie.go @@ -0,0 +1,102 @@ +package utils + +import ( + "errors" + "net/http" + + "github.com/gorilla/securecookie" +) + +type CookieHandler struct { + securecookie *securecookie.SecureCookie + secureOnly bool + sameSite http.SameSite + maxAge int + domain string +} + +func NewCookieHandler(hashKey, encryptKey []byte, opts ...CookieHandlerOpt) *CookieHandler { + c := &CookieHandler{ + securecookie: securecookie.New(hashKey, encryptKey), + secureOnly: true, + sameSite: http.SameSiteNoneMode, + } + + for _, opt := range opts { + opt(c) + } + return c +} + +type CookieHandlerOpt func(*CookieHandler) + +func WithUnsecure() CookieHandlerOpt { + return func(c *CookieHandler) { + c.secureOnly = false + } +} + +func WithSameSite(sameSite http.SameSite) CookieHandlerOpt { + return func(c *CookieHandler) { + c.sameSite = sameSite + } +} + +func WithMaxAge(maxAge int) CookieHandlerOpt { + return func(c *CookieHandler) { + c.maxAge = maxAge + c.securecookie.MaxAge(maxAge) + } +} + +func WithDomain(domain string) CookieHandlerOpt { + return func(c *CookieHandler) { + c.domain = domain + } +} + +func (c *CookieHandler) CheckQueryCookie(r *http.Request, name string) (string, error) { + cookie, err := r.Cookie(name) + if err != nil { + return "", err + } + var value string + if err := c.securecookie.Decode(name, cookie.Value, &value); err != nil { + return "", err + } + if value != r.FormValue(name) { + return "", errors.New(name + " does not compare") + } + return value, nil +} + +func (c *CookieHandler) SetQueryCookie(w http.ResponseWriter, name, value string) error { + encoded, err := c.securecookie.Encode(name, value) + if err != nil { + return err + } + http.SetCookie(w, &http.Cookie{ + Name: name, + Value: encoded, + Domain: c.domain, + Path: "/", + MaxAge: c.maxAge, + HttpOnly: true, + Secure: c.secureOnly, + SameSite: c.sameSite, + }) + return nil +} + +func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, name string) { + http.SetCookie(w, &http.Cookie{ + Name: name, + Value: "", + Domain: c.domain, + Path: "/", + MaxAge: -1, + HttpOnly: true, + Secure: c.secureOnly, + SameSite: c.sameSite, + }) +} diff --git a/pkg/utils/go.mod b/pkg/utils/go.mod new file mode 100644 index 0000000..8dc62d1 --- /dev/null +++ b/pkg/utils/go.mod @@ -0,0 +1,14 @@ +module github.com/caos/oidc/pkg/utils + +go 1.13 + +require ( + github.com/caos/utils/logging v0.0.0-20191104132131-b318678afbef + github.com/caos/utils/pairs v0.0.0-20191104132131-b318678afbef // indirect + github.com/gorilla/schema v1.1.0 + github.com/gorilla/securecookie v1.1.1 + github.com/grpc-ecosystem/grpc-gateway v1.12.1 // indirect + golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2 // indirect + google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 // indirect + google.golang.org/grpc v1.25.1 // indirect +) diff --git a/pkg/utils/go.sum b/pkg/utils/go.sum new file mode 100644 index 0000000..54aab6c --- /dev/null +++ b/pkg/utils/go.sum @@ -0,0 +1,108 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/caos/utils/config v0.0.0-20191002113340-78986eef31d3/go.mod h1:4iI2a+qaiRFiLV1RAPG5dLp67M+NP2832toQbG9Uu74= +github.com/caos/utils/logging v0.0.0-20191104132131-b318678afbef h1:ZkyR2deIvTjvULYw6bInjRR4YNfktQ9F6/z0VijtfmU= +github.com/caos/utils/logging v0.0.0-20191104132131-b318678afbef/go.mod h1:1QHTbh4VS/6qN5fOApyEa70ESW+nDri3XE7j3oIzbyU= +github.com/caos/utils/pairs v0.0.0-20191002113340-78986eef31d3 h1:HYlIp17vhqqUc9YOURRHOX948dSNfGYMo+aC+oaJvb0= +github.com/caos/utils/pairs v0.0.0-20191002113340-78986eef31d3/go.mod h1:UZHeoVF6vhET4wTA/alcu0KOy65P2WI3AFlMvThGDcE= +github.com/caos/utils/pairs v0.0.0-20191104132131-b318678afbef h1:wpkm8hj2qNBOchILsfjLAqMWS1a4C+/7E4RM/NQ7T2U= +github.com/caos/utils/pairs v0.0.0-20191104132131-b318678afbef/go.mod h1:UZHeoVF6vhET4wTA/alcu0KOy65P2WI3AFlMvThGDcE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= +github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/grpc-ecosystem/grpc-gateway v1.11.3 h1:h8+NsYENhxNTuq+dobk3+ODoJtwY4Fu0WQXsxJfL8aM= +github.com/grpc-ecosystem/grpc-gateway v1.11.3/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.12.1 h1:zCy2xE9ablevUOrUZc3Dl72Dt+ya2FNAvC2yLYMHzi4= +github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0 h1:2mqDk8w/o6UmeUCu5Qiq2y7iMf6anbx+YA8d1JFoFrs= +golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed h1:5TJcLJn2a55mJjzYk0yOoqN8X1OdvBDUnaZaKKyQtkY= +golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2 h1:wAW1U21MfVN0sUipAD8952TBjGXMRHFKQugDlQ9RwwE= +golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c h1:hrpEMCZ2O7DR5gC1n2AJGVhrwiEjOi35+jxtIuZpTMo= +google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11 h1:51D++eCgOHufw5VfDE9Uzqyyc+OyQIjb9hkYy9LN5Fk= +google.golang.org/genproto v0.0.0-20191115221424-83cc0476cb11/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/utils/http.go b/pkg/utils/http.go new file mode 100644 index 0000000..b3d3434 --- /dev/null +++ b/pkg/utils/http.go @@ -0,0 +1,57 @@ +package utils + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + + "github.com/gorilla/schema" +) + +var ( + DefaultHTTPClient = &http.Client{ + Timeout: time.Duration(30 * time.Second), + } +) + +func FormRequest(endpoint string, request interface{}) (*http.Request, error) { + form := make(map[string][]string) + encoder := schema.NewEncoder() + if err := encoder.Encode(request, form); err != nil { + return nil, err + } + body := strings.NewReader(url.Values(form).Encode()) + req, err := http.NewRequest("POST", endpoint, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + return req, nil +} + +func HttpRequest(client *http.Client, req *http.Request, response interface{}) error { + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("unable to read response body: %v", err) + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("http status not ok: %s %s", resp.Status, body) + } + + err = json.Unmarshal(body, response) + if err != nil { + return fmt.Errorf("failed to unmarshal response: %v %s", err, body) + } + return nil +}