Skip to content
Snippets Groups Projects
main.go 10.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Nicolas PERNOUD's avatar
    Nicolas PERNOUD committed
    package main
    
    import (
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    	"encoding/json"
    
    Nicolas PERNOUD's avatar
    Nicolas PERNOUD committed
    	"flag"
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    	"io/ioutil"
    
    Nicolas PERNOUD's avatar
    Nicolas PERNOUD committed
    	"net/http"
    
    	"net/url"
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    	"reflect"
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    	"os"
    
    Nicolas PERNOUD's avatar
    Nicolas PERNOUD committed
    	"strconv"
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    	"strings"
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    
    	log "github.com/sirupsen/logrus"
    
    Yoan VALLET's avatar
    Yoan VALLET committed
    	httpPort    		= flag.Int("http_port", LookupEnvOrInt("HTTP_PORT", 80), "HTTP port to serve on (defaults to 80)")
    	logLevel    		= flag.String("loglevel", LookupEnvOrString("LOGLEVEL", "debug"), "log level (debug, info, warning, error) (defaults to debug)")
    	cozyDomain 			= flag.String("cozy_domain", LookupEnvOrString("COZY_DOMAIN", "cozy.wf.alpha.grandlyon.com"), "Cozy domain (defaults to cozy.wf.alpha.grandlyon.com)")
    
    	cozyRedirectURI 	= flag.String("cozy_redirect_uri", LookupEnvOrString("COZY_REDIRECT_URI", "/accounts/enedisgrandlyon/redirect"), "Cozy redirect URI (defaults to /accounts/enedisgrandlyon/redirect)")
    
    type TokenResponse struct {
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    	AccessToken          string `json:"access_token"`
    	TokenType            string `json:"token_type"`
    	ExpiresIn            int    `json:"expires_in"`
    	RefreshToken         string `json:"refresh_token"`
    	Scope                string `json:"scope"`
    	RefreshTokenIssuedAt string `json:"refresh_token_issued_at"`
    	IssueAt              string `json:"issued_at"`
    	UsagePointId         string `json:"usage_points_id"`
    
    func LookupEnvOrString(key string, defaultVal string) string {
    	if val, ok := os.LookupEnv(key); ok {
    		return val
    	}
    	return defaultVal
    }
    
    func LookupEnvOrInt(key string, defaultVal int) int {
    	if val, ok := os.LookupEnv(key); ok {
    		v, err := strconv.Atoi(val)
    		if err != nil {
    			log.Fatalf("LookupEnvOrInt[%s]: %v", key, err)
    		}
    		return v
    	}
    	return defaultVal
    }
    
    
    func findItem(arrayType interface{}, item interface{}) bool {
    	arr := reflect.ValueOf(arrayType)
    	if arr.Kind() != reflect.Array {
    		panic("Invalid data-type")
    	}
    	for i := 0; i < arr.Len(); i++ {
    		if arr.Index(i).Interface() == item {
    			return true
    		}
    	}
    
    	return false
    }
    
    
    Nicolas PERNOUD's avatar
    Nicolas PERNOUD committed
    func main() {
    	// Parse the flags
    	flag.Parse()
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    
    	// Init logging
    	log.SetOutput(os.Stdout)
    	log.SetFormatter(&log.TextFormatter{
    		PadLevelText:     true,
    		ForceQuote:       true,
    		DisableTimestamp: false,
    		FullTimestamp:    true,
    		TimestampFormat:  "2006-01-02 15:04:05",
    	})
    
    
    	// Configure log level
    	switch strings.ToLower(*logLevel) {
    	case "error":
    		log.SetLevel(log.ErrorLevel)
    	case "warning":
    		log.SetLevel(log.WarnLevel)
    	case "info":
    		log.SetLevel(log.InfoLevel)
    	case "debug":
    		log.SetLevel(log.DebugLevel)
    	default:
    		log.SetLevel(log.DebugLevel)
    		log.Fatalf("Unknown logging level %s. Choose between debug, info, warning or error.", *logLevel)
    	}
    
    Nicolas PERNOUD's avatar
    Nicolas PERNOUD committed
    	mux := http.NewServeMux()
    
    	log.Infof("Starting Server on port %d\n", *httpPort)
    
    	mux.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) {
    		io.WriteString(w, "OK\n")
    	})
    
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    	// ENEDIS AUTH ENDPOINT
    
    	mux.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    		log.Debug("New auth request")
    
    		query := r.URL.Query()
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    		log.Debug("Query received - ", query)
    
    
    		clientId := query.Get("client_id")
    		state := query.Get("state")
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    		responseType := "code"
    
    Yoan VALLET's avatar
    Yoan VALLET committed
    		// here we use the redirect_uri param to transmit our stack url
    		// We keep only the instance name to not reach the 100 max char of redirectUrl
    		cozyOrigin := query.Get("redirect_uri")
    		splitIndexStart := strings.Index(cozyOrigin, ":")
    		if splitIndexStart == -1 {
    			log.Error("redirect_uri bad format " + cozyOrigin)
    			http.Error(w, http.StatusText(500), 500)
    		}
    		splitIndexEnd := strings.Index(cozyOrigin, ".")
    		if splitIndexEnd == -1 {
    			log.Error("redirect_uri bad format " + cozyOrigin)
    			http.Error(w, http.StatusText(500), 500)
    		}
    		instanceName := cozyOrigin[splitIndexStart+3:splitIndexEnd]
    
    		// DEV API
    
    Yoan VALLET's avatar
    Yoan VALLET committed
    		// authURL := "https://gw.hml.api.enedis.fr/dataconnect/v1/oauth2/authorize"
    
    		// PROD API
    
    Yoan VALLET's avatar
    Yoan VALLET committed
    		authURL := "https://mon-compte-particulier.enedis.fr/dataconnect/v1/oauth2/authorize"
    
    		redirectUrl := authURL + "?client_id=" + clientId + "&duration=P6M&response_type=" + responseType + "&state=" + state + "-" + instanceName
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    		log.Debug("Redirect to - ", redirectUrl)
    
    		http.Redirect(w, r, redirectUrl, 302)
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    	// GRDF ADICT ACCESS_TOKEN ENDPOINT
    	mux.HandleFunc("/grdfAccess", func(w http.ResponseWriter, r *http.Request) {
    		log.Debug("New grdf auth request")
    		query := r.URL.Query()
    		log.Debug("Query received - ", query)
    
    		clientId := query.Get("client_id")
    		state := query.Get("state")
    		scope := "/adict/v1"
    		// here we use the redirect_uri param to transmit our stack url
    		// We keep only the instance name to not reach the 100 max char of redirectUrl
    		cozyOrigin := query.Get("redirect_uri")
    		splitIndexStart := strings.Index(cozyOrigin, ":")
    		if splitIndexStart == -1 {
    			log.Error("redirect_uri bad format " + cozyOrigin)
    			http.Error(w, http.StatusText(500), 500)
    		}
    		splitIndexEnd := strings.Index(cozyOrigin, ".")
    		if splitIndexEnd == -1 {
    			log.Error("redirect_uri bad format " + cozyOrigin)
    			http.Error(w, http.StatusText(500), 500)
    		}
    		instanceName := cozyOrigin[splitIndexStart+3:splitIndexEnd]
    
    		// DEV API
    		// authURL := "https://gw.hml.api.enedis.fr/dataconnect/v1/oauth2/authorize"
    		// PROD API
    		authURL := "https://mon-compte-particulier.enedis.fr/dataconnect/v1/oauth2/authorize"
    
    		redirectUrl := authURL + "?client_id=" + clientId + "&duration=P6M&response_type=" + responseType + "&state=" + state + "-" + instanceName
    
    		log.Debug("Redirect to - ", redirectUrl)
    		http.Redirect(w, r, redirectUrl, 302)
    	})
    
    	//ENEDIS REDIRECT ENDPOINT
    
    	mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    		log.Debug("New redirect request")
    
    		query := r.URL.Query()
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    		log.Debug(query)
    
    
    		code := query.Get("code")
    
    		req_state := query.Get("state")
    
    		statusCodes := [4]string{"400", "403", "500", "503"}
    
    		if (findItem(statusCodes, code)) {
    			intCode, err := strconv.Atoi(code)
    			if err != nil {
    				log.Print("status code string to int error: ", err)
    			}
    			log.Print("status code error : ", code)
    			http.Error(w, http.StatusText(intCode), intCode)
    		} else {
    			splitIndex := strings.Index(req_state, "-")
    			if splitIndex == -1 {
    				log.Warning("No host found")
    			}
    			state := req_state[0:splitIndex]
    			host := req_state[splitIndex+1:]
    
    			usagePointId := query.Get("usage_point_id")
    
    			cozyURL := "https://" + host + "." + *cozyDomain + *cozyRedirectURI
    
    			redir := cozyURL + "?code=" + code + "&state=" + state + "&usage_point_id=" + usagePointId
    			log.Debug("Redirect to -", redir)
    			http.Redirect(w, r, redir, 302)
    		}
    
    Hugo NOUTS's avatar
    Hugo NOUTS committed
    	//ENEDIS TOKEN ENDPOINT
    
    	mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    		log.Debug("New token request")
    
    		query := r.URL.Query()
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    		log.Debug(query)
    
    		clientId := ""
    		clientSecret := ""
    		code := ""
    		grantType := ""
    		refreshToken := ""
    
    		// For request token params are into query parameters
    
    		if len(query) == 0 {
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    			log.Warn("No params found in url query - Trying to catch them from body")
    
    			contents, err := ioutil.ReadAll(r.Body)
    			if err != nil {
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    				log.Error(err)
    
    			}
    			pageContent := string(contents)
    			//Check for client_id
    			clientIdStartIndex := strings.Index(pageContent, "client_id=")
    			if clientIdStartIndex == -1 {
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    				log.Error("No client_id found")
    
    				http.Error(w, http.StatusText(500), 500)
    			}
    			clientIdStartIndex += 10
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    			clientId = pageContent[clientIdStartIndex : clientIdStartIndex+36]
    
    			//Check for client_secret
    			clientSecretStartIndex := strings.Index(pageContent, "client_secret=")
    			if clientSecretStartIndex == -1 {
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    				log.Error("No client_secret found")
    
    				http.Error(w, http.StatusText(500), 500)
    			}
    			clientSecretStartIndex += 14
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    			clientSecret = pageContent[clientSecretStartIndex : clientSecretStartIndex+36]
    
    			//Check for code
    			codeStartIndex := strings.Index(pageContent, "code=")
    			if codeStartIndex == -1 {
    
    				log.Info("No code found (optional param)")
    
    			} else {
    				codeStartIndex += 5
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    				code = pageContent[codeStartIndex : codeStartIndex+30]
    
    			}
    			//Check for grant_type
    			grandTypeStartIndex := strings.Index(pageContent, "grant_type=")
    			if grandTypeStartIndex == -1 {
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    				log.Error("No grant_type found")
    
    				http.Error(w, http.StatusText(500), 500)
    			}
    			grandTypeStartIndex += 11
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    			tempGrandTypeString := pageContent[grandTypeStartIndex:]
    
    			grandTypeEndIndex := strings.Index(tempGrandTypeString, "&")
    			if grandTypeEndIndex == -1 {
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    				log.Error("No closing tag for grant_type found")
    
    				http.Error(w, http.StatusText(500), 500)
    			}
    			grantType = tempGrandTypeString[0:grandTypeEndIndex]
    			//Check for refresh_token
    			refershTokenStartIndex := strings.Index(pageContent, "refresh_token=")
    			if refershTokenStartIndex == -1 {
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    				log.Error("No refresh_token found")
    
    				http.Error(w, http.StatusText(500), 500)
    
    			}
    			refershTokenStartIndex += 14
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    			refreshToken = pageContent[refershTokenStartIndex : refershTokenStartIndex+46]
    
    			// Retrieve params from query
    
    			clientId = query.Get("client_id")
    			clientSecret = query.Get("client_secret")
    			code = query.Get("code")
    			grantType = query.Get("grant_type")
    			refreshToken = query.Get("refresh_token")
    		}
    		// Print out the result
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    		log.WithFields(log.Fields{
    			"client_id":     clientId,
    			"client_secret": clientSecret,
    			"code":          code,
    			"grant_type":    grantType,
    			"refresh_token": refreshToken,
    		}).Debug("result")
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    
    
    		// DEV API
    
    Yoan VALLET's avatar
    Yoan VALLET committed
    		// tokenUrl := "https://gw.hml.api.enedis.fr/v1/oauth2/token"
    
    		// PROD API
    
    Yoan VALLET's avatar
    Yoan VALLET committed
    		tokenUrl := "https://gw.prd.api.enedis.fr/v1/oauth2/token"
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    
    
    		data := url.Values{}
    		data.Set("client_id", clientId)
    		data.Set("client_secret", clientSecret)
    		data.Set("code", code)
    		data.Set("grant_type", grantType)
    
    		if refreshToken != "" {
    			data.Set("refresh_token", refreshToken)
    			data.Set("grant_type", "refresh_token")
    		}
    
    		log.Debug("Send request to token endpoint: ", tokenUrl)
    
    		response, err := http.PostForm(tokenUrl, data)
    
    		if err != nil {
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    			log.Error(err)
    
    		} else {
    
    			log.Debug("Endpoint response with status", response.Status)
    
    			defer response.Body.Close()
    
    			if response.StatusCode >= 200 && response.StatusCode <= 299 {
    				// Set Content-Type in response header
    				w.Header().Add("Content-Type", "application/json")
    
    				// Decode response Body using the defined type "TokenResponse"
    				data := TokenResponse{}
    				decodeError := json.NewDecoder(response.Body).Decode(&data)
    				if decodeError != nil {
    					http.Error(w, decodeError.Error(), 500)
    					return
    				}
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    
    
    				// Response with json data
    				jsonError := json.NewEncoder(w).Encode(data)
    				if jsonError != nil {
    					http.Error(w, jsonError.Error(), 500)
    					return
    				}
    			} else {
    				http.Error(w, http.StatusText(response.StatusCode), response.StatusCode)
    
    Sébastien Blaisot's avatar
    Sébastien Blaisot committed
    	log.Fatal(http.ListenAndServe(":"+strconv.Itoa(*httpPort), mux))
    
    Nicolas PERNOUD's avatar
    Nicolas PERNOUD committed
    }