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 {
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 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("Enedis - auth - Received new auth request from Cozy")
log.Debug("Enedis - auth - 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.Debug("Enedis - auth - Redirect user to Enedis - ", redirectUrl)
http.Redirect(w, r, redirectUrl, 302)
} else {
log.Error("ProxyError: Enedis - auth - redirect_uri bad format from Cozy " + cozyOrigin)
http.Error(w, http.StatusText(500), 500)
}
} else {
log.Error("CozyError: Enedis - auth - Missing parameters in request")
// GRDF ADICT AUTHORIZE ENDPOINT
mux.HandleFunc("/grdf_authorize", func(w http.ResponseWriter, r *http.Request) {
log.Debug("GRDF - grdf_authorize - Received new auth request from Cozy")
query := r.URL.Query()
log.Debug("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.Debug("GRDF - grdf_authorize - Redirect user to - ", redirectUrl)
http.Redirect(w, r, redirectUrl, 302)
} else {
log.Error("ProxyError: GRDF - grdf_authorize - redirect_uri bad format " + cozyOrigin)
http.Error(w, http.StatusText(500), 500)
}
} else {
log.Error("ProxyError: GRDF - 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("Enedis - redirect - Received redirect answer from Enedis")
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("ProxyError: Enedis - redirect - string to int convert error for status code: ", err)
http.Error(w, http.StatusText(500), 500)
return
}
log.Error("EnedisError: Enedis - redirect - status code error : ", code)
http.Error(w, http.StatusText(intCode), intCode)
return
splitIndex := strings.Index(req_state, "-")
if splitIndex == -1 {
log.Error("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.Debug("Enedis - redirect - Redirect to Cozy stack - ", redir)
http.Redirect(w, r, redir, 302)
} else {
log.Error("ProxyError: Enedis - redirect - Missing parameters in request")
http.Error(w, http.StatusText(500), 500)
//ENEDIS WRONG REDIRECT ENDPOINT
mux.HandleFunc("/redirect/", func(w http.ResponseWriter, r *http.Request) {
log.Error("EnedisError: Enedis - 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("GRDF - redirect-grdf - Received redirect answer from GRDF")
query := r.URL.Query()
log.Debug(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 {
log.Error("ProxyError: GRDF - redirect-grdf - 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.Debug("GRDF - redirect - Redirect to Cozy stack - ", redir)
http.Redirect(w, r, redir, 302)
} else {
log.Error("ProxyError: GRDF - redirect - Missing parameters in request")
http.Error(w, http.StatusText(500), 500)
}
mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
log.Debug("Enedis - token - Received new token request from Cozy")
clientId := ""
clientSecret := ""
code := ""
grantType := ""
refreshToken := ""
// For request token params are into query parameters
log.Debug("Enedis - token - No params found in url query, trying to catch them from body")
contents, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Error("ProxyError: Enedis - token - Unable to read the body: ", err)
params, err := url.ParseQuery(string(contents))
if err != nil {
log.Error(err)
http.Error(w, err.Error(), 500)
return
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("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)
if refreshToken != "" {
data.Set("refresh_token", refreshToken)
data.Set("grant_type", "refresh_token")
}
log.Debug("Enedis - token - Send request to Enedis token endpoint: ", tokenUrl)
response, err := http.PostForm(tokenUrl, data)
log.Error("ProxyError: Enedis - token - Unable to post the request: ", err)
http.Error(w, http.StatusText(500), 500)
return
}
log.Debug("Enedis - token - Enedis 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 {
log.Error("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 {
log.Error("ProxyError: Enedis - token - Unable to encode data: ", jsonError)
http.Error(w, jsonError.Error(), 500)
return
log.Info("Enedis - token - Response correctly to Cozy stack")
} else {
log.Error("EnedisError: Enedis - token - Enedis response 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("GRDF - grdf_token - Received new token request from Cozy")
query := r.URL.Query()
log.Debug(query)
clientId := ""
clientSecret := ""
code := ""
grantType := ""
scope := ""
redirectUri := *cozyProxyURI + "/redirect-grdf"
// For request token params are into query parameters
if len(query) == 0 {
log.Debug("GRDF - grdf_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 {
log.Error("ProxyError: GRDF - grdf_token - Unable to read the body: ", err)
params, err := url.ParseQuery(string(contents))
if err != nil {
log.Error("ProxyError: GRDF - 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("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)
log.Debug("GRDF - grdf_token - data sent is : ", data)
log.Debug("GRDF - grdf_token - Send request to access_token endpoint with authorization_code: ", tokenUrl)
response, err := http.PostForm(tokenUrl, data)
if err != nil {
log.Error("ProxyError: GRDF - grdf_token - Unable to post the request: ", err)
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
http.Error(w, http.StatusText(500), 500)
return
}
log.Debug("GRDF - grdf_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 {
log.Error("ProxyError: GRDF - 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(string(payload))
// Decode the payload from the token
var token GrdfConsentementToken
err := json.Unmarshal(payload, &token)
if err != nil {
log.Error("ProxyError: GRDF - grdf_token - Unable to unmarshal payload from token: ", err)
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("ProxyError: GRDF - grdf_token - Unable to unmarshal consentement information: ", err2)
http.Error(w, err2.Error(), 500)
if len(consentements[0].Pce) > 0 {
pce = consentements[0].Pce
if len(pce) <= 0 {
log.Error("ProxyError: GRDF - grdf_token - No PCE found")
http.Error(w, http.StatusText(500), 500)
return
}
} else {
log.Error("GRDFError: GRDF - grdf_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("GRDF - grdf_token - data sent is : ", data2)
log.Debug("GRDF - grdf_token - Send request to access_token endpoint with client_credentials: ", tokenUrl)
response2, err2 := http.PostForm(tokenUrl, data2)
if err2 != nil {
log.Error("ProxyError: GRDF - grdf_token - Unable to post the request: ", err2)
http.Error(w, http.StatusText(500), 500)
return
}
log.Debug("GRDF - grdf_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 {
log.Error("ProxyError: GRDF - grdf_token - Unable to decode data: ", decodeError)
http.Error(w, decodeError.Error(), 500)
return
if grantType != "refresh_token"{
data.RefreshToken = "-"
data.Pce = pce
data.IdToken = IdToken
}
jsonError := json.NewEncoder(w).Encode(data)
if jsonError != nil {
log.Error("ProxyError: GRDF - grdf_token - Unable to encode data: ", jsonError)
http.Error(w, jsonError.Error(), 500)
return
}
log.Info("GRDF - grdf_token - Response correctly to Cozy stack")
} else {
log.Error("GRDFError: GRDF - grdf_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))