From 320d93c34f019ffda8b07c3d0328cf0c0485ab54 Mon Sep 17 00:00:00 2001
From: Nicolas Pernoud <github@ninico.fr>
Date: Wed, 30 Jun 2021 11:17:22 +0200
Subject: [PATCH] feat: completed tests on rootmux

---
 internal/franceconnect/mock.go   | 11 +++-
 internal/matcher/matcher.go      | 15 +++--
 internal/matcher/matcher_test.go |  6 +-
 internal/rootmux/rootmux_test.go | 99 ++++++++++++++++++++++++++++++--
 pkg/middlewares/middlewares.go   |  3 +-
 5 files changed, 118 insertions(+), 16 deletions(-)
 mode change 100644 => 100755 internal/franceconnect/mock.go
 mode change 100644 => 100755 internal/matcher/matcher.go
 mode change 100644 => 100755 internal/matcher/matcher_test.go
 mode change 100644 => 100755 internal/rootmux/rootmux_test.go

diff --git a/internal/franceconnect/mock.go b/internal/franceconnect/mock.go
old mode 100644
new mode 100755
index fc5770d..4881d39
--- a/internal/franceconnect/mock.go
+++ b/internal/franceconnect/mock.go
@@ -15,7 +15,12 @@ func CreateMock() *http.ServeMux {
 		redir := strings.Replace(query.Get("redirect_uri"), "/callback", "/api/oidc/callback", 1) + "?state=" + query.Get("state") + "&code=mock_code"
 		http.Redirect(w, r, redir, http.StatusFound)
 	})
-
+	// Returns authorization code back to the user for matcher use case
+	mux.HandleFunc("/auth2", func(w http.ResponseWriter, r *http.Request) {
+		query := r.URL.Query()
+		redir := strings.Replace(query.Get("redirect_uri"), "/callback", "/api/matcher/callback", 1) + "?state=" + query.Get("state") + "&code=mock_code"
+		http.Redirect(w, r, redir, http.StatusFound)
+	})
 	// Returns access token back to the user
 	mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Content-Type", "application/json")
@@ -26,6 +31,10 @@ func CreateMock() *http.ServeMux {
 		w.Header().Set("Content-Type", "application/json")
 		w.Write([]byte(`{"given_name":"Angela Claire Louise","family_name":"DUBOIS","birthdate":"1962-08-24","gender":"female","birthplace":"75107","birthcountry":"99100","preferred_username":"","sub":"b6048e95bb134ec5b1d1e1fa69f287172e91722b9354d637a1bcf2ebb0fd2ef5v1"}`))
 	})
+	mux.HandleFunc("/userinfo2", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		w.Write([]byte(`{"given_name":"Paul Louis","family_name":"DUPONT","birthdate":"1962-08-24","gender":"male","birthplace":"75107","birthcountry":"99100","preferred_username":"","sub":"dcc2d409424c519ae0599c8585b585711020bd4035b91633e9eabaa9b7542721v1"}`))
+	})
 	// Logout
 	mux.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprint(w, "Logout OK")
diff --git a/internal/matcher/matcher.go b/internal/matcher/matcher.go
old mode 100644
new mode 100755
index 8dd7b24..d5166a0
--- a/internal/matcher/matcher.go
+++ b/internal/matcher/matcher.go
@@ -65,7 +65,7 @@ func CreateMatcherServer() *http.ServeMux {
 		}
 
 		// Send a mail to the CEO with the aforementionned token
-		err = sendMailToMandater(string(email), rd.Id.GivenName+" "+rd.Id.FamilyName, rd.Sirent, token, r.Host)
+		err = sendMailToMandater(string(email), rd.Id.GivenName+" "+rd.Id.FamilyName, rd.Sirent, token, common.GetDomain(r))
 		if err != nil {
 			http.Error(w, "could not send email", http.StatusBadRequest)
 			return
@@ -80,10 +80,10 @@ func CreateMatcherServer() *http.ServeMux {
 		tokens.ExtractAndValidateToken(r, "", &md, false)
 
 		// Store the demand in a cookie
-		tokens.CreateCookie(md, r.Host, "MandateDemand", 60*time.Second, w)
+		tokens.CreateCookie(md, common.GetDomain(r), "MandateDemand", 60*time.Second, w)
 
 		// Set a cookie to redirect to the needed callback route from the front end generic callback
-		cookie := http.Cookie{Name: "callbackRoute", Domain: r.Host, Path: "/", Value: "/api/matcher/callback", MaxAge: 60, Secure: false, HttpOnly: false, SameSite: http.SameSiteLaxMode}
+		cookie := http.Cookie{Name: "callbackRoute", Domain: common.GetDomain(r), Path: "/", Value: "/api/matcher/callback", MaxAge: 60, Secure: false, HttpOnly: false, SameSite: http.SameSiteLaxMode}
 		http.SetCookie(w, &cookie)
 
 		// Perform an France Connect authentication ... and wait for the callback
@@ -126,7 +126,6 @@ func CreateMatcherServer() *http.ServeMux {
 			return
 		}
 		w.Write([]byte("the mandate could not be created, you do not seem to be the company CEO"))
-		return
 
 		// TODO : Inform the original asker that is demand has been validated/rejected
 	})
@@ -170,3 +169,11 @@ func sendMailToMandater(email string, mandate string, sirent string, token strin
 	// Sending email.
 	return mailSender.Send(to, body.Bytes())
 }
+
+// SetTestMailSender allows overriding mail sender for test purposes
+func SetTestModeAndReturnRecorder() *email.EmailRecorder {
+	mailTemplate = "../../mailtemplate.html"
+	sender, recorder := email.NewMockSender()
+	mailSender = sender
+	return recorder
+}
diff --git a/internal/matcher/matcher_test.go b/internal/matcher/matcher_test.go
old mode 100644
new mode 100755
index 5a9371e..c19f93c
--- a/internal/matcher/matcher_test.go
+++ b/internal/matcher/matcher_test.go
@@ -3,14 +3,10 @@ package matcher
 import (
 	"strings"
 	"testing"
-
-	"forge.grandlyon.com/npernoud/glcpro/pkg/email"
 )
 
 func Test_sendMailToMandater(t *testing.T) {
-	mailTemplate = "../../mailtemplate.html"
-	sender, recorder := email.NewMockSender()
-	mailSender = sender
+	recorder := SetTestModeAndReturnRecorder()
 	type args struct {
 		email   string
 		mandate string
diff --git a/internal/rootmux/rootmux_test.go b/internal/rootmux/rootmux_test.go
old mode 100644
new mode 100755
index 771c60b..5970056
--- a/internal/rootmux/rootmux_test.go
+++ b/internal/rootmux/rootmux_test.go
@@ -2,6 +2,7 @@ package rootmux
 
 import (
 	"encoding/base64"
+	"fmt"
 	"net/http/cookiejar"
 	"net/http/httptest"
 	"net/url"
@@ -13,17 +14,21 @@ import (
 
 	"forge.grandlyon.com/npernoud/glcpro/internal/apientreprise"
 	"forge.grandlyon.com/npernoud/glcpro/internal/franceconnect"
+	"forge.grandlyon.com/npernoud/glcpro/internal/matcher"
 	"forge.grandlyon.com/npernoud/glcpro/pkg/tester"
 	"forge.grandlyon.com/npernoud/glcpro/pkg/tokens"
 )
 
 var (
-	noH map[string]string
+	noH               map[string]string
+	fcServer          *httptest.Server
+	clientRedirectURI = "http://client.org"
+	tokenMustContent  = `"id_token":{"given_name":"Angela`
 )
 
 func TestMain(m *testing.M) {
 	// Create the france connect mock server
-	fcServer := httptest.NewServer(franceconnect.CreateMock())
+	fcServer = httptest.NewServer(franceconnect.CreateMock())
 	defer fcServer.Close()
 	// Setup to use the france connect mock server
 	os.Setenv("FC_AUTH", fcServer.URL+"/auth")
@@ -37,6 +42,8 @@ func TestMain(m *testing.M) {
 	apientreprise.Init("../../configs/apicache.json")
 
 	code := m.Run()
+	// Remove the database
+	os.Remove("./glcpro.db")
 	os.Exit(code)
 }
 
@@ -55,18 +62,22 @@ func createTester(t *testing.T) (*httptest.Server, tester.DoFn, tester.DoFn) {
 // Test the use case 1 : "Cas d'usage 1 : Accès à une démarche depuis un portail de service public pour un dirigeant"
 func TestUseCase1(t *testing.T) {
 
-	clientRedirectURI := "http://client.org"
-
 	// Create the tester
 	_, do, _ := createTester(t)
 
 	// (We arrive from a client with a query, and the front end add the requested SIRENT to the query), we should be redirected to france connect, login, and be back with an authorisation code
 	r := do("GET", "/api/oidc/auth?scope=openid%20profile&client_id=A_RANDOM_ID&redirect_uri="+clientRedirectURI+"&response_type=code&state=A_RANDOM_STATE&sirent=000000001", noH, "", 302, "")
+	// We are redirected to France Connect
 	r = redirectURIFromBody(r)
+	fmt.Printf("Redirected to France Connect : %v\n", r)
 	r = do("GET", r, noH, "", 302, "")
+	// We are redirected to GLC Pro (France Connect callback)
 	r = redirectURIFromBody(r)
+	fmt.Printf("Redirected to GLC Pro callback : %v\n", r)
 	r = do("GET", r, noH, "", 302, "")
+	// We are redirected to the client
 	r = redirectURIFromBody(r)
+	fmt.Printf("Redirected to Client : %v\n", r)
 	if !strings.Contains(r, clientRedirectURI) {
 		t.Errorf("no redirection to the client")
 	}
@@ -75,9 +86,87 @@ func TestUseCase1(t *testing.T) {
 	tk := do("POST", "/api/oidc/token", noH, "client_id=A_RANDOM_ID&client_secret=A_RANDOM_SECRET&grant_type=authorization_code&code="+code, 200, "")
 	tk = regexp.MustCompile(`id_token=(.*)&scope.*`).FindStringSubmatch(tk)[1]
 	token, _ := base64.StdEncoding.DecodeString(tk)
-	if !strings.Contains(string(token), "Angela Claire Louise") || !strings.Contains(string(token), "THE TEST COMPANY") {
+	if !strings.Contains(string(token), tokenMustContent) || !strings.Contains(string(token), "THE TEST COMPANY") {
 		t.Errorf("id token is not complete")
 	}
+	fmt.Printf("Token : %v\n", string(token))
+}
+
+// Test the use case 2 : "Cas d'usage 2 : Demande d'habilitation par un dirigeant"
+func TestUseCase2(t *testing.T) {
+
+	// Create the tester
+	_, do, _ := createTester(t)
+
+	// Create the mock mail server
+	recorder := matcher.SetTestModeAndReturnRecorder()
+
+	/////////////////////////////////////
+	//  THE MANDATEE MAKES THE DEMAND  //
+	/////////////////////////////////////
+
+	// (We arrive from a client with a query, and the front end add the requested SIRENT to the query), we should be redirected to france connect, login, .. and do NOT get a code but be redirect on the mandate demand page
+	// Configure FC Mock to give the ID of Paul Louis Dupont
+	os.Setenv("FC_USER_INFO", fcServer.URL+"/userinfo2")
+	franceconnect.Init()
+	r := do("GET", "/api/oidc/auth?scope=openid%20profile&client_id=A_RANDOM_ID&redirect_uri="+clientRedirectURI+"&response_type=code&state=A_RANDOM_STATE&sirent=000000001", noH, "", 302, "")
+	// We are redirected to France Connect
+	r = redirectURIFromBody(r)
+	fmt.Printf("Redirected to France Connect : %v\n", r)
+	r = do("GET", r, noH, "", 302, "")
+	// We are redirected to GLC Pro (France Connect callback)
+	r = redirectURIFromBody(r)
+	fmt.Printf("Redirected to GLC Pro callback : %v\n", r)
+	r = do("GET", r, noH, "", 302, "")
+	// We are redirected to the matcher
+	r = redirectURIFromBody(r)
+	fmt.Printf("Redirected to GLC Pro matcher : %v\n", r)
+	// We are redirected to the matcher
+	if !strings.Contains(r, "/matcher") {
+		t.Errorf("no redirection to the matcher")
+	}
+	// Let's send a mail to the company CEO
+	do("POST", "/api/matcher/demand", noH, "angela@testcompany.com", 200, "")
+	fmt.Printf("Sent mail : %v\n", recorder.Msg())
+	if !strings.Contains(recorder.Msg(), "Demande de mandatement") {
+		t.Errorf("received body is not what is expected")
+	}
+
+	////////////////////////////////////////////
+	//  THE COMPANY CEO VALIDATES THE DEMAND  //
+	////////////////////////////////////////////
+
+	// Configure FC Mock to give the ID of Angela Claire Louise DUBOIS, configure the FC Auth to callback to the matcher (what is normaly made by the js front client)
+	os.Setenv("FC_USER_INFO", fcServer.URL+"/userinfo")
+	os.Setenv("FC_AUTH", fcServer.URL+"/auth2")
+	franceconnect.Init()
+	// Create a new tester with a new cookie jar (because we are someone else, on a different computer)
+	_, do, _ = createTester(t)
+	// Extract the link from the mail
+	r = do("GET", "/api/matcher/validate?code="+regexp.MustCompile(`code=(.*)"`).FindStringSubmatch(recorder.Msg())[1], noH, "", 302, "")
+	// We are redirected to France Connect
+	r = redirectURIFromBody(r)
+	fmt.Printf("Redirected to France Connect : %v\n", r)
+	r = do("GET", r, noH, "", 302, "")
+	fmt.Printf("Body 5 : %v\n", r)
+	r = redirectURIFromBody(r)
+	r = do("GET", r, noH, "", 302, "")
+	fmt.Printf("Body 6 : %v\n", r)
+	r = redirectURIFromBody(r)
+	if r != "/mandatecreated" {
+		t.Errorf("CEO was not redirected to the mandate created information")
+	}
+
+	///////////////////////////////////////////////////
+	//  THE MANDATEE USE THE NEWLY OBTAINED MANDATE  //
+	///////////////////////////////////////////////////
+
+	// It's actually the use case 1, only with France Connect mock giving the identity of Paul Louis Dubois
+	os.Setenv("FC_USER_INFO", fcServer.URL+"/userinfo2")
+	os.Setenv("FC_AUTH", fcServer.URL+"/auth")
+	franceconnect.Init()
+	tokenMustContent = `"id_token":{"given_name":"Paul`
+	TestUseCase1(t)
 }
 
 func redirectURIFromBody(body string) string {
diff --git a/pkg/middlewares/middlewares.go b/pkg/middlewares/middlewares.go
index f219afc..804baad 100644
--- a/pkg/middlewares/middlewares.go
+++ b/pkg/middlewares/middlewares.go
@@ -34,7 +34,7 @@ type webSecurityWriter struct {
 }
 
 func (s webSecurityWriter) WriteHeader(code int) {
-	if s.wroteHeader == false {
+	if !s.wroteHeader {
 		s.w.Header().Set("Strict-Transport-Security", "max-age=63072000")
 		var inline string
 		if s.allowEvalInlineScript {
@@ -57,6 +57,7 @@ func (s webSecurityWriter) WriteHeader(code int) {
 		s.w.Header().Set("X-XSS-Protection", "1; mode=block")
 		s.w.Header().Set("Referrer-Policy", "strict-origin")
 		s.w.Header().Set("X-Content-Type-Options", "nosniff")
+		//lint:ignore SA4005 we need to assign true so that when the WriteHeader method will be used again, we won't rewrite security headers
 		s.wroteHeader = true
 	}
 	s.w.WriteHeader(code)
-- 
GitLab