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.Debug("Enedis - Received - Authorize - new /auth request from Cozy")
case "Ping_enedis_redirect":
log.Debug("Enedis - Received - Redirect")
case "Ping_enedis_new_token":
log.Debug("Enedis - Received - Token - new /token request from Cozy")
case "Ping_enedis_oauth_token":
log.Debug("Enedis - Received - Token - an oauth token request from Cozy")
case "Ping_enedis_refresh":
log.Debug("Enedis - Received - Token - a refresh token request from Cozy")
case "Ping_grdf_new_auth":
log.Debug("Grdf - Received - Authorize - new /auth request from Cozy")
case "Ping_grdf_redirect":
log.Debug("Grdf - Received - Redirect")
case "Ping_grdf_new_token":
log.Debug("Grdf - Received - Token - new /token request from Cozy")
case "Enedis_success_oauth":
log.Debug("Enedis - Success - Oauth - oauth dance successfully done")
case "Enedis_success_token":
log.Debug("Enedis - Success - Token - Enedis gave a new token or refresh token to the cozy stack")
case "Grdf_success":
log.Debug("Grdf - Success - Oauth - oauth dance successfully done")
case "Grdf_success_token":
log.Debug("Grdf - Success - Token - Grdf gave a new token to the cozy stack")
case "Enedis_error":
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")
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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.Debug("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)
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=P6M&response_type=" + responseType + "&state=" + state + "-" + instanceName
log.Info("WL - EnedisSuccess - Authorize - Redirecting user to Enedis: ", redirectUrl)
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 {
logRoute("Cozy_error_enedis_auth")
log.Debug("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)
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|Nom||Ecolyo&state=" + state + "-" + instanceName
log.Info("WL - GrdfSuccess - Authorize - Redirect user to: ", redirectUrl)
http.Redirect(w, r, redirectUrl, 302)
} else {
log.Error("WL - ProxyError - Grdf - Authorize - redirect_uri bad format from Cozy " + cozyOrigin)
logRoute("Cozy_error_grdf_auth")
log.Debug("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)
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: ", redir)
http.Redirect(w, r, redir, 302)
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)
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: ", redir)
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)
clientId := ""
clientSecret := ""
code := ""
grantType := ""
refreshToken := ""
// For request token params are into query parameters
log.Debug("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,
// 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)
logRoute("Ping_enedis_refresh")
log.Debug("WL - EnedisReceived - Token - Cozystack asks for a new refresh token")
data.Set("refresh_token", refreshToken)
data.Set("grant_type", "refresh_token")
} else {
logRoute("Ping_enedis_oauth_token")
log.Debug("WL - EnedisReceived - Token - Cozystack asks for a regular token to end the oauth dance")
log.Debug("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.Debug("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)
clientId := ""
clientSecret := ""
code := ""
grantType := ""
scope := ""
redirectUri := *cozyProxyURI + "/redirect-grdf"
// For request token params are into query parameters
if len(query) == 0 {
log.Debug("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")
// 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("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.Debug("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.Debug("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)
// 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.Debug("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.Debug("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))