package main import ( "encoding/json" "encoding/base64" "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)") cozyGrdfRedirectURI = flag.String("cozy_grdf_redirect_uri", LookupEnvOrString("COZY_GRDF_REDIRECT_URI", "/accounts/grdfgrandlyon/redirect"), "Cozy redirect URI (defaults to /accounts/grdfgrandlyon/redirect)") ) type EnedisTokenResponse 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"` } type GrdfConsentement []struct { Pce string `json:"pce"` IdAccreditation string `json:"id_accreditation"` } type GrdfConsentementToken struct { AtHash string `json:"at_hash"` Sub string `json:"sub"` AuditTrackingId string `json:"auditTrackingId"` Iss string `json:"iss"` TokenName string `json:"tokenName"` Aud string `json:"aud"` CHash string `json:"c_hash"` Acr string `json:"acr"` Azp string `json:"azp"` AuthYime int `json:"auth_time"` Realm string `json:"realm"` Consentements string `json:"consentements"` Exp int `json:"exp"` TokenType string `json:"tokenType"` Iat int `json:"iat"` } type GrdfTokenResponse struct { AccessToken string `json:"access_token"` IdToken string `json:"id_token"` TokenType string `json:"token_type"` ExpiresIn int `json:"expires_in"` Scope string `json:"scope"` Pce string `json:"pce"` } 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 AUTHORIZE ENDPOINT mux.HandleFunc("/grdf_authorize", 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") 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] redirectProxy := "https://oauth-proxy.wf.alpha.grandlyon.com/redirect-grdf" authURL := "https://sofit-sso-oidc.grdf.fr/openam/oauth2/realms/externeGrdf/authorize" redirectUrl := authURL + "?client_id=" + clientId + "&scope=openid&response_type=code&redirect_uri="+ redirectProxy + "&login_hint=Sebastien|Riera|sebastien.riera@laposte.net|Ecolyo&state=" + state + "-" + instanceName // TODO Add Login Hint in request 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) } }) //GRDF REDIRECT ENDPOINT mux.HandleFunc("/redirect-grdf", func(w http.ResponseWriter, r *http.Request) { log.Debug("New redirection on grdf-redirect") query := r.URL.Query() log.Debug(query) code := query.Get("code") req_state := query.Get("state") //TODO Get pce_id splitIndex := strings.Index(req_state, "-") if splitIndex == -1 { log.Warning("No host found") } state := req_state[0:splitIndex] host := req_state[splitIndex+1:] cozyURL := "https://" + host + "." + *cozyDomain + *cozyGrdfRedirectURI redir := cozyURL + "?code=" + code + "&state=" + state 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 := EnedisTokenResponse{} 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) } } }) //GRDF TOKEN ENDPOINT mux.HandleFunc("/grdf_token", func(w http.ResponseWriter, r *http.Request) { log.Debug("New GRDF token request") query := r.URL.Query() log.Debug(query) clientId := "" clientSecret := "" code := "" grantType := "" scope := "" redirectUri := "https://oauth-proxy.wf.alpha.grandlyon.com/redirect-grdf" pce := "" // For request token params are into query parameters if len(query) == 0 { log.Warn("No params found in url query \nStack probably asks for a refresh token \nTrying 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] } else { // Retrieve params from query clientId = query.Get("client_id") clientSecret = query.Get("client_secret") code = query.Get("code") grantType = query.Get("grant_type") } // Print out the result log.WithFields(log.Fields{ "client_id": clientId, "client_secret": clientSecret, "code": code, "grant_type": grantType, "redirect_uri": redirectUri, "scope": scope, }).Debug("result") tokenUrl := "https://sofit-sso-oidc.grdf.fr/openam/oauth2/realms/externeGrdf/access_token" if grantType != "refresh_token"{ // Call GRDF access_token endpoint with code & grant_type = "authorization_code" data := url.Values{} data.Set("client_id", clientId) data.Set("client_secret", clientSecret) data.Set("grant_type", "authorization_code") data.Set("redirect_uri", redirectUri) data.Set("code", code) log.Debug("data sent is : ", data) 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 { // Decode response Body using the defined type "GrdfTokenResponse" data := GrdfTokenResponse{} decodeError := json.NewDecoder(response.Body).Decode(&data) if decodeError != nil { http.Error(w, decodeError.Error(), 500) return } // Check if AccessToken exist // Decode the token and retrieve the pce from it if len(data.AccessToken) > 0 { log.Debug("Access Token : ", data.AccessToken) s := strings.Split(data.AccessToken, ".") if len(s[1]) > 0 { log.Debug("Split Access Token : ", s[1]) payload, _ := base64.StdEncoding.DecodeString(s[1]) log.Debug(string(payload)) // Decode the payload from the token var token GrdfConsentementToken err := json.Unmarshal(payload, &token) if err != nil { log.Error(err.Error()) http.Error(w, err.Error(), 500) return } log.Debug("Consentements found : ", token.Consentements) // Decode the consentement information if len(token.Consentements) > 0 { var consentements GrdfConsentement err2 := json.Unmarshal([]byte(token.Consentements), &consentements) if err2 != nil { log.Error(err2.Error()) http.Error(w, err2.Error(), 500) return } if len(consentements[0].Pce) > 0 { pce = consentements[0].Pce } } } } if len(pce) <= 0 { log.Error("No PCE found") http.Error(w, http.StatusText(500), 500) return } } else { http.Error(w, http.StatusText(response.StatusCode), response.StatusCode) } } } // Call GRDF access_token endpoint with scope & grant_type = "client_credentials" data2 := url.Values{} data2.Set("client_id", clientId) data2.Set("client_secret", clientSecret) data2.Set("grant_type", "client_credentials") data2.Set("redirect_uri", redirectUri) data2.Set("scope", "adict/v1") log.Debug("data sent is : ", data2) log.Debug("Send request to token endpoint: ", tokenUrl) response2, err2 := http.PostForm(tokenUrl, data2) if err2 != nil { log.Error(err2) } else { log.Debug("Endpoint response with status ", response2.Status) defer response2.Body.Close() if response2.StatusCode >= 200 && response2.StatusCode <= 299 { // Set Content-Type in response header w.Header().Add("Content-Type", "application/json") // Decode response Body using the defined type "GrdfTokenResponse" data := GrdfTokenResponse{} decodeError := json.NewDecoder(response2.Body).Decode(&data) if decodeError != nil { http.Error(w, decodeError.Error(), 500) return } if grantType != "refresh_token"{ data.Pce = pce } jsonError := json.NewEncoder(w).Encode(data) if jsonError != nil { http.Error(w, jsonError.Error(), 500) return } } else { http.Error(w, http.StatusText(response2.StatusCode), response2.StatusCode) } } }) log.Fatal(http.ListenAndServe(":"+strconv.Itoa(*httpPort), mux)) }