Browse Source

fix example and reuse of http.Default client (#370)

Jonathan Turner 6 years ago
parent
commit
3bfb2adb52
3 changed files with 90 additions and 35 deletions
  1. 82 32
      v8/examples/example-AD.go
  2. 2 1
      v8/examples/httpServer.go
  3. 6 2
      v8/spnego/http.go

+ 82 - 32
v8/examples/example-AD.go

@@ -4,8 +4,15 @@ package main
 
 import (
 	"encoding/hex"
-	"encoding/json"
+	"errors"
 	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"net/http/httptest"
+	"os"
+
+	"github.com/gorilla/sessions"
 	"github.com/jcmturner/goidentity/v6"
 	"github.com/jcmturner/gokrb5/v8/client"
 	"github.com/jcmturner/gokrb5/v8/config"
@@ -14,16 +21,12 @@ import (
 	"github.com/jcmturner/gokrb5/v8/service"
 	"github.com/jcmturner/gokrb5/v8/spnego"
 	"github.com/jcmturner/gokrb5/v8/test/testdata"
-	"io/ioutil"
-	"log"
-	"net/http"
-	"net/http/httptest"
-	"os"
 )
 
 func main() {
 	s := httpServer()
 	defer s.Close()
+	fmt.Printf("Listening on %s\n", s.URL)
 
 	b, _ := hex.DecodeString(testdata.TESTUSER1_USERKRB5_AD_KEYTAB)
 	kt := keytab.New()
@@ -38,8 +41,6 @@ func main() {
 	c, _ = config.NewFromString(testdata.TEST_KRB5CONF)
 	cl = client.NewWithKeytab("testuser2", "USER.GOKRB5", kt, c, client.DisablePAFXFAST(true))
 	httpRequest(s.URL, cl)
-
-	//httpRequest("http://host.test.gokrb5/index.html")
 }
 
 func httpRequest(url string, cl *client.Client) {
@@ -49,33 +50,42 @@ func httpRequest(url string, cl *client.Client) {
 	if err != nil {
 		l.Printf("Error on AS_REQ: %v\n", err)
 	}
+
+	spnegoCl := spnego.NewClient(cl, nil, "HTTP/host.res.gokrb5")
+
+	// Make the request for the first time with no session
 	r, _ := http.NewRequest("GET", url, nil)
-	err = spnego.SetSPNEGOHeader(cl, r, "HTTP/host.test.gokrb5")
+	httpResp, err := spnegoCl.Do(r)
 	if err != nil {
-		l.Printf("Error setting client SPNEGO header: %v", err)
+		l.Fatalf("error making request: %v", err)
 	}
-	httpResp, err := http.DefaultClient.Do(r)
+	fmt.Fprintf(os.Stdout, "Response Code: %v\n", httpResp.StatusCode)
+	content, _ := ioutil.ReadAll(httpResp.Body)
+	fmt.Fprintf(os.Stdout, "Response Body:\n%s\n", content)
+
+	// Make the request again which should use the session
+	httpResp, err = spnegoCl.Do(r)
 	if err != nil {
-		l.Printf("Request error: %v\n", err)
+		l.Fatalf("error making request: %v", err)
 	}
 	fmt.Fprintf(os.Stdout, "Response Code: %v\n", httpResp.StatusCode)
-	content, _ := ioutil.ReadAll(httpResp.Body)
+	content, _ = ioutil.ReadAll(httpResp.Body)
 	fmt.Fprintf(os.Stdout, "Response Body:\n%s\n", content)
 }
 
 func httpServer() *httptest.Server {
 	l := log.New(os.Stderr, "GOKRB5 Service Tests: ", log.Ldate|log.Ltime|log.Lshortfile)
-	b, _ := hex.DecodeString(testdata.HTTP_KEYTAB)
+	b, _ := hex.DecodeString(testdata.SYSHTTP_RESGOKRB5_AD_KEYTAB)
 	kt := keytab.New()
 	kt.Unmarshal(b)
 	th := http.HandlerFunc(testAppHandler)
-	s := httptest.NewServer(spnego.SPNEGOKRB5Authenticate(th, kt, service.Logger(l)))
+	s := httptest.NewServer(spnego.SPNEGOKRB5Authenticate(th, kt, service.Logger(l), service.KeytabPrincipal("sysHTTP"), service.SessionManager(NewSessionMgr("gokrb5"))))
 	return s
 }
 
 func testAppHandler(w http.ResponseWriter, r *http.Request) {
 	creds := goidentity.FromHTTPRequestContext(r)
-	fmt.Fprint(w, "<html>\n<p><h1>TEST.GOKRB5 Handler</h1></p>\n")
+	fmt.Fprint(w, "<html>\n<p><h1>GOKRB5 Handler</h1></p>\n")
 	if creds != nil && creds.Authenticated() {
 		fmt.Fprintf(w, "<ul><li>Authenticed user: %s</li>\n", creds.UserName())
 		fmt.Fprintf(w, "<li>User's realm: %s</li>\n", creds.Domain())
@@ -85,22 +95,23 @@ func testAppHandler(w http.ResponseWriter, r *http.Request) {
 		}
 		fmt.Fprint(w, "</ul>\n")
 		if ADCredsJSON, ok := creds.Attributes()[credentials.AttributeKeyADCredentials]; ok {
-			ADCreds := new(credentials.ADCredentials)
-			err := json.Unmarshal([]byte(ADCredsJSON), ADCreds)
-			if err == nil {
-				// Now access the fields of the ADCredentials struct. For example:
-				fmt.Fprintf(w, "<li>EffectiveName: %v</li>\n", ADCreds.EffectiveName)
-				fmt.Fprintf(w, "<li>FullName: %v</li>\n", ADCreds.FullName)
-				fmt.Fprintf(w, "<li>UserID: %v</li>\n", ADCreds.UserID)
-				fmt.Fprintf(w, "<li>PrimaryGroupID: %v</li>\n", ADCreds.PrimaryGroupID)
-				fmt.Fprintf(w, "<li>Group SIDs: %v</li>\n", ADCreds.GroupMembershipSIDs)
-				fmt.Fprintf(w, "<li>LogOnTime: %v</li>\n", ADCreds.LogOnTime)
-				fmt.Fprintf(w, "<li>LogOffTime: %v</li>\n", ADCreds.LogOffTime)
-				fmt.Fprintf(w, "<li>PasswordLastSet: %v</li>\n", ADCreds.PasswordLastSet)
-				fmt.Fprintf(w, "<li>LogonServer: %v</li>\n", ADCreds.LogonServer)
-				fmt.Fprintf(w, "<li>LogonDomainName: %v</li>\n", ADCreds.LogonDomainName)
-				fmt.Fprintf(w, "<li>LogonDomainID: %v</li>\n", ADCreds.LogonDomainID)
-			}
+			//ADCreds := new(credentials.ADCredentials)
+			ADCreds := ADCredsJSON.(credentials.ADCredentials)
+			//err := json.Unmarshal(aj, ADCreds)
+			//if err == nil {
+			// Now access the fields of the ADCredentials struct. For example:
+			fmt.Fprintf(w, "<li>EffectiveName: %v</li>\n", ADCreds.EffectiveName)
+			fmt.Fprintf(w, "<li>FullName: %v</li>\n", ADCreds.FullName)
+			fmt.Fprintf(w, "<li>UserID: %v</li>\n", ADCreds.UserID)
+			fmt.Fprintf(w, "<li>PrimaryGroupID: %v</li>\n", ADCreds.PrimaryGroupID)
+			fmt.Fprintf(w, "<li>Group SIDs: %v</li>\n", ADCreds.GroupMembershipSIDs)
+			fmt.Fprintf(w, "<li>LogOnTime: %v</li>\n", ADCreds.LogOnTime)
+			fmt.Fprintf(w, "<li>LogOffTime: %v</li>\n", ADCreds.LogOffTime)
+			fmt.Fprintf(w, "<li>PasswordLastSet: %v</li>\n", ADCreds.PasswordLastSet)
+			fmt.Fprintf(w, "<li>LogonServer: %v</li>\n", ADCreds.LogonServer)
+			fmt.Fprintf(w, "<li>LogonDomainName: %v</li>\n", ADCreds.LogonDomainName)
+			fmt.Fprintf(w, "<li>LogonDomainID: %v</li>\n", ADCreds.LogonDomainID)
+			//}
 		}
 		fmt.Fprint(w, "</ul>")
 	} else {
@@ -110,3 +121,42 @@ func testAppHandler(w http.ResponseWriter, r *http.Request) {
 	fmt.Fprint(w, "</html>")
 	return
 }
+
+type SessionMgr struct {
+	skey       []byte
+	store      sessions.Store
+	cookieName string
+}
+
+func NewSessionMgr(cookieName string) SessionMgr {
+	skey := []byte("thisistestsecret") // Best practice is to load this key from a secure location.
+	return SessionMgr{
+		skey:       skey,
+		store:      sessions.NewCookieStore(skey),
+		cookieName: cookieName,
+	}
+}
+
+func (smgr SessionMgr) Get(r *http.Request, k string) ([]byte, error) {
+	s, err := smgr.store.Get(r, smgr.cookieName)
+	if err != nil {
+		return nil, err
+	}
+	if s == nil {
+		return nil, errors.New("nil session")
+	}
+	b, ok := s.Values[k].([]byte)
+	if !ok {
+		return nil, fmt.Errorf("could not get bytes held in session at %s", k)
+	}
+	return b, nil
+}
+
+func (smgr SessionMgr) New(w http.ResponseWriter, r *http.Request, k string, v []byte) error {
+	s, err := smgr.store.New(r, smgr.cookieName)
+	if err != nil {
+		return fmt.Errorf("could not get new session from session manager: %v", err)
+	}
+	s.Values[k] = v
+	return s.Save(r, w)
+}

+ 2 - 1
v8/examples/httpServer.go

@@ -4,6 +4,7 @@ package main
 
 import (
 	"encoding/hex"
+	"errors"
 	"fmt"
 	"log"
 	"net/http"
@@ -85,7 +86,7 @@ func (smgr SessionMgr) Get(r *http.Request, k string) ([]byte, error) {
 		return nil, err
 	}
 	if s == nil {
-		return nil, error.New("nil session")
+		return nil, errors.New("nil session")
 	}
 	b, ok := s.Values[k].([]byte)
 	if !ok {

+ 6 - 2
v8/spnego/http.go

@@ -47,10 +47,14 @@ type teeReadCloser struct {
 	io.Closer
 }
 
-// NewClient returns an SPNEGO enabled HTTP client.
+// NewClient returns a SPNEGO enabled HTTP client.
+// Be careful when passing in the *http.Client if it is beginning reused in multiple calls to this function.
+// Ensure reuse of the provided *http.Client is for the same user as a session cookie may have been added to
+// http.Client's cookie jar.
+// Incorrect reuse of the provided *http.Client could lead to access to the wrong user's session.
 func NewClient(krb5Cl *client.Client, httpCl *http.Client, spn string) *Client {
 	if httpCl == nil {
-		httpCl = http.DefaultClient
+		httpCl = &http.Client{}
 	}
 	// Add a cookie jar if there isn't one
 	if httpCl.Jar == nil {