Newer
Older
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.self-data.alpha.grandlyon.com"), "Cozy domain (defaults to cozy.self-data.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)")
cozyProxyURI = flag.String("cozy_proxy_uri", LookupEnvOrString("COZY_PROXY_URI", "https://oauth-proxy.self-data.alpha.grandlyon.com"), "Cozy domain (defaults to https://oauth-proxy.self-data.alpha.grandlyon.com)")
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"`
RefreshToken string `json:"refresh_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 logRoute(key string) {
// format is : Subject - action - location - description
switch key {
case "Ping_enedis_new_auth":
log.Info("Enedis - Received - Authorize - new /auth request from Cozy")
log.Info("Enedis - Received - Token - new /token request from Cozy")
log.Info("Enedis - Received - Token - an oauth token request from Cozy")
log.Info("Enedis - Received - Token - a refresh token request from Cozy")
log.Info("Grdf - Received - Authorize - new /auth request from Cozy")
log.Info("Grdf - Received - Token - new /token request from Cozy")
case "Enedis_success_oauth":
log.Info("Enedis - Success - Oauth - oauth dance successfully done")
log.Info("Enedis - Success - Token - Enedis gave a new token or refresh token to the cozy stack")
log.Info("Grdf - Success - Oauth - oauth dance successfully done")
log.Info("Grdf - Success - Token - Grdf gave a new token to the cozy stack")
log.Error("Enedis - Error - Oauth - something wrong happened with enedis")
log.Error("Enedis - Error - Redirect - an error occured with enedis redirection")
log.Error("Enedis - Error - Token - an error occured with enedis /token endpoint")
log.Error("Grdf - Error - Oauth - something wrong happened with grdf")
log.Error("Grdf - Error - Redirect - an error occured with grdf redirection")
log.Error("Grdf - Error - Token - an error occured with grdf /token endpoint")
case "Proxy_error":
log.Error("Proxy - Error - Oauth - something wrong happened in the proxy")
case "Proxy_error_enedis_auth":
log.Error("Proxy - Error - Enedis - Authorize")
case "Proxy_error_grdf_auth":
log.Error("Proxy - Error - Grdf - Authorize")
case "Proxy_error_enedis_redirect":
log.Error("Proxy - Error - Enedis - Redirect")
case "Proxy_error_grdf_redirect":
log.Error("Proxy - Error - Grdf - Redirect")
case "Proxy_error_enedis_token":
log.Error("Proxy - Error - Enedis - Token")
case "Proxy_error_grdf_token":
log.Error("Proxy - Error - Grdf - Token")
case "Cozy_error_enedis_auth":
log.Error("Cozy - Error - Enedis - Authorize")
case "Cozy_error_grdf_auth":
log.Error("Cozy - Error - Grdf - Authorize")
default:
log.Info("logRoute was called with unknown arguments")
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)
}
log.Infof("Starting Server on port %d\n", *httpPort)
mux.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "OK\n")
})
mux.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
//log.Debug("WL - EnedisReceived - Authorize - Query received from Cozy: ", query)
log.Info("WL - EnedisReceived - Authorize - Query received from Cozy")
clientId := query.Get("client_id")
state := query.Get("state")
// 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")
if len(clientId) > 0 && len(state) > 0 && len(cozyOrigin) > 0 {
splitIndexStart := strings.Index(cozyOrigin, ":")
splitIndexEnd := strings.Index(cozyOrigin, ".")
if splitIndexStart > -1 && splitIndexEnd > -1 {
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=P12M&response_type=" + responseType + "&state=" + state + "-" + instanceName
log.Info("WL - EnedisSuccess - Authorize - Redirecting user to Enedis")
http.Redirect(w, r, redirectUrl, 302)
} else {
log.Error("WL - ProxyError - Enedis - Authorize - redirect_uri bad format from Cozy " + cozyOrigin)
http.Error(w, http.StatusText(500), 500)
}
} else {
log.Info("WL - CozyError - Enedis - Authorize - Missing parameters in request")
// GRDF ADICT AUTHORIZE ENDPOINT
mux.HandleFunc("/grdf_authorize", func(w http.ResponseWriter, r *http.Request) {
//log.Debug("WL - GrdfReceived - Authorize - Query received: ", query)
log.Info("WL - GrdfReceived - Authorize - Query received")
clientId := query.Get("client_id")
state := query.Get("state")
cozyOrigin := query.Get("redirect_uri")
if len(clientId) > 0 && len(state) > 0 && len(cozyOrigin) > 0 {
splitIndexStart := strings.Index(cozyOrigin, ":")
splitIndexEnd := strings.Index(cozyOrigin, ".")
if splitIndexStart > -1 && splitIndexEnd > -1 {
instanceName := cozyOrigin[splitIndexStart+3 : splitIndexEnd]
redirectProxy := *cozyProxyURI + "/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=Prénom%7cNom%7c%7cEcolyo&state=" + state + "-" + instanceName
log.Info("WL - GrdfSuccess - Authorize - Redirect user")
http.Redirect(w, r, redirectUrl, 302)
} else {
log.Error("WL - ProxyError - Grdf - Authorize - redirect_uri bad format from Cozy " + cozyOrigin)
log.Info("WL - CozyError - Grdf - Authorize - Missing parameters in request")
http.Error(w, http.StatusText(500), 500)
}
})
//ENEDIS REDIRECT ENDPOINT
mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
//log.Debug("WL - EnedisReceived - Redirect - Query received: ", query)
log.Info("WL - EnedisReceived - Redirect - Query received")
req_state := query.Get("state")
statusCodes := [4]string{"400", "403", "500", "503"}
if len(code) > 0 && len(req_state) > 0 {
if findItem(statusCodes, code) {
intCode, err := strconv.Atoi(code)
if err != nil {
log.Error("WL - ProxyError - Enedis - Redirect - String to int convert error for status code: ", err)
http.Error(w, http.StatusText(500), 500)
return
}
log.Error("WL - EnedisError - Redirect - status code error: ", code)
http.Error(w, http.StatusText(intCode), intCode)
return
splitIndex := strings.Index(req_state, "-")
if splitIndex == -1 {
log.Error("WL - ProxyError - Enedis - Redirect - No host found in query")
http.Error(w, http.StatusText(500), 500)
return
}
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.Info("WL - EnedisSuccess - Redirect - Redirecting user to Cozy stack")
http.Redirect(w, r, redir, 302)
if len(code) > 0 {
if findItem(statusCodes, code) {
intCode, err := strconv.Atoi(code)
if err != nil {
logRoute("Proxy_error_enedis_redirect")
log.Error("WL - ProxyError - Enedis - Redirect - String to int convert error for status code: ", err)
http.Error(w, http.StatusText(500), 500)
return
}
logRoute("Enedis_redirect_error")
log.Error("WL - EnedisError - Redirect - status code error: ", code)
http.Error(w, http.StatusText(intCode), intCode)
return
}
}
log.Error("WL - ProxyError - Enedis - Redirect - Missing parameters in request")
//ENEDIS WRONG REDIRECT ENDPOINT
mux.HandleFunc("/redirect/", func(w http.ResponseWriter, r *http.Request) {
logRoute("Enedis_redirect_error")
log.Error("WL - EnedisError - Redirect - Wrong route received from Enedis ")
http.Error(w, http.StatusText(500), 500)
})
//GRDF REDIRECT ENDPOINT
mux.HandleFunc("/redirect-grdf", func(w http.ResponseWriter, r *http.Request) {
//log.Debug("WL - GrdfReceived - Redirect - Received redirect answer from GRDF: ", query)
log.Info("WL - GrdfReceived - Redirect - Received redirect answer from GRDF")
code := query.Get("code")
req_state := query.Get("state")
if len(code) > 0 && len(req_state) > 0 {
splitIndex := strings.Index(req_state, "-")
if splitIndex == -1 {
logRoute("Proxy_error_grdf_redirect")
log.Error("WL - ProxyError - Grdf - Redirect - No host found")
http.Error(w, http.StatusText(500), 500)
return
}
state := req_state[0:splitIndex]
host := req_state[splitIndex+1:]
cozyURL := "https://" + host + "." + *cozyDomain + *cozyGrdfRedirectURI
redir := cozyURL + "?code=" + code + "&state=" + state
log.Info("WL - GrdfSuccess - Redirect - Redirect to Cozy stack")
logRoute("Proxy_error_grdf_redirect")
log.Error("WL - ProxyError - Grdf - Redirect - Missing parameters in request")
mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
// log.Debug("WL - EnedisReceived - Token - Received new token request from Cozy: ", query)
log.Info("WL - EnedisReceived - Token - Received new token request from Cozy")
clientId := ""
clientSecret := ""
code := ""
grantType := ""
refreshToken := ""
// For request token params are into query parameters
log.Info("WL - EnedisInfo - Token - No params found in url query, trying to catch them from body")
contents, err := ioutil.ReadAll(r.Body)
if err != nil {
logRoute("Proxy_error_enedis_token")
log.Error("WL - ProxyError - Enedis - Token - Unable to read the body: ", err)
params, err := url.ParseQuery(string(contents))
if err != nil {
logRoute("Proxy_error_enedis_token")
log.Error("WL - ProxyError - Enedis - Token - Unable to parse query", err)
if val, ok := params["client_id"]; ok {
clientId = val[0]
if val, ok := params["client_secret"]; ok {
clientSecret = val[0]
if val, ok := params["code"]; ok {
code = val[0]
if val, ok := params["grant_type"]; ok {
grantType = val[0]
if val, ok := params["refresh_token"]; ok {
refreshToken = val[0]
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("WL - EnedisInfo - Token - Result")
// tokenUrl := "https://gw.hml.api.enedis.fr/v1/oauth2/token"
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)
log.Info("WL - EnedisReceived - Token - Cozystack asks for a new refresh token")
data.Set("refresh_token", refreshToken)
data.Set("grant_type", "refresh_token")
log.Info("WL - EnedisReceived - Token - Cozystack asks for a regular token to end the oauth dance")
log.Info("WL - EnedisInfo - Token - Send request to Enedis token endpoint: ", tokenUrl)
response, err := http.PostForm(tokenUrl, data)
logRoute("Proxy_error_enedis_token")
log.Error("WL - ProxyError - Enedis - Token - Unable to post the request: ", err)
http.Error(w, http.StatusText(500), 500)
return
log.Info("WL - EnedisInfo - Token - Enedis answered back 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 {
logRoute("Proxy_error_enedis_token")
log.Error("WL - ProxyError - Enedis - Token - Unable to decode data: ", decodeError)
http.Error(w, decodeError.Error(), 500)
return
}
// Response with json data
jsonError := json.NewEncoder(w).Encode(data)
if jsonError != nil {
logRoute("Proxy_error_enedis_token")
log.Error("WL - ProxyError - Enedis - Token - Unable to encode data: ", jsonError)
if refreshToken != "" {
logRoute("Enedis_success_token")
} else {
logRoute("Enedis_success_oauth")
}
log.Info("WL - EnedisSuccess - Token - Respond correctly to Cozy stack")
logRoute("Enedis_token_error")
log.Error("WL - EnedisError - Token - Enedis answer back with status code: ", response.StatusCode)
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("WL - GrdfReceived - Token - Received new token request from Cozy", query)
log.Info("WL - GrdfReceived - Token - Received new token request from Cozy")
clientId := ""
clientSecret := ""
code := ""
grantType := ""
redirectUri := *cozyProxyURI + "/redirect-grdf"
// For request token params are into query parameters
if len(query) == 0 {
log.Info("WL - GrdfInfo - Token - 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 {
logRoute("Proxy_error_grdf_token")
log.Error("WL - ProxyError - Grdf - Token - Unable to read the body: ", err)
params, err := url.ParseQuery(string(contents))
if err != nil {
logRoute("Proxy_error_grdf_token")
log.Error("WL - ProxyError - Grdf - Token - Unable to parse the query: ", err)
if val, ok := params["client_id"]; ok {
clientId = val[0]
if val, ok := params["client_secret"]; ok {
clientSecret = val[0]
if val, ok := params["grant_type"]; ok {
grantType = val[0]
// Retrieve params from query
clientId = query.Get("client_id")
clientSecret = query.Get("client_secret")
code = query.Get("code")
grantType = query.Get("grant_type")
// log.WithFields(log.Fields{
// "client_id": clientId,
// "client_secret": clientSecret,
// "code": code,
// "grant_type": grantType,
// "redirect_uri": redirectUri,
// "scope": scope,
// }).Debug("WL - GrdfInfo - Token - Result")
tokenUrl := "https://sofit-sso-oidc.grdf.fr/openam/oauth2/realms/externeGrdf/access_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)
// log.Debug("WL - GrdfInfo - Token - data sent is: ", data)
log.Info("WL - GrdfInfo - Token - Send request to access_token endpoint with authorization_code: ", tokenUrl)
response, err := http.PostForm(tokenUrl, data)
if err != nil {
logRoute("Proxy_error_grdf_token")
log.Error("WL - ProxyError - Grdf - Token - Unable to post the request: ", err)
http.Error(w, http.StatusText(500), 500)
return
}
log.Info("WL - GrdfInfo - Token - GRDF 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 {
logRoute("Proxy_error_grdf_token")
log.Error("WL - ProxyError - Grdf - Token - Unable to decode data: ", err)
http.Error(w, decodeError.Error(), 500)
return
}
// Check if IdToken exist
// Decode the token and retrieve the pce from it
if len(data.IdToken) > 0 {
IdToken = data.IdToken
s := strings.Split(IdToken, ".")
if len(s[1]) > 0 {
payload, _ := base64.StdEncoding.DecodeString(s[1])
// Check if the payload is well ended
if payload[len(payload)-1] != 125 {
payload = append(payload, []byte{125}...)
}
// log.Debug("WL - GrdfInfo - Token - token decoded payload: ", string(payload))
// Decode the payload from the token
var token GrdfConsentementToken
err := json.Unmarshal(payload, &token)
if err != nil {
logRoute("Proxy_error_grdf_token")
log.Error("WL - ProxyError - Grdf - Token - Unable to unmarshal payload from token: ", err)
// log.Debug("WL - GrdfInfo - Token - Consentements found: ", token.Consentements)
log.Info("WL - GrdfInfo - Token - Consentements found")
// Decode the consentement information
if len(token.Consentements) > 0 {
var consentements GrdfConsentement
err2 := json.Unmarshal([]byte(token.Consentements), &consentements)
if err2 != nil {
logRoute("Proxy_error_grdf_token")
log.Error("WL - ProxyError - Grdf - Token - Unable to unmarshal consentement information: ", err2)
if len(consentements[0].Pce) > 0 {
pce = consentements[0].Pce
logRoute("Proxy_error_grdf_token")
log.Error("WL - ProxyError - Grdf - Token - No PCE found")
http.Error(w, http.StatusText(500), 500)
return
}
} else {
logRoute("Grdf_token_error")
log.Error("WL - GrdfError - Token - GRDF response with status code: ", response.StatusCode)
http.Error(w, http.StatusText(response.StatusCode), response.StatusCode)
return
// 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)
// log.Debug("WL - GrdfInfo - Token - data sent is: ", data2)
log.Info("WL - GrdfInfo - Token - send request to access_token endpoint with client_credentials: ", tokenUrl)
response2, err2 := http.PostForm(tokenUrl, data2)
if err2 != nil {
logRoute("Proxy_error_grdf_token")
log.Error("WL - ProxyError - Grdf - Token - Unable to post the request: ", err2)
http.Error(w, http.StatusText(500), 500)
return
log.Info("WL - GrdfInfo - Token - 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 {
logRoute("Proxy_error_grdf_token")
log.Error("WL - ProxyError - Grdf - Token - Unable to decode data: ", decodeError)
http.Error(w, decodeError.Error(), 500)
return
data.RefreshToken = "-"
data.Pce = pce
data.IdToken = IdToken
}
jsonError := json.NewEncoder(w).Encode(data)
if jsonError != nil {
logRoute("Proxy_error_grdf_token")
log.Error("WL - ProxyError - Grdf - Token - Unable to encode data: ", jsonError)
http.Error(w, jsonError.Error(), 500)
return
}
logRoute("Grdf_success_token")
log.Info("WL - GrdfSuccess - Token - Response correctly to Cozy stack")
logRoute("Grdf_token_error")
log.Error("WL - GrdfError - Token - GRDF response with status code: ", response2.StatusCode)
http.Error(w, http.StatusText(response2.StatusCode), response2.StatusCode)
log.Fatal(http.ListenAndServe(":"+strconv.Itoa(*httpPort), mux))