diff --git a/.env.template b/.env.template index 05ab3a8960882958c0e9f1043fa87f61579272d1..3990d028175b9cdfb675a7ddadd3c140deeb81f7 100644 --- a/.env.template +++ b/.env.template @@ -22,5 +22,6 @@ DATABASE_NAME BO_API_TOKEN +FETCH_GRDF_TOKEN=true GRDF_CLIENT_ID GRDF_CLIENT_SECRET diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 258ea3124f8cffde8471803535840b6f3d4b2f60..25133644736f3249e615543f916370d2ba26208d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,7 +25,7 @@ stages: - deploy import-convert-assets: - image: alpine:3.16.2 + image: alpine:3.20.3 stage: import-convert-assets before_script: - apk add inkscape curl @@ -142,6 +142,7 @@ 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/{{BO_API_TOKEN}}/$REC_BO_API_TOKEN/" ./k8s/secrets/ecolyo-agent-server-config.yml + - sed -i "s/{{FETCH_GRDF_TOKEN}}/\"$REC_FETCH_GRDF_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 @@ -152,7 +153,7 @@ deploy_rec: script: - find k8s/ -name '*.yml' -exec sed -i "s/{{NS}}/$NAMESPACE/g" {} \; - - oc create secret -n $NAMESPACE docker-registry llle-project --docker-server=$CI_REGISTRY --docker-username=llle-project --docker-password=$READ_REGISTRY_TOKEN --dry-run=client -o yaml | oc apply -f - + - oc create secret -n $NAMESPACE docker-registry forge-secret --docker-server=$CI_REGISTRY --docker-username=read_registry --docker-password=$READ_REGISTRY_TOKEN --dry-run=client -o yaml | oc apply -f - - oc apply -f k8s/secrets - oc apply -f k8s/deployments @@ -174,6 +175,7 @@ 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/{{BO_API_TOKEN}}/$PROD_BO_API_TOKEN/" ./k8s/secrets/ecolyo-agent-server-config.yml + - sed -i "s/{{FETCH_GRDF_TOKEN}}/\"$PROD_FETCH_GRDF_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 @@ -184,7 +186,7 @@ deploy_prod: script: - find k8s/ -name '*.yml' -exec sed -i "s/{{NS}}/$NAMESPACE/g" {} \; - - oc create secret -n $NAMESPACE docker-registry llle-project --docker-server=$CI_REGISTRY --docker-username=llle-project --docker-password=$READ_REGISTRY_TOKEN --dry-run=client -o yaml | oc apply -f - + - oc create secret -n $NAMESPACE docker-registry forge-secret --docker-server=$CI_REGISTRY --docker-username=read_registry --docker-password=$READ_REGISTRY_TOKEN --dry-run=client -o yaml | oc apply -f - - oc apply -f k8s/secrets - oc apply -f k8s/deployments diff --git a/Dockerfile b/Dockerfile index f4efa81e01e91845d2e5aa036ec955ad556f9561..a79e9e97b6e1fbd65d0cdb7269c8494754e024e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,7 +48,7 @@ RUN chown -Rf "${UID}" ./* ############################## # STEP 2 build a small image # ############################## -FROM curlimages/curl:8.00.1 +FROM curlimages/curl:8.11.0 WORKDIR /app diff --git a/go.mod b/go.mod index 1476d8bb39a7f7e0e3901a5c179b45e628673c20..c6eb4521782396720aacfeb7e0fefb89d5421ea2 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module forge.grandlyon.com/web-et-numerique/factory/llle_project/backoffice-serv go 1.18 require ( - github.com/go-chi/chi/v5 v5.0.11 + github.com/go-chi/chi/v5 v5.1.0 github.com/google/uuid v1.5.0 golang.org/x/oauth2 v0.16.0 - gorm.io/driver/mysql v1.5.2 + gorm.io/driver/mysql v1.5.7 gorm.io/driver/sqlite v1.5.4 - gorm.io/gorm v1.25.5 + gorm.io/gorm v1.25.7 ) require ( diff --git a/go.sum b/go.sum index b71a4a2954f2b1b2a3d1eb1af0b064af89fba3a8..90b71b9164c299d7f9ab369f28630890dd6243c3 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= @@ -52,8 +54,12 @@ google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7 google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs= gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/internal/auth/oauth2.go b/internal/auth/oauth2.go index 423df8eac680bc3d4b740993ceb38eadfb004312..3e9521d0b10237d4744f55c455207f0da3447d09 100644 --- a/internal/auth/oauth2.go +++ b/internal/auth/oauth2.go @@ -106,11 +106,11 @@ func (m Manager) HandleOAuth2Callback() http.Handler { } //////////////////////////////////////////////// // UNCOMMENT THIS TO DEBUG USERINFO RESPONSE // - // readBody, err := ioutil.ReadAll(response.Body) + // readBody, err := io.ReadAll(response.Body) // if err != nil { // panic(err) // } - // newBody := ioutil.NopCloser(bytes.NewBuffer(readBody)) + // newBody := io.NopCloser(bytes.NewBuffer(readBody)) // response.Body = newBody // if string(readBody) != "" { // fmt.Printf("BODY : %q \n", readBody) @@ -126,6 +126,15 @@ func (m Manager) HandleOAuth2Callback() http.Handler { user.Roles[key] = strings.TrimPrefix(strings.Split(role, ",")[0], "CN=") } + // Filter only allowed roles to reduce the cookie size + var filteredRoles []string + for _, role := range user.Roles { + if role == AdminRole || role == AnimatorRole { + filteredRoles = append(filteredRoles, role) + } + } + user.Roles = filteredRoles + // Check if user has the correct role err = checkUserHasRole(TokenData{User: user}, []string{AdminRole, AnimatorRole}) @@ -145,7 +154,7 @@ func (m Manager) HandleOAuth2Callback() http.Handler { } tokenData := TokenData{User: user, XSRFToken: xsrfToken} tokens.CreateCookie(tokenData, m.Hostname, authTokenKey, 24*time.Hour, w) - // Log the connexion + // Log the connection log.Printf("| %v (%v %v) | Login success | %v", user.Login, user.Name, user.Surname, req.RemoteAddr) // Redirect http.Redirect(w, r, "/", http.StatusFound) diff --git a/internal/common/common.go b/internal/common/common.go index 7030f4e3e379847568661da255ba4f04008f8e13..d818a5f54767c691b9701170317d241d018ce2f9 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -203,12 +203,6 @@ func PageLimitFromRequest(r *http.Request) (page int, limit int, err error) { if page < 0 { page = 0 } - switch { - case limit > 100: - limit = 100 - case limit < 10: - limit = 10 - } return page, limit, nil } diff --git a/internal/models/consent_cleanup.go b/internal/models/consent_cleanup.go new file mode 100644 index 0000000000000000000000000000000000000000..c7fe21a721cfd6c4c4aa7ae66cfa565603d43653 --- /dev/null +++ b/internal/models/consent_cleanup.go @@ -0,0 +1,35 @@ +package models + +import ( + "log" + "time" +) + +// deleteOutdatedConsents hard deletes outdated consents where end_date is more than 5 years old +func deleteOutdatedConsents[T GrdfConsent | SgeConsent](dh *DataHandler, model *T, consentType string) { + log.Printf("Running %v outdated consents cleanup", consentType) + cutoffDate := time.Now().AddDate(-5, 0, 0) + + result := dh.sqlClient.Unscoped(). + Where("end_date < ?", cutoffDate). + Delete(model) + + log.Printf("nb of rows %v", result.RowsAffected) + + if result.Error != nil { + log.Printf("Error deleting outdated %s consents: %v\n", consentType, result.Error) + return + } + + if result.RowsAffected > 0 { + log.Printf("Successfully deleted %d outdated %s consent(s) created before %v\n", + result.RowsAffected, + consentType, + cutoffDate.Format("2006-01-02")) + } +} + +func DeleteOutdatedConsents(dh *DataHandler) { + deleteOutdatedConsents(dh, &GrdfConsent{}, "GRDF") + deleteOutdatedConsents(dh, &SgeConsent{}, "SGE") +} diff --git a/internal/rootmux/rootmux_test.go b/internal/rootmux/rootmux_test.go index 1258e607fea578697c8f1da1d537642032b298cb..15425b948230351a7b4aaefbee0319df27f990db 100644 --- a/internal/rootmux/rootmux_test.go +++ b/internal/rootmux/rootmux_test.go @@ -247,7 +247,7 @@ func animatorTests(t *testing.T) { do("GET", "/api/common/monthlyReport?year=2021&month=1", noH, "", http.StatusOK, `{"year":2021,"month":1,"subject":"[Ecolyo] Votre bilan de décembre 2020","info":"Informations du mois","image":"imagebase64","newsTitle":"","newsContent":"","question":"","link":""`) // Try to get SGE consents (must fail) - do("GET", "/api/admin/consent?limit=50&page=0", xsrfHeader, "", http.StatusForbidden, "no user role among [ANIMATORS OTHER_GROUP] is in allowed roles ([ADMINS])") + do("GET", "/api/admin/consent?limit=50&page=0", xsrfHeader, "", http.StatusForbidden, "no user role among [ANIMATORS] is in allowed roles ([ADMINS])") } // Try to login (must pass) do("GET", "/OAuth2Login", noH, "", http.StatusOK, "") diff --git a/internal/tokens/tokens_test.go b/internal/tokens/tokens_test.go index 66a24e00b1c9e8006d53f239edaddba8efc91c57..66e4b5cb9bcf6c87e348138034e75c1ac58f136a 100644 --- a/internal/tokens/tokens_test.go +++ b/internal/tokens/tokens_test.go @@ -1,7 +1,6 @@ package tokens import ( - "fmt" "testing" "time" @@ -13,10 +12,6 @@ type user struct { Password string } -func (u user) String() string { - return fmt.Sprintf("Login: %v, Password: %v", u.Login, u.Password) -} - func TestManagerCreateTokenUnStoreData(t *testing.T) { key, _ := common.GenerateRandomBytes(32) key2, _ := common.GenerateRandomBytes(32) diff --git a/k8s/README.md b/k8s/README.md index abd3f1822ce46cb8e4d98b7cb35409cfa2d3deec..948896b4754f4b7dc0bed0471daeff47d3415e5a 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -31,10 +31,10 @@ Configuration: - Depuis la console Web, se rendre dans la section "Workloads > Secrets" - Cliquer sur le bouton bleu "Create" puis "Image pull secret" - Donner les informations : - - Secret name : llle-project + - Secret name : forge-secret - Authentification type : Image registry credentials - Registry server address : registry.forge.grandlyon.com - - Username: llle-project + - Username: read_registry - Password: demander le password - Cliquer sur Create diff --git a/k8s/deployments/ecolyo-agent-database-deployment.yml b/k8s/deployments/ecolyo-agent-database-deployment.yml index 9c0d54fe6d3c0e1c65b8c421f50166ee7c63bdf4..fafc676f25301796a1b1f2e7c38b1e71dbd33966 100644 --- a/k8s/deployments/ecolyo-agent-database-deployment.yml +++ b/k8s/deployments/ecolyo-agent-database-deployment.yml @@ -40,7 +40,7 @@ spec: resources: limits: cpu: 100m - memory: 512Mi + memory: 1Gi requests: cpu: 100m - memory: 512Mi + memory: 1Gi diff --git a/k8s/deployments/ecolyo-agent-server-deployment.yml b/k8s/deployments/ecolyo-agent-server-deployment.yml index 2b733b8b3e045a2a7c9adeccbfb8bfebcd50db3d..c37c1cb5c748def2985cd26dafa9c11c181c0ff0 100644 --- a/k8s/deployments/ecolyo-agent-server-deployment.yml +++ b/k8s/deployments/ecolyo-agent-server-deployment.yml @@ -54,4 +54,4 @@ spec: cpu: 100m memory: 64Mi imagePullSecrets: - - name: llle-project + - name: forge-secret diff --git a/k8s/secrets/ecolyo-agent-server-config.yml b/k8s/secrets/ecolyo-agent-server-config.yml index 8beaebcd8d310d2546c52060152f15e5fa404c80..3f09d2beeddafaacca0ffc78ef761bc944a12510 100644 --- a/k8s/secrets/ecolyo-agent-server-config.yml +++ b/k8s/secrets/ecolyo-agent-server-config.yml @@ -20,6 +20,7 @@ stringData: BO_API_TOKEN: {{BO_API_TOKEN}} TOKEN_URL: {{TOKEN_URL}} USERINFO_URL: {{USERINFO_URL}} + FETCH_GRDF_TOKEN: {{FETCH_GRDF_TOKEN}} GRDF_CLIENT_ID: {{GRDF_CLIENT_ID}} GRDF_CLIENT_SECRET: {{GRDF_CLIENT_SECRET}} type: Opaque diff --git a/main.go b/main.go index baa3d5cb59a1e6ab4a0f15a646020afb2082146a..1ff1d184a5cf90889992b2d1dc6569b67369b207 100644 --- a/main.go +++ b/main.go @@ -16,14 +16,15 @@ import ( ) var ( - httpsPort = common.IntValueFromEnv("HTTPS_PORT", 443) // HTTPS port to serve on - debugMode = common.BoolValueFromEnv("DEBUG_MODE", false) // Debug mode, disable Secure attribute for cookies - mockOAuth2 = common.BoolValueFromEnv("MOCK_OAUTH2", false) // Enable mock OAuth2 login + httpsPort = common.IntValueFromEnv("HTTPS_PORT", 443) // HTTPS port to serve on + debugMode = common.BoolValueFromEnv("DEBUG_MODE", false) // Debug mode, disable Secure attribute for cookies + mockOAuth2 = common.BoolValueFromEnv("MOCK_OAUTH2", false) // Enable mock OAuth2 login + fetchGrdfToken = common.BoolValueFromEnv("FETCH_GRDF_TOKEN", true) // HTTPS port to serve on ) func main() { - log.Println("--- Server is starting ---") + log.Printf("--- Server is starting on port %v ---", httpsPort) // Initializations tokens.Init("./mnt/configs/tokenskey.json", debugMode) @@ -41,19 +42,38 @@ func main() { 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{}) + + // If needed, we shall request a new GRDF token every 2-hours + if fetchGrdfToken { + // Call the function immediately when the server starts + models.FetchGRDFAuthAPI() + + // then call GRDF auth api every two hours + ticker := time.NewTicker(time.Hour * 2) + go func() { + for { + select { + case <-ticker.C: + models.FetchGRDFAuthAPI() + case <-quit: + ticker.Stop() + return + } + } + }() + } + + // Deletes outdated consents every 24h + dh := models.NewDataHandler() + dailyTicker := time.NewTicker(time.Hour * 24) go func() { for { select { - case <-ticker.C: - models.FetchGRDFAuthAPI() + case <-dailyTicker.C: + models.DeleteOutdatedConsents(dh) case <-quit: - ticker.Stop() + dailyTicker.Stop() return } }