From 999780b7bd6ef215f312016bd11a1de36608996a Mon Sep 17 00:00:00 2001 From: Bastien DUMONT <bdumont@grandlyon.com> Date: Thu, 8 Aug 2024 12:25:14 +0000 Subject: [PATCH] feat(grdf): fetch access token every two hours --- .env.template | 4 + .gitlab-ci.yml | 18 ++-- .vscode/settings.json | 1 + internal/models/grdfToken.go | 99 ++++++++++++++++++++++ internal/models/models.go | 12 +++ internal/models/partnersInfo.go | 1 - internal/rootmux/rootmux.go | 1 + internal/tokens/tokens.go | 2 +- k8s/secrets/ecolyo-agent-server-config.yml | 2 + main.go | 20 +++++ 10 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 internal/models/grdfToken.go diff --git a/.env.template b/.env.template index 6eff757..f01896f 100644 --- a/.env.template +++ b/.env.template @@ -20,4 +20,8 @@ DATABASE_USER DATABASE_PASSWORD DATABASE_NAME +# rename this to backoffice token ? SGE_API_TOKEN + +GRDF_CLIENT_ID +GRDF_CLIENT_SECRET diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f4a4de4..9bb7369 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ services: variables: DOCKER_DRIVER: overlay2 - DOCKER_TLS_CERTDIR: "" + DOCKER_TLS_CERTDIR: '' GIT_STRATEGY: clone GIT_DEPTH: 0 @@ -89,10 +89,10 @@ sonarqube: - dev image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/sonarsource/sonar-scanner-cli:4 variables: - SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache - GIT_DEPTH: "0" # T + SONAR_USER_HOME: '${CI_PROJECT_DIR}/.sonar' # Defines the location of the analysis task cache + GIT_DEPTH: '0' # T cache: - key: "${CI_JOB_NAME}" + key: '${CI_JOB_NAME}' paths: - .sonar/cache script: @@ -114,10 +114,10 @@ sonarqube-mr: - merge_requests image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/sonarsource/sonar-scanner-cli:4 variables: - SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache - GIT_DEPTH: "0" # T + SONAR_USER_HOME: '${CI_PROJECT_DIR}/.sonar' # Defines the location of the analysis task cache + GIT_DEPTH: '0' # T cache: - key: "${CI_JOB_NAME}" + key: '${CI_JOB_NAME}' paths: - .sonar/cache script: @@ -142,6 +142,8 @@ deploy_rec: - sed -i "s/{{CLIENT_ID}}/$REC_CLIENT_ID/" ./k8s/secrets/ecolyo-agent-server-config.yml - sed -i "s/{{CLIENT_SECRET}}/$REC_CLIENT_SECRET/" ./k8s/secrets/ecolyo-agent-server-config.yml - sed -i "s/{{SGE_API_TOKEN}}/$REC_SGE_API_TOKEN/" ./k8s/secrets/ecolyo-agent-server-config.yml + - sed -i "s/{{GRDF_CLIENT_ID}}/$GRDF_CLIENT_ID/" ./k8s/secrets/ecolyo-agent-server-config.yml + - sed -i "s/{{GRDF_CLIENT_SECRET}}/$GRDF_CLIENT_SECRET/" ./k8s/secrets/ecolyo-agent-server-config.yml - sed -i "s/{{HOSTNAME}}/ecolyo-agent-rec.apps.grandlyon.com/g" ./k8s/secrets/ecolyo-agent-server-config.yml - sed -i "s+{{AUTH_URL}}+https://connexion-rec.grandlyon.fr/IdPOAuth2/authorize/oidc-rec-2+" ./k8s/secrets/ecolyo-agent-server-config.yml - sed -i "s+{{USERINFO_URL}}+https://connexion-rec.grandlyon.fr/IdPOAuth2/userinfo/oidc-rec-2+" ./k8s/secrets/ecolyo-agent-server-config.yml @@ -172,6 +174,8 @@ deploy_prod: - sed -i "s/{{CLIENT_ID}}/$PROD_CLIENT_ID/" ./k8s/secrets/ecolyo-agent-server-config.yml - sed -i "s/{{CLIENT_SECRET}}/$PROD_CLIENT_SECRET/" ./k8s/secrets/ecolyo-agent-server-config.yml - sed -i "s/{{SGE_API_TOKEN}}/$PROD_SGE_API_TOKEN/" ./k8s/secrets/ecolyo-agent-server-config.yml + - sed -i "s/{{GRDF_CLIENT_ID}}/$GRDF_CLIENT_ID/" ./k8s/secrets/ecolyo-agent-server-config.yml + - sed -i "s/{{GRDF_CLIENT_SECRET}}/$GRDF_CLIENT_SECRET/" ./k8s/secrets/ecolyo-agent-server-config.yml - sed -i "s/{{HOSTNAME}}/ecolyo-agent.apps.grandlyon.com/g" ./k8s/secrets/ecolyo-agent-server-config.yml - sed -i "s+{{AUTH_URL}}+https://connexion.grandlyon.fr/IdPOAuth2/authorize/oidc-2+" ./k8s/secrets/ecolyo-agent-server-config.yml - sed -i "s+{{USERINFO_URL}}+https://connexion.grandlyon.fr/IdPOAuth2/userinfo/oidc-2+" ./k8s/secrets/ecolyo-agent-server-config.yml diff --git a/.vscode/settings.json b/.vscode/settings.json index c18971e..c32570c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ }, "peacock.color": "#32f0ff", "cSpell.words": [ + "adict", "admininfo", "animatorinfo", "backoffice", diff --git a/internal/models/grdfToken.go b/internal/models/grdfToken.go new file mode 100644 index 0000000..7c531c6 --- /dev/null +++ b/internal/models/grdfToken.go @@ -0,0 +1,99 @@ +package models + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "strings" + "time" + + "forge.grandlyon.com/web-et-numerique/factory/llle_project/backoffice-server/internal/constants" + "gorm.io/gorm" +) + +type GrdfAccessToken struct { + AccessToken string `json:"access_token"` + FetchedAt time.Time `json:"fetched_at"` +} + +// Fetches GRDF auth API for an access token and save it in DB +func FetchGRDFAuthAPI() { + log.Println("| Calling GRDF auth API") + + dh := NewDataHandler() + + reqBody := url.Values{ + "scope": {"/adict/v2"}, + "grant_type": {"client_credentials"}, + "client_id": {os.Getenv("GRDF_CLIENT_ID")}, + "client_secret": {os.Getenv("GRDF_CLIENT_SECRET")}, + } + + client := &http.Client{} + req, err := http.NewRequest(http.MethodPost, "https://adict-connexion.grdf.fr/oauth2/aus5y2ta2uEHjCWIR417/v1/token", strings.NewReader(reqBody.Encode())) + if err != nil { + fmt.Println("Error creating request:", err) + return + } + + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + resp, err := client.Do(req) + if err != nil { + fmt.Println("Error sending request:", err) + return + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println("Error reading response body:", err) + return + } + + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + fmt.Println("Error unmarshaling JSON:", err) + return + } + + accessToken, ok := result["access_token"].(string) + if !ok { + fmt.Println("Access token not found in response") + return + } + + dh.WriteAccessToken(accessToken) +} + +// Writes a new access token to the database +func (dh *DataHandler) WriteAccessToken(token string) { + result := dh.sqlClient.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&GrdfAccessToken{}).Updates(&GrdfAccessToken{ + AccessToken: token, + FetchedAt: time.Now(), + }) + + if result.Error != nil { + log.Println("| Error updating GRDF access token") + log.Println(result.Error) + } else { + log.Println("| New GRDF access token written") + } +} + +func (dh *DataHandler) GetGrdfAccessToken(w http.ResponseWriter, r *http.Request) { + var token GrdfAccessToken + err := dh.sqlClient.First(&token).Error + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + w.Header().Set(constants.ContentType, constants.Json) + json.NewEncoder(w).Encode(token) + log.Printf("| get partnersInfo | %v", r.RemoteAddr) +} diff --git a/internal/models/models.go b/internal/models/models.go index ca67395..22b3843 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -50,6 +50,7 @@ func NewDataHandler() *DataHandler { sqlClient.AutoMigrate(&Price{}) sqlClient.AutoMigrate(&SgeConsent{}) sqlClient.AutoMigrate(&GrdfConsent{}) + sqlClient.AutoMigrate(&GrdfAccessToken{}) // Check if partners info already exists var partnersInfo PartnersInfo @@ -78,5 +79,16 @@ func NewDataHandler() *DataHandler { }) } + // check if access token already exists + var accessToken GrdfAccessToken + err = sqlClient.First(&accessToken).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + // Create default access token + sqlClient.Create(&GrdfAccessToken{ + AccessToken: "", + FetchedAt: time.Now(), + }) + } + return &DataHandler{sqlClient: sqlClient} } diff --git a/internal/models/partnersInfo.go b/internal/models/partnersInfo.go index 9237a30..7494b1f 100644 --- a/internal/models/partnersInfo.go +++ b/internal/models/partnersInfo.go @@ -64,5 +64,4 @@ func (dh *DataHandler) SavePartnersInfo(w http.ResponseWriter, r *http.Request) w.Header().Set(constants.ContentType, constants.Json) json.NewEncoder(w).Encode(partnersInfo) log.Printf("| updated partnersInfo | %v", r.RemoteAddr) - } diff --git a/internal/rootmux/rootmux.go b/internal/rootmux/rootmux.go index 962169f..7603056 100644 --- a/internal/rootmux/rootmux.go +++ b/internal/rootmux/rootmux.go @@ -90,6 +90,7 @@ func CreateRootMux() RootMux { r.Post("/consent", dh.PostGrdfConsent) r.Get("/consent/{id}", dh.GetGrdfConsentById) r.Delete("/consent/{id}", dh.DeleteGrdfConsentById) + r.Get("/access-token", dh.GetGrdfAccessToken) }) r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { /** handles Oauth2 redirection */ }) diff --git a/internal/tokens/tokens.go b/internal/tokens/tokens.go index 86a5bf8..9a940cc 100644 --- a/internal/tokens/tokens.go +++ b/internal/tokens/tokens.go @@ -60,7 +60,7 @@ func newManager(keyfile string, debug bool) manager { } } -// Token represents a token containting data +// Token represents a token containing data type Token struct { ExpiresAt int64 IssuedAt int64 `json:"iat,omitempty"` diff --git a/k8s/secrets/ecolyo-agent-server-config.yml b/k8s/secrets/ecolyo-agent-server-config.yml index 9503f60..8974c88 100644 --- a/k8s/secrets/ecolyo-agent-server-config.yml +++ b/k8s/secrets/ecolyo-agent-server-config.yml @@ -20,4 +20,6 @@ stringData: SGE_API_TOKEN: {{SGE_API_TOKEN}} TOKEN_URL: {{TOKEN_URL}} USERINFO_URL: {{USERINFO_URL}} + GRDF_CLIENT_ID: {{GRDF_CLIENT_ID}} + GRDF_CLIENT_SECRET: {{GRDF_CLIENT_SECRET}} type: Opaque diff --git a/main.go b/main.go index 137ea8d..c8dbfb5 100644 --- a/main.go +++ b/main.go @@ -4,11 +4,13 @@ import ( "fmt" "net/http" "strconv" + "time" "log" "forge.grandlyon.com/web-et-numerique/factory/llle_project/backoffice-server/internal/common" "forge.grandlyon.com/web-et-numerique/factory/llle_project/backoffice-server/internal/mocks" + "forge.grandlyon.com/web-et-numerique/factory/llle_project/backoffice-server/internal/models" "forge.grandlyon.com/web-et-numerique/factory/llle_project/backoffice-server/internal/rootmux" "forge.grandlyon.com/web-et-numerique/factory/llle_project/backoffice-server/internal/tokens" ) @@ -37,6 +39,24 @@ func main() { mockOAuth2Port := ":8090" go http.ListenAndServe(mockOAuth2Port, mocks.CreateMockOAuth2()) fmt.Println("Mock OAuth2 server Listening on: http://localhost" + mockOAuth2Port) + + // Call the function immediately when the server starts + models.FetchGRDFAuthAPI() + + // then call GRDF auth api every two hours + ticker := time.NewTicker(time.Hour * 2) + quit := make(chan struct{}) + go func() { + for { + select { + case <-ticker.C: + models.FetchGRDFAuthAPI() + case <-quit: + ticker.Stop() + return + } + } + }() } // Serve locally with https -- GitLab