package main import ( "encoding/json" "flag" "io" "io/ioutil" "net/http" "net/url" "reflect" "os" "strconv" "strings" log "github.com/sirupsen/logrus" ) var ( 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 { 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 } func main() { // Parse the flags flag.Parse() // 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) } 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") }) // ENEDIS AUTH ENDPOINT mux.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) { log.Debug("New auth request") query := r.URL.Query() log.Debug("Query received - ", query) clientId := query.Get("client_id") state := query.Get("state") responseType := "code" // 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) }) // 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) { log.Debug("New redirect request") query := r.URL.Query() 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) } }) //ENEDIS TOKEN ENDPOINT mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) { log.Debug("New token request") query := r.URL.Query() log.Debug(query) clientId := "" clientSecret := "" code := "" grantType := "" refreshToken := "" // For request token params are into query parameters if len(query) == 0 { log.Warn("No params found in url query - Trying to catch them from body") contents, err := ioutil.ReadAll(r.Body) if err != nil { log.Error(err) } pageContent := string(contents) //Check for client_id clientIdStartIndex := strings.Index(pageContent, "client_id=") if clientIdStartIndex == -1 { log.Error("No client_id found") http.Error(w, http.StatusText(500), 500) } clientIdStartIndex += 10 clientId = pageContent[clientIdStartIndex : clientIdStartIndex+36] //Check for client_secret clientSecretStartIndex := strings.Index(pageContent, "client_secret=") if clientSecretStartIndex == -1 { log.Error("No client_secret found") http.Error(w, http.StatusText(500), 500) } clientSecretStartIndex += 14 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 code = pageContent[codeStartIndex : codeStartIndex+30] } //Check for grant_type grandTypeStartIndex := strings.Index(pageContent, "grant_type=") if grandTypeStartIndex == -1 { log.Error("No grant_type found") http.Error(w, http.StatusText(500), 500) } grandTypeStartIndex += 11 tempGrandTypeString := pageContent[grandTypeStartIndex:] grandTypeEndIndex := strings.Index(tempGrandTypeString, "&") if grandTypeEndIndex == -1 { 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 { log.Error("No refresh_token found") http.Error(w, http.StatusText(500), 500) } refershTokenStartIndex += 14 refreshToken = pageContent[refershTokenStartIndex : refershTokenStartIndex+46] } else { // 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 log.WithFields(log.Fields{ "client_id": clientId, "client_secret": clientSecret, "code": code, "grant_type": grantType, "refresh_token": refreshToken, }).Debug("result") // DEV API // tokenUrl := "https://gw.hml.api.enedis.fr/v1/oauth2/token" // PROD API tokenUrl := "https://gw.prd.api.enedis.fr/v1/oauth2/token" 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 { 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 } // 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) } } }) log.Fatal(http.ListenAndServe(":"+strconv.Itoa(*httpPort), mux)) }