167 lines
5.5 KiB
Go
167 lines
5.5 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/go-playground/form/v4"
|
|
"github.com/gorilla/csrf"
|
|
"github.com/lestrrat-go/jwx/jwt/openid"
|
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
|
"github.com/ory/hydra-client-go/client/admin"
|
|
"github.com/ory/hydra-client-go/models"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"git.cacert.org/oidc_login/idp/services"
|
|
)
|
|
|
|
type consentHandler struct {
|
|
adminClient *admin.Client
|
|
bundle *i18n.Bundle
|
|
consentTemplate *template.Template
|
|
logger *log.Logger
|
|
messageCatalog *services.MessageCatalog
|
|
}
|
|
|
|
type ConsentInformation struct {
|
|
ConsentChecked bool `form:"consent"`
|
|
}
|
|
|
|
func (h *consentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
challenge := r.URL.Query().Get("consent_challenge")
|
|
h.logger.Debugf("received consent challenge %s", challenge)
|
|
accept := r.Header.Get("Accept-Language")
|
|
localizer := i18n.NewLocalizer(h.bundle, accept)
|
|
|
|
// retrieve consent information
|
|
consentData, err := h.adminClient.GetConsentRequest(
|
|
admin.NewGetConsentRequestParams().WithConsentChallenge(challenge))
|
|
if err != nil {
|
|
h.logger.Error("error getting consent information: %v", err)
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
trans := h.messageCatalog.LookupMessage
|
|
|
|
// render consent form
|
|
client := consentData.GetPayload().Client
|
|
err = h.consentTemplate.Lookup("base").Execute(w, map[string]interface{}{
|
|
"Title": trans("TitleRequestConsent", nil, localizer),
|
|
csrf.TemplateTag: csrf.TemplateField(r),
|
|
"errors": map[string]string{},
|
|
"client": client,
|
|
"requestedScope": h.mapRequestedScope(consentData.GetPayload().RequestedScope, localizer),
|
|
"LabelSubmit": trans("LabelSubmit", nil, localizer),
|
|
"LabelConsent": trans("LabelConsent", nil, localizer),
|
|
"IntroMoreInformation": template.HTML(trans("IntroConsentMoreInformation", map[string]interface{}{
|
|
"client": client.ClientName,
|
|
"clientLink": client.ClientURI,
|
|
}, localizer)),
|
|
"IntroConsentRequested": template.HTML(trans("IntroConsentRequested", map[string]interface{}{
|
|
"client": client.ClientName,
|
|
}, localizer)),
|
|
})
|
|
break
|
|
case http.MethodPost:
|
|
var consentInfo ConsentInformation
|
|
|
|
// validate input
|
|
decoder := form.NewDecoder()
|
|
if err := decoder.Decode(&consentInfo, r.Form); err != nil {
|
|
h.logger.Error(err)
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if consentInfo.ConsentChecked {
|
|
idTokenData := make(map[string]interface{}, 0)
|
|
|
|
for _, scope := range consentData.GetPayload().RequestedScope {
|
|
switch scope {
|
|
case "email":
|
|
idTokenData[openid.EmailKey] = "john@theripper.mil"
|
|
idTokenData[openid.EmailVerifiedKey] = true
|
|
break
|
|
case "profile":
|
|
idTokenData[openid.GivenNameKey] = "John"
|
|
idTokenData[openid.FamilyNameKey] = "The ripper"
|
|
idTokenData[openid.MiddleNameKey] = ""
|
|
idTokenData[openid.NameKey] = "John the Ripper"
|
|
idTokenData[openid.BirthdateKey] = "1970-01-01"
|
|
idTokenData[openid.ZoneinfoKey] = "Europe/London"
|
|
idTokenData[openid.LocaleKey] = "en_UK"
|
|
idTokenData["https://cacert.localhost/groups"] = []string{"admin", "user"}
|
|
break
|
|
}
|
|
}
|
|
|
|
sessionData := &models.ConsentRequestSession{
|
|
AccessToken: nil,
|
|
IDToken: idTokenData,
|
|
}
|
|
consentRequest, err := h.adminClient.AcceptConsentRequest(
|
|
admin.NewAcceptConsentRequestParams().WithConsentChallenge(challenge).WithBody(
|
|
&models.AcceptConsentRequest{
|
|
GrantAccessTokenAudience: nil,
|
|
GrantScope: consentData.GetPayload().RequestedScope,
|
|
HandledAt: models.NullTime(time.Now()),
|
|
Remember: true,
|
|
RememberFor: 86400,
|
|
Session: sessionData,
|
|
}).WithTimeout(time.Second * 10))
|
|
if err != nil {
|
|
h.logger.Error(err)
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Add("Location", *consentRequest.GetPayload().RedirectTo)
|
|
w.WriteHeader(http.StatusFound)
|
|
} else {
|
|
consentRequest, err := h.adminClient.RejectConsentRequest(
|
|
admin.NewRejectConsentRequestParams().WithConsentChallenge(challenge).WithBody(
|
|
&models.RejectRequest{}))
|
|
if err != nil {
|
|
h.logger.Error(err)
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Add("Location", *consentRequest.GetPayload().RedirectTo)
|
|
w.WriteHeader(http.StatusFound)
|
|
}
|
|
}
|
|
}
|
|
|
|
type scopeWithLabel struct {
|
|
Name string
|
|
Label string
|
|
}
|
|
|
|
func (h *consentHandler) mapRequestedScope(scope models.StringSlicePipeDelimiter, localizer *i18n.Localizer) []*scopeWithLabel {
|
|
result := make([]*scopeWithLabel, 0)
|
|
for _, scopeName := range scope {
|
|
result = append(result, &scopeWithLabel{Name: scopeName, Label: h.messageCatalog.LookupMessage(
|
|
fmt.Sprintf("Scope-%s-Description", scopeName), nil, localizer)})
|
|
}
|
|
return result
|
|
}
|
|
|
|
func NewConsentHandler(logger *log.Logger, ctx context.Context) (*consentHandler, error) {
|
|
consentTemplate, err := template.ParseFiles("templates/base.gohtml", "templates/consent.gohtml")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &consentHandler{
|
|
adminClient: ctx.Value(CtxAdminClient).(*admin.Client),
|
|
bundle: ctx.Value(services.CtxI18nBundle).(*i18n.Bundle),
|
|
consentTemplate: consentTemplate,
|
|
logger: logger,
|
|
messageCatalog: ctx.Value(services.CtxI18nCatalog).(*services.MessageCatalog),
|
|
}, nil
|
|
}
|