github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/command/testdata/login-oauth-server/oauthserver.go (about)

     1  // Package oauthserver is a very simplistic OAuth server used only for
     2  // the testing of the "durgaform login" and "terraform logout" commands.
     3  package oauthserver
     4  
     5  import (
     6  	"crypto/sha256"
     7  	"encoding/base64"
     8  	"fmt"
     9  	"html"
    10  	"log"
    11  	"net/http"
    12  	"net/url"
    13  	"strings"
    14  )
    15  
    16  // Handler is an implementation of net/http.Handler that provides a stub
    17  // OAuth server implementation with the following endpoints:
    18  //
    19  //     /authz  - authorization endpoint
    20  //     /token  - token endpoint
    21  //     /revoke - token revocation (logout) endpoint
    22  //
    23  // The authorization endpoint returns HTML per normal OAuth conventions, but
    24  // it also includes an HTTP header X-Redirect-To giving the same URL that the
    25  // link in the HTML indicates, allowing a non-browser user-agent to traverse
    26  // this robotically in automated tests.
    27  var Handler http.Handler
    28  
    29  type handler struct{}
    30  
    31  func (h handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
    32  	switch req.URL.Path {
    33  	case "/authz":
    34  		h.serveAuthz(resp, req)
    35  	case "/token":
    36  		h.serveToken(resp, req)
    37  	case "/revoke":
    38  		h.serveRevoke(resp, req)
    39  	default:
    40  		resp.WriteHeader(404)
    41  	}
    42  }
    43  
    44  func (h handler) serveAuthz(resp http.ResponseWriter, req *http.Request) {
    45  	args := req.URL.Query()
    46  	if rt := args.Get("response_type"); rt != "code" {
    47  		resp.WriteHeader(400)
    48  		resp.Write([]byte("wrong response_type"))
    49  		log.Printf("/authz: incorrect response type %q", rt)
    50  		return
    51  	}
    52  	redirectURL, err := url.Parse(args.Get("redirect_uri"))
    53  	if err != nil {
    54  		resp.WriteHeader(400)
    55  		resp.Write([]byte(fmt.Sprintf("invalid redirect_uri %s: %s", args.Get("redirect_uri"), err)))
    56  		return
    57  	}
    58  
    59  	state := args.Get("state")
    60  	challenge := args.Get("code_challenge")
    61  	challengeMethod := args.Get("code_challenge_method")
    62  	if challengeMethod == "" {
    63  		challengeMethod = "plain"
    64  	}
    65  
    66  	// NOTE: This is not a suitable implementation for a real OAuth server
    67  	// because the code challenge is providing no security whatsoever. This
    68  	// is just a simple implementation for this stub server.
    69  	code := fmt.Sprintf("%s:%s", challengeMethod, challenge)
    70  
    71  	redirectQuery := redirectURL.Query()
    72  	redirectQuery.Set("code", code)
    73  	if state != "" {
    74  		redirectQuery.Set("state", state)
    75  	}
    76  	redirectURL.RawQuery = redirectQuery.Encode()
    77  
    78  	respBody := fmt.Sprintf(`<a href="%s">Log In and Consent</a>`, html.EscapeString(redirectURL.String()))
    79  	resp.Header().Set("Content-Type", "text/html")
    80  	resp.Header().Set("Content-Length", fmt.Sprintf("%d", len(respBody)))
    81  	resp.Header().Set("X-Redirect-To", redirectURL.String()) // For robotic clients, using webbrowser.MockLauncher
    82  	resp.WriteHeader(200)
    83  	resp.Write([]byte(respBody))
    84  }
    85  
    86  func (h handler) serveToken(resp http.ResponseWriter, req *http.Request) {
    87  	if req.Method != "POST" {
    88  		resp.WriteHeader(405)
    89  		log.Printf("/token: unsupported request method %q", req.Method)
    90  		return
    91  	}
    92  
    93  	if err := req.ParseForm(); err != nil {
    94  		resp.WriteHeader(500)
    95  		log.Printf("/token: error parsing body: %s", err)
    96  		return
    97  	}
    98  
    99  	grantType := req.Form.Get("grant_type")
   100  	log.Printf("/token: grant_type is %q", grantType)
   101  	switch grantType {
   102  
   103  	case "authorization_code":
   104  		code := req.Form.Get("code")
   105  		codeParts := strings.SplitN(code, ":", 2)
   106  		if len(codeParts) != 2 {
   107  			log.Printf("/token: invalid code %q", code)
   108  			resp.Header().Set("Content-Type", "application/json")
   109  			resp.WriteHeader(400)
   110  			resp.Write([]byte(`{"error":"invalid_grant"}`))
   111  			return
   112  		}
   113  
   114  		codeVerifier := req.Form.Get("code_verifier")
   115  
   116  		switch codeParts[0] {
   117  		case "plain":
   118  			if codeParts[1] != codeVerifier {
   119  				log.Printf("/token: incorrect code verifier %q; want %q", codeParts[1], codeVerifier)
   120  				resp.Header().Set("Content-Type", "application/json")
   121  				resp.WriteHeader(400)
   122  				resp.Write([]byte(`{"error":"invalid_grant"}`))
   123  				return
   124  			}
   125  		case "S256":
   126  			h := sha256.New()
   127  			h.Write([]byte(codeVerifier))
   128  			encVerifier := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
   129  			if codeParts[1] != encVerifier {
   130  				log.Printf("/token: incorrect code verifier %q; want %q", codeParts[1], encVerifier)
   131  				resp.Header().Set("Content-Type", "application/json")
   132  				resp.WriteHeader(400)
   133  				resp.Write([]byte(`{"error":"invalid_grant"}`))
   134  				return
   135  			}
   136  		default:
   137  			log.Printf("/token: unsupported challenge method %q", codeParts[0])
   138  			resp.Header().Set("Content-Type", "application/json")
   139  			resp.WriteHeader(400)
   140  			resp.Write([]byte(`{"error":"invalid_grant"}`))
   141  			return
   142  		}
   143  
   144  		resp.Header().Set("Content-Type", "application/json")
   145  		resp.WriteHeader(200)
   146  		resp.Write([]byte(`{"access_token":"good-token","token_type":"bearer"}`))
   147  		log.Println("/token: successful request")
   148  
   149  	case "password":
   150  		username := req.Form.Get("username")
   151  		password := req.Form.Get("password")
   152  
   153  		if username == "wrong" || password == "wrong" {
   154  			// These special "credentials" allow testing for the error case.
   155  			resp.Header().Set("Content-Type", "application/json")
   156  			resp.WriteHeader(400)
   157  			resp.Write([]byte(`{"error":"invalid_grant"}`))
   158  			log.Println("/token: 'wrong' credentials")
   159  			return
   160  		}
   161  
   162  		resp.Header().Set("Content-Type", "application/json")
   163  		resp.WriteHeader(200)
   164  		resp.Write([]byte(`{"access_token":"good-token","token_type":"bearer"}`))
   165  		log.Println("/token: successful request")
   166  
   167  	default:
   168  		resp.WriteHeader(400)
   169  		log.Printf("/token: unsupported grant type %q", grantType)
   170  	}
   171  }
   172  
   173  func (h handler) serveRevoke(resp http.ResponseWriter, req *http.Request) {
   174  	resp.WriteHeader(404)
   175  }
   176  
   177  func init() {
   178  	Handler = handler{}
   179  }