/* Copyright CAcert Inc. SPDX-License-Identifier: Apache-2.0 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package handlers import ( "bytes" "context" "fmt" "html/template" "log/slog" "net/http" "github.com/dustin/go-humanize" "github.com/gorilla/sessions" "github.com/nicksnyder/go-i18n/v2/i18n" "code.cacert.org/cacert/oidc-idp/ui" "code.cacert.org/cacert/oidc-idp/internal/services" ) const sessionName = "idp_session" func GetSession(r *http.Request) (*sessions.Session, error) { session, err := services.GetSessionStore().Get(r, sessionName) if err != nil { return nil, fmt.Errorf("could not get session") } return session, nil } type AuthMiddleware struct { logger *slog.Logger trans *services.I18NService templates TemplateCache } func getLocalizer(trans *services.I18NService, r *http.Request) *i18n.Localizer { accept := r.Header.Get("Accept-Language") return trans.Localizer(accept) } type authContextKey int const ctxKeyAddresses authContextKey = iota func (a *AuthMiddleware) NeedsAuth(handler http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { _, addresses := getDataFromClientCert(a.logger, r) if len(addresses) < 1 { renderNoEmailsInClientCertificate(a.logger, a.templates, a.trans, w, getLocalizer(a.trans, r)) return } handler.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), ctxKeyAddresses, addresses))) } } func GetAuthenticatedAddresses(r *http.Request) []string { if addresses, ok := r.Context().Value(ctxKeyAddresses).([]string); ok { return addresses } return nil } func NewAuthMiddleware(logger *slog.Logger, tc TemplateCache, trans *services.I18NService) *AuthMiddleware { return &AuthMiddleware{logger: logger, trans: trans, templates: tc} } type templateName string const ( CertificateLogin templateName = "cert" ConfirmRevoke templateName = "confirm_revoke" Consent templateName = "consent" Error templateName = "error" Index templateName = "index" LogoutSuccessful templateName = "logout_successful" ManageConsent templateName = "manage_consent" NoChallengeInRequest templateName = "no_challenge" NoEmailsInClientCertificate templateName = "no_emails" ) type TemplateCache map[templateName]*template.Template func (c TemplateCache) render( logger *slog.Logger, w http.ResponseWriter, name templateName, params map[string]interface{}, ) { rendered := bytes.NewBuffer(make([]byte, 0)) err := c[name].Lookup("base").Execute(rendered, params) if err != nil { logger.Error("template rendering failed", "error", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } _, _ = w.Write(rendered.Bytes()) } func PopulateTemplateCache() TemplateCache { funcMap := map[string]any{"humantime": humanize.Time} cache := TemplateCache{ CertificateLogin: template.Must(template.New("").Funcs(funcMap).ParseFS( ui.Templates, "templates/base.gohtml", "templates/client_certificate.gohtml", )), ConfirmRevoke: template.Must(template.New("").Funcs(funcMap).ParseFS( ui.Templates, "templates/base.gohtml", "templates/confirm_revoke.gohtml", )), NoEmailsInClientCertificate: template.Must(template.New("").Funcs(funcMap).ParseFS( ui.Templates, "templates/base.gohtml", "templates/no_email_in_client_certificate.gohtml", )), NoChallengeInRequest: template.Must(template.New("").Funcs(funcMap).ParseFS( ui.Templates, "templates/base.gohtml", "templates/no_challenge_in_request.gohtml", )), Consent: template.Must(template.New("").Funcs(funcMap).ParseFS( ui.Templates, "templates/base.gohtml", "templates/consent.gohtml", )), LogoutSuccessful: template.Must(template.New("").Funcs(funcMap).ParseFS( ui.Templates, "templates/base.gohtml", "templates/logout_successful.gohtml", )), Index: template.Must(template.New("").Funcs(funcMap).ParseFS( ui.Templates, "templates/base.gohtml", "templates/index.gohtml", )), ManageConsent: template.Must(template.New("").Funcs(funcMap).ParseFS( ui.Templates, "templates/base.gohtml", "templates/manage_consent.gohtml", )), Error: template.Must(template.ParseFS( ui.Templates, "templates/base.gohtml", "templates/errors.gohtml", )), } return cache } func renderNoEmailsInClientCertificate( logger *slog.Logger, templates TemplateCache, trans *services.I18NService, w http.ResponseWriter, localizer *i18n.Localizer, ) { msg := trans.LookupMessage err := templates[NoEmailsInClientCertificate].Lookup("base").Execute(w, map[string]interface{}{ "Title": msg("NoEmailsInClientCertificateTitle", nil, localizer), "Explanation": msg("NoEmailsInClientCertificateExplanation", nil, localizer), }) if err != nil { logger.Error("template rendering failed", "error", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } }