Skip to content
Snippets Groups Projects
matcher.go 5.92 KiB
Newer Older
  • Learn to ignore specific revisions
  • package matcher
    
    import (
    	"bytes"
    	"fmt"
    	"html/template"
    	"io/ioutil"
    	"net/http"
    	"regexp"
    	"time"
    
    
    Nicolas Pernoud's avatar
    Nicolas Pernoud committed
    	"forge.grandlyon.com/npernoud/glcpro/internal/apientreprise"
    	"forge.grandlyon.com/npernoud/glcpro/internal/franceconnect"
    
    	"forge.grandlyon.com/npernoud/glcpro/internal/mandate"
    
    Nicolas Pernoud's avatar
    Nicolas Pernoud committed
    	"forge.grandlyon.com/npernoud/glcpro/internal/oidcserver"
    
    	"forge.grandlyon.com/npernoud/glcpro/pkg/common"
    
    	"forge.grandlyon.com/npernoud/glcpro/pkg/email"
    
    	"forge.grandlyon.com/npernoud/glcpro/pkg/tokens"
    )
    
    var (
    
    	mailTemplate = "mailtemplate.html"
    	mailConfig   = email.EmailConfig{
    		Username:   common.StringValueFromEnv("EMAIL_SENDER_ADDRESS", ""),
    		Password:   common.StringValueFromEnv("EMAIL_SENDER_PASSWORD", ""),
    		ServerHost: common.StringValueFromEnv("EMAIL_SMTP_SERVER", ""),
    		ServerPort: common.StringValueFromEnv("EMAIL_SMTP_PORT", ""),
    		SenderAddr: common.StringValueFromEnv("EMAIL_SENDER_ADDRESS", ""),
    	}
    	mailSender = email.NewSender(mailConfig)
    	emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
    
    )
    
    type MandateDemand struct {
    	AskerSub string
    	Sirent   string
    }
    
    // CreateMatcherServer creates the "matcher", being the server that handles the mandate requessts
    func CreateMatcherServer() *http.ServeMux {
    	mux := http.NewServeMux()
    	mux.HandleFunc("/demand", func(w http.ResponseWriter, r *http.Request) {
    
    		// Work out who the user is and for what sirent he wants a mandate for
    		rd := oidcserver.ClientRequestData{}
    		_, err := tokens.ExtractAndValidateToken(r, "ClientRequestData", &rd, false)
    		fmt.Printf("Request Data:%v\n", rd)
    		if err != nil {
    			http.Error(w, "could not get the initial client request data from cookie", http.StatusInternalServerError)
    			return
    		}
    		// Work out the email of the company CEO
    		email, err := ioutil.ReadAll(r.Body)
    		if err != nil || !isEmailValid(string(email)) {
    			http.Error(w, "email adress is invalid", http.StatusBadRequest)
    			return
    		}
    
    		// Bundle all that into a token
    		m := MandateDemand{AskerSub: rd.Id.Sub, Sirent: rd.Sirent}
    		token, err := tokens.CreateToken(m, time.Now().Add(time.Hour*24*31))
    		if err != nil {
    			http.Error(w, "could not create demand token", http.StatusBadRequest)
    			return
    		}
    
    		// Send a mail to the CEO with the aforementionned token
    
    		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
    		}
    
    	})
    
    	mux.HandleFunc("/validate", func(w http.ResponseWriter, r *http.Request) {
    
    		// Unpack the token to work out who the mandate is for
    
    		md := MandateDemand{}
    		tokens.ExtractAndValidateToken(r, "", &md, false)
    
    		// Store the demand in a cookie
    
    		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: 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
    		franceconnect.RedirectToFranceConnect(w, r)
    
    	})
    
    	mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
    		// Get the mandater identity from
    		i, err := franceconnect.HandleCallBack(w, r)
    		if err != nil {
    			http.Error(w, err.Error(), http.StatusInternalServerError)
    			return
    		}
    		// Get the redirect url from the cookie
    		md := MandateDemand{}
    		_, err = tokens.ExtractAndValidateToken(r, "MandateDemand", &md, false)
    		if err != nil {
    			http.Error(w, "could not get the mandate demand data from cookie", http.StatusInternalServerError)
    			return
    		}
    
    		// Check that the France Connect user can create a mandate : he is the CEO of the company or has a transitive mandate
    		// Get the data from the API Entreprise
    		e, _, err := apientreprise.GetData(md.Sirent)
    		if err != nil {
    			http.Error(w, err.Error(), http.StatusInternalServerError)
    			return
    		}
    
    		// TODO : Iterate on all MandatairesSociaux and clean the big if, refactor with oidcserver
    		if len(e.MandatairesSociaux) < 1 {
    			http.Error(w, "there is no Mandataires Sociaux for this SIREN/T", http.StatusOK)
    			return
    		}
    		if e.MandatairesSociaux[0].Nom == i.FamilyName || e.MandatairesSociaux[0].Prenom == i.GivenName || e.MandatairesSociaux[0].DateNaissance == i.Birthdate {
    			// Create the mandate
    			mandate.Create(md.Sirent, md.AskerSub, i.Sub)
    
    Nicolas Pernoud's avatar
    Nicolas Pernoud committed
    			http.Redirect(w, r, "/mandatecreated", http.StatusFound)
    
    			return
    		}
    		w.Write([]byte("the mandate could not be created, you do not seem to be the company CEO"))
    
    		// TODO : Inform the original asker that is demand has been validated/rejected
    
    	return mux
    }
    
    // isEmailValid checks if the email provided passes the required structure and length.
    func isEmailValid(e string) bool {
    	if len(e) < 3 && len(e) > 254 {
    		return false
    	}
    	return emailRegex.MatchString(e)
    }
    
    
    Nicolas Pernoud's avatar
    Nicolas Pernoud committed
    func sendMailToMandater(email string, mandate string, sirent string, token string, hostname string) error {
    
    	// Receiver email address.
    	to := []string{
    		email,
    	}
    
    
    Nicolas Pernoud's avatar
    Nicolas Pernoud committed
    	t, _ := template.ParseFiles(mailTemplate)
    
    
    	var body bytes.Buffer
    
    	mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
    	body.Write([]byte(fmt.Sprintf("Subject: GLC PRO - Demande de validation de mandatement \n%s\n\n", mimeHeaders)))
    
    	t.Execute(&body, struct {
    
    Nicolas Pernoud's avatar
    Nicolas Pernoud committed
    		Mandate  string
    		Sirent   string
    		Token    string
    		Hostname string
    
    Nicolas Pernoud's avatar
    Nicolas Pernoud committed
    		Mandate:  mandate,
    		Sirent:   sirent,
    		Token:    token,
    		Hostname: hostname,
    
    	})
    
    	// 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
    }