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")
splitIndexStart := strings.Index(cozyOrigin, ":")
if splitIndexStart == -1 {
log.Error("ProxyError: Enedis - auth - redirect_uri bad format from Cozy" + cozyOrigin)
http.Error(w, http.StatusText(500), 500)
}
splitIndexEnd := strings.Index(cozyOrigin, ".")
if splitIndexEnd == -1 {
log.Error("ProxyError: Enedis - auth - redirect_uri bad format from Cozy" + cozyOrigin)
http.Error(w, http.StatusText(500), 500)
}
instanceName := cozyOrigin[splitIndexStart+3:splitIndexEnd]
// authURL := "https://gw.hml.api.enedis.fr/dataconnect/v1/oauth2/authorize"
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)
// 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")
splitIndexStart := strings.Index(cozyOrigin, ":")
if splitIndexStart == -1 {
log.Error("ProxyError: GRDF - grdf_authorize - redirect_uri bad format " + cozyOrigin)
http.Error(w, http.StatusText(500), 500)
}
splitIndexEnd := strings.Index(cozyOrigin, ".")
if splitIndexEnd == -1 {
log.Error("ProxyError: GRDF - grdf_authorize - redirect_uri bad format " + cozyOrigin)
http.Error(w, http.StatusText(500), 500)
}
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)
})
//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 (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)
log.Error("EnedisError: Enedis - redirect - status code error : ", code)
http.Error(w, http.StatusText(intCode), intCode)
} else {
splitIndex := strings.Index(req_state, "-")
if splitIndex == -1 {
log.Error("ProxyError: Enedis - redirect - No host found in query")
}
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)
}
//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")
//TODO Get pce_id
splitIndex := strings.Index(req_state, "-")
if splitIndex == -1 {
log.Error("ProxyError: GRDF - redirect-grdf - 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("GRDF - redirect - Redirect to Cozy stack - ", redir)
http.Redirect(w, r, redir, 302)
})
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)
log.Debug("Enedis - token - Enedis Endpoint response with status ", response.Status)
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")
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]
} 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)
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)
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
}
// Decode the token and retrieve the pce from it
IdToken = data.IdToken
s := strings.Split(IdToken, ".")
payload, _ := base64.StdEncoding.DecodeString(s[1])
// Check if the payload is well ended
if payload[len(payload)-1] != 125 {
payload = append(payload, []byte{125}...)
}
// 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)
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)
}
if len(consentements[0].Pce) > 0 {
pce = consentements[0].Pce
}
}
}
}
if len(pce) <= 0 {
log.Error("ProxyError: GRDF - grdf_token - No PCE found")
log.Error("GRDFError: GRDF - grdf_token - GRDF response with status code: ", response.StatusCode)
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)
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)
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)
log.Error("ProxyError: GRDF - grdf_token - Unable to decode data: ", decodeError)
http.Error(w, decodeError.Error(), 500)
return
}
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")
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))