2023-08-03 21:49:57 +00:00
|
|
|
/*
|
2024-05-11 20:42:21 +00:00
|
|
|
Copyright CAcert Inc.
|
2023-08-03 21:49:57 +00:00
|
|
|
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 (
|
2023-08-07 13:15:45 +00:00
|
|
|
"bytes"
|
|
|
|
"context"
|
2023-08-03 21:49:57 +00:00
|
|
|
"fmt"
|
2023-08-07 13:15:45 +00:00
|
|
|
"html/template"
|
2024-05-11 23:07:34 +00:00
|
|
|
"log/slog"
|
2023-08-03 21:49:57 +00:00
|
|
|
"net/http"
|
|
|
|
|
2023-08-07 13:15:45 +00:00
|
|
|
"github.com/dustin/go-humanize"
|
2023-08-03 21:49:57 +00:00
|
|
|
"github.com/gorilla/sessions"
|
2023-08-07 13:15:45 +00:00
|
|
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
|
|
|
|
|
|
|
"code.cacert.org/cacert/oidc-idp/ui"
|
2023-08-03 21:49:57 +00:00
|
|
|
|
|
|
|
"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
|
|
|
|
}
|
2023-08-07 13:15:45 +00:00
|
|
|
|
|
|
|
type AuthMiddleware struct {
|
2024-05-11 23:07:34 +00:00
|
|
|
logger *slog.Logger
|
2023-08-07 13:15:45 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-05-11 23:07:34 +00:00
|
|
|
func NewAuthMiddleware(logger *slog.Logger, tc TemplateCache, trans *services.I18NService) *AuthMiddleware {
|
2023-08-07 13:15:45 +00:00
|
|
|
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(
|
2024-05-11 23:07:34 +00:00
|
|
|
logger *slog.Logger, w http.ResponseWriter, name templateName, params map[string]interface{},
|
2023-08-07 13:15:45 +00:00
|
|
|
) {
|
|
|
|
rendered := bytes.NewBuffer(make([]byte, 0))
|
|
|
|
|
|
|
|
err := c[name].Lookup("base").Execute(rendered, params)
|
|
|
|
if err != nil {
|
2024-05-11 23:07:34 +00:00
|
|
|
logger.Error("template rendering failed", "error", err)
|
2023-08-07 13:15:45 +00:00
|
|
|
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(
|
2024-05-11 23:07:34 +00:00
|
|
|
logger *slog.Logger,
|
2023-08-07 13:15:45 +00:00
|
|
|
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 {
|
2024-05-11 23:07:34 +00:00
|
|
|
logger.Error("template rendering failed", "error", err)
|
2023-08-07 13:15:45 +00:00
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|