Improve consent handling

- hide client logo if there is no logo URI
- hide client information link if there is no client URI
- use buttons instead of a checkbox for consent
- use Markdown for messages
This commit is contained in:
Jan Dittberner 2023-08-07 17:38:57 +02:00
parent 73735d47b6
commit 56ff01600f
7 changed files with 92 additions and 54 deletions

View file

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- HTML for client logo image is only rendered if a client application has a logo URL
- link to client information is only rendered if a client application has a client URL
- use Consent and Deny buttons instead of a checkbox when asking for consent
## [0.3.0] - 2023-08-07
### Changed
- use a session to transport data from the login to the consent screens

View file

@ -49,7 +49,7 @@ type ConsentHandler struct {
type ConsentInformation struct {
GrantedScopes []string `form:"scope"`
SelectedClaims []string `form:"claims"`
ConsentChecked bool `form:"consent"`
ConsentAction string `form:"consent"`
}
type UserInfo struct {
@ -188,7 +188,7 @@ func (h *ConsentHandler) handlePost(
return
}
if consentInfo.ConsentChecked {
if consentInfo.ConsentAction == "consent" {
consentRequest, err := h.rememberNewConsent(consentData, consentInfo, requestedClaims, session)
if err != nil {
h.logger.WithError(err).Error("could not accept consent")
@ -357,36 +357,42 @@ func (h *ConsentHandler) renderConsentForm(
claims *models.OIDCClaimsRequest,
localizer *i18n.Localizer,
) {
trans := func(id string, values ...map[string]interface{}) string {
if len(values) > 0 {
return h.trans.LookupMessage(id, values[0], localizer)
}
return h.trans.LookupMessage(id, nil, localizer)
trans := h.trans.LookupMessage
transMarkdown := func(id string, params map[string]interface{}, localizer *i18n.Localizer) template.HTML {
return template.HTML( //nolint:gosec
h.trans.LookupMarkdownMessage(id, params, localizer),
)
}
// render consent form
oAuth2Client := consentRequest.Client
clientLogoURI := oAuth2Client.GetLogoUri()
clientName := template.HTMLEscaper(oAuth2Client.GetClientName())
clientURI := oAuth2Client.GetClientUri()
h.templates.render(h.logger, w, Consent, map[string]interface{}{
"Title": trans("TitleRequestConsent"),
csrf.TemplateTag: csrf.TemplateField(r),
"errors": map[string]string{},
"client": oAuth2Client,
"requestedScope": h.mapRequestedScope(consentRequest.RequestedScope, localizer),
"requestedClaims": h.mapRequestedClaims(claims, localizer),
"LabelSubmit": trans("LabelSubmit"),
"LabelConsent": trans("LabelConsent"),
"IntroMoreInformation": template.HTML( //nolint:gosec
trans("IntroConsentMoreInformation", map[string]interface{}{
"client": oAuth2Client.GetClientName(),
"clientLink": oAuth2Client.GetClientUri(),
})),
"ClaimsInformation": template.HTML( //nolint:gosec
trans("ClaimsInformation", nil)),
"IntroConsentRequested": template.HTML( //nolint:gosec
trans("IntroConsentRequested", map[string]interface{}{
"client": oAuth2Client.GetClientName(),
})),
"Title": trans("TitleRequestConsent", nil, localizer),
csrf.TemplateTag: csrf.TemplateField(r),
"errors": map[string]string{},
"LogoURI": clientLogoURI,
"ClientName": clientName,
"requestedScope": h.mapRequestedScope(consentRequest.RequestedScope, localizer),
"requestedClaims": h.mapRequestedClaims(claims, localizer),
"ButtonTitleConsent": trans("ButtonTitleConsent", nil, localizer),
"ButtonTitleDeny": trans("ButtonTitleDeny", nil, localizer),
"HasMoreInformation": clientURI != "",
"IntroMoreInformation": transMarkdown(
"IntroConsentMoreInformation", map[string]interface{}{
"client": clientName,
"clientLink": clientURI,
}, localizer),
"LabelConsent": transMarkdown("LabelConsent", nil, localizer),
"ClaimsInformation": transMarkdown(
"ClaimsInformation", nil, localizer),
"IntroConsentRequested": transMarkdown(
"IntroConsentRequested", map[string]interface{}{
"client": clientName,
}, localizer),
})
}

View file

@ -181,6 +181,16 @@ func (s *I18NService) AddMessages() error {
Description: "Title for a button to cancel an action",
Other: "Cancel",
}
messages["ButtonTitleConsent"] = &i18n.Message{
ID: "ButtonTitleConsent",
Description: "Title for a button to give consent",
Other: "Consent",
}
messages["ButtonTitleDeny"] = &i18n.Message{
ID: "ButtonTitleDeny",
Description: "Title for a button to deny consent",
Other: "Deny",
}
messages["ButtonTitleRevoke"] = &i18n.Message{
ID: "ButtonTitleRevoke",
Description: "Title for a button to revoke consent",
@ -246,14 +256,12 @@ func (s *I18NService) AddMessages() error {
Other: "Unknown",
}
messages["IntroConsentRequested"] = &i18n.Message{
ID: "IntroConsentRequested",
Other: "The <strong>{{ .client }}</strong> application requested your consent for the following set of " +
"permissions:",
ID: "IntroConsentRequested",
Other: "The **{{ .client }}** application requested your consent for the following set of permissions:",
}
messages["IntroConsentMoreInformation"] = &i18n.Message{
ID: "IntroConsentMoreInformation",
Other: "You can find more information about <strong>{{ .client }}</strong> at " +
"<a href=\"{{ .clientLink }}\">its description page</a>.",
ID: "IntroConsentMoreInformation",
Other: "You can find more information about **{{ .client }}** at [its description page]({{ .clientLink }}).",
}
messages["ClaimsInformation"] = &i18n.Message{
ID: "ClaimsInformation",
@ -265,7 +273,7 @@ func (s *I18NService) AddMessages() error {
}
messages["CertLoginIntroText"] = &i18n.Message{
ID: "CertLoginIntroText",
Other: "The application <strong>{{ .ClientName }}</strong> requests a login.",
Other: "The application **{{ .ClientName }}** requests a login.",
}
messages["EmailChoiceText"] = &i18n.Message{
ID: "EmailChoiceText",

View file

@ -16,14 +16,24 @@ description = "Title for a button to confirm consent revocation"
hash = "sha1-bb25839982a1fe044fc2ac39552903c65402ad48"
other = "Ja, Widerrufen!"
[ButtonTitleConsent]
description = "Title for a button to give consent"
hash = "sha1-7f5c5d105e7a9600c7a8d3d092c7e812cf344e95"
other = "Zustimmen"
[ButtonTitleDeny]
description = "Title for a button to deny consent"
hash = "sha1-d5245e4b19ed6f0af002aee7ab488b8f822d53c7"
other = "Ablehnen"
[ButtonTitleRevoke]
description = "Title for a button to revoke consent"
hash = "sha1-b4524373ff63f37e062bd5f496e8ba04ee5c678d"
other = "Widerrufen"
[CertLoginIntroText]
hash = "sha1-e9f7c0522e49ffacc49e3fc35c6ffd31e495baf6"
other = "Die Anwendung <strong>{{ .ClientName }}</strong> fragt nach einer Anmeldung."
hash = "sha1-02871569c8d36e522cdb0716081ef62b7c7a71ec"
other = "Die Anwendung **{{ .ClientName }}** fragt nach einer Anmeldung."
[CertLoginRequestText]
hash = "sha1-1b20eea0f6fbb4ff139ecfe6b7a93c98cb14b8d7"
@ -92,12 +102,12 @@ hash = "sha1-00632c6562df53c62861c33e468e729887816419"
other = "Besuche [die Freigabeverwaltung]({{ .ManageConsentHRef }}), um deine Freigaben für Applikationen einzusehen oder zu widerrufen."
[IntroConsentMoreInformation]
hash = "sha1-f58b8378238bd433deef3c3e6b0b70d0fd0dd59e"
other = "Auf der <a href=\"{{ .clientLink }}\">Beschreibungsseite</a> findest du mehr Informationen zu <strong>{{ .client }}</strong>."
hash = "sha1-836b2931b417e98db4102731604443ee2700123c"
other = "Auf der [Beschreibungsseite]({{ .clientLink }}) findest du mehr Informationen zu **{{ .client }}**."
[IntroConsentRequested]
hash = "sha1-3ac6a3583d40b5e8930c57531f0be9706f1e0194"
other = "Die Anwendung <strong>{{ .client }}</strong> hat deine Zustimmung für die Erteilung der folgenden Berechtigungen angefragt:"
hash = "sha1-db5e67a16c181c4d27baf5d0e3bf255224b0fffc"
other = "Die Anwendung **{{ .client }}** hat deine Zustimmung für die Erteilung der folgenden Berechtigungen angefragt:"
[LabelAcceptCertLogin]
description = "Label for a button to accept certificate login"

View file

@ -1,6 +1,6 @@
AuthServerErrorExplanation = "A request that your browser sent to the authorization server caused an error. The authorization server returned details about the error that are printed below."
AuthServerErrorTitle = "Authorization server returned an error"
CertLoginIntroText = "The application <strong>{{ .ClientName }}</strong> requests a login."
CertLoginIntroText = "The application **{{ .ClientName }}** requests a login."
CertLoginRequestText = "Do you want to use the chosen identity from the certificate for authentication?"
ClaimsInformation = "In addition the application wants access to the following information:"
ConfirmRevokeExplanation = "Do you want to revoke your consent to allow **{{ .Application }}** access to identity data for **{{ .Subject }}**?"
@ -10,8 +10,8 @@ ErrorUnknown = "Unknown error"
HintChooseAnIdentityForAuthentication = "Choose an identity for authentication."
IndexTitle = "Welcome to your identity provider"
IndexWelcomeMessage = "Go to [manage consent]({{ .ManageConsentHRef }}) to show or revoke consent you have given to client applications."
IntroConsentMoreInformation = "You can find more information about <strong>{{ .client }}</strong> at <a href=\"{{ .clientLink }}\">its description page</a>."
IntroConsentRequested = "The <strong>{{ .client }}</strong> application requested your consent for the following set of permissions:"
IntroConsentMoreInformation = "You can find more information about **{{ .client }}** at [its description page]({{ .clientLink }})."
IntroConsentRequested = "The **{{ .client }}** application requested your consent for the following set of permissions:"
LabelConsent = "I hereby agree that the application may get the requested permissions."
LabelNever = "Never"
LabelSubmit = "Submit"
@ -43,6 +43,14 @@ other = "Cancel"
description = "Title for a button to confirm consent revocation"
other = "Yes, Revoke!"
[ButtonTitleConsent]
description = "Title for a button to give consent"
other = "Consent"
[ButtonTitleDeny]
description = "Title for a button to deny consent"
other = "Deny"
[ButtonTitleRevoke]
description = "Title for a button to revoke consent"
other = "Revoke"

View file

@ -1,7 +1,7 @@
{{ define "content" }}
<main role="main" class="container">
<h1>{{ .Title }}</h1>
<p>{{ .IntroText }}</p>
{{ .IntroText }}
{{ with .FlashMessage }}
<div class="alert alert-{{ .Type }}" role="alert">
{{ .Message }}

View file

@ -1,12 +1,12 @@
{{ define "content" }}
<main role="main" class="container">
<h1 class="h3 mb-3">{{ .Title }}</h1>
{{ if .client.LogoUri }}
{{ if .LogoURI }}
<p>
<img src="{{ .client.LogoUri }}" alt="{{ .client.ClientName }}"/>
<img src="{{ .LogoURI }}" alt="{{ .ClientName }}"/>
</p>
{{ end }}
<p class="text-left">{{ .IntroConsentRequested }}</p>
{{ .IntroConsentRequested }}
<form method="post">
<ul class="list-group text-left small mb-3">
{{ range $i, $scope := .requestedScope }}
@ -26,16 +26,16 @@
{{ end}}
</ul>
{{ end }}
<p class="text-left">{{ .IntroMoreInformation }}</p>
{{ if .HasMoreInformation }}
{{ .IntroMoreInformation }}
{{ end }}
{{ .csrfField }}
<div class="checkbox mb-3">
<label>
<input type="checkbox" name="consent" id="consent" value="true"/>
{{ .LabelConsent }}</label>
</div>
{{ .LabelConsent }}
<button class="btn btn-lg btn-primary btn-block" type="submit">{{ .LabelSubmit }}</button>
<button class="btn btn-primary" type="submit" name="consent"
value="consent">{{ .ButtonTitleConsent }}</button>
<button class="btn btn-outline-secondary" type="submit" name="consent" value="deny">{{ .ButtonTitleDeny }}</button>
</form>
</main>
{{ end }}