diff --git a/command/testdata/login-oauth-server/main.go b/command/testdata/login-oauth-server/main.go new file mode 100644 index 0000000000..70333b61a7 --- /dev/null +++ b/command/testdata/login-oauth-server/main.go @@ -0,0 +1,71 @@ +// +build ignore + +// This file is a helper for those doing _manual_ testing of "terraform login" +// and/or "terraform logout" and want to start up a test OAuth server in a +// separate process for convenience: +// +// go run ./command/testdata/login-oauth-server/main.go :8080 +// +// This is _not_ the main way to use this oauthserver package. For automated +// test code, import it as a normal Go package instead: +// +// import oauthserver "github.com/hashicorp/terraform/command/testdata/login-oauth-server" + +package main + +import ( + "fmt" + "net" + "net/http" + "os" + + oauthserver "github.com/hashicorp/terraform/command/testdata/login-oauth-server" +) + +func main() { + if len(os.Args) < 2 { + fmt.Fprintln(os.Stderr, "Usage: go run ./command/testdata/login-oauth-server/main.go ") + os.Exit(1) + } + + host, port, err := net.SplitHostPort(os.Args[1]) + if err != nil { + fmt.Fprintln(os.Stderr, "Invalid address: %s", err) + os.Exit(1) + } + + if host == "" { + host = "127.0.0.1" + } + addr := fmt.Sprintf("%s:%s", host, port) + + fmt.Printf("Will listen on %s...\n", addr) + fmt.Printf( + configExampleFmt, + fmt.Sprintf("http://%s:%s/authz", host, port), + fmt.Sprintf("http://%s:%s/token", host, port), + fmt.Sprintf("http://%s:%s/revoke", host, port), + ) + + server := &http.Server{ + Addr: addr, + Handler: oauthserver.Handler, + } + err = server.ListenAndServe() + fmt.Fprintln(os.Stderr, err.Error()) +} + +const configExampleFmt = ` +host "login-test.example.com" { + services = { + "login.v1" = { + authz = %q + token = %q + client = "placeholder" + grant_types = ["code", "password"] + } + "logout.v1" = %q + } +} + +` diff --git a/command/testdata/login-oauth-server/oauthserver.go b/command/testdata/login-oauth-server/oauthserver.go new file mode 100644 index 0000000000..15fe32939f --- /dev/null +++ b/command/testdata/login-oauth-server/oauthserver.go @@ -0,0 +1,88 @@ +// Package oauthserver is a very simplistic OAuth server used only for +// the testing of the "terraform login" and "terraform logout" commands. +package oauthserver + +import ( + "log" + "net/http" +) + +// Handler is an implementation of net/http.Handler that provides a stub +// OAuth server implementation with the following endpoints: +// +// /authz - authorization endpoint +// /token - token endpoint +// /revoke - token revocation (logout) endpoint +// +// The authorization endpoint returns HTML per normal OAuth conventions, but +// it also includes an HTTP header X-Redirect-To giving the same URL that the +// link in the HTML indicates, allowing a non-browser user-agent to traverse +// this robotically in automated tests. +var Handler http.Handler + +type handler struct{} + +func (h handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + switch req.URL.Path { + case "/authz": + h.serveAuthz(resp, req) + case "/token": + h.serveToken(resp, req) + case "/revoke": + h.serveRevoke(resp, req) + default: + resp.WriteHeader(404) + } +} + +func (h handler) serveAuthz(resp http.ResponseWriter, req *http.Request) { + resp.WriteHeader(404) +} + +func (h handler) serveToken(resp http.ResponseWriter, req *http.Request) { + if req.Method != "POST" { + resp.WriteHeader(405) + log.Printf("/token: unsupported request method %q", req.Method) + return + } + + if err := req.ParseForm(); err != nil { + resp.WriteHeader(500) + log.Printf("/token: error parsing body: %s", err) + return + } + + grantType := req.Form.Get("grant_type") + log.Printf("/token: grant_type is %q", grantType) + switch grantType { + case "password": + username := req.Form.Get("username") + password := req.Form.Get("password") + + if username == "wrong" || password == "wrong" { + // These special "credentials" allow testing for the error case. + resp.Header().Set("Content-Type", "application/json") + resp.WriteHeader(400) + resp.Write([]byte(`{"error":"invalid_grant"}`)) + log.Println("/token: 'wrong' credentials") + return + } + + resp.Header().Set("Content-Type", "application/json") + resp.WriteHeader(200) + resp.Write([]byte(`{"access_token":"good-token","token_type":"bearer"}`)) + log.Println("/token: successful request") + + default: + resp.WriteHeader(400) + log.Printf("/token: unsupported grant type %q", grantType) + } +} + +func (h handler) serveRevoke(resp http.ResponseWriter, req *http.Request) { + resp.WriteHeader(404) +} + +func init() { + Handler = handler{} +}