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
main
Jan Dittberner 9 months ago
parent 73735d47b6
commit 56ff01600f

@ -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/), 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). 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 ## [0.3.0] - 2023-08-07
### Changed ### Changed
- use a session to transport data from the login to the consent screens - use a session to transport data from the login to the consent screens

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

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

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

@ -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." 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" 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?" 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:" 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 }}**?" 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." HintChooseAnIdentityForAuthentication = "Choose an identity for authentication."
IndexTitle = "Welcome to your identity provider" IndexTitle = "Welcome to your identity provider"
IndexWelcomeMessage = "Go to [manage consent]({{ .ManageConsentHRef }}) to show or revoke consent you have given to client applications." 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>." IntroConsentMoreInformation = "You can find more information about **{{ .client }}** at [its description page]({{ .clientLink }})."
IntroConsentRequested = "The <strong>{{ .client }}</strong> application requested your consent for the following set of permissions:" 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." LabelConsent = "I hereby agree that the application may get the requested permissions."
LabelNever = "Never" LabelNever = "Never"
LabelSubmit = "Submit" LabelSubmit = "Submit"
@ -43,6 +43,14 @@ other = "Cancel"
description = "Title for a button to confirm consent revocation" description = "Title for a button to confirm consent revocation"
other = "Yes, Revoke!" 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] [ButtonTitleRevoke]
description = "Title for a button to revoke consent" description = "Title for a button to revoke consent"
other = "Revoke" other = "Revoke"

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

@ -1,12 +1,12 @@
{{ define "content" }} {{ define "content" }}
<main role="main" class="container"> <main role="main" class="container">
<h1 class="h3 mb-3">{{ .Title }}</h1> <h1 class="h3 mb-3">{{ .Title }}</h1>
{{ if .client.LogoUri }} {{ if .LogoURI }}
<p> <p>
<img src="{{ .client.LogoUri }}" alt="{{ .client.ClientName }}"/> <img src="{{ .LogoURI }}" alt="{{ .ClientName }}"/>
</p> </p>
{{ end }} {{ end }}
<p class="text-left">{{ .IntroConsentRequested }}</p> {{ .IntroConsentRequested }}
<form method="post"> <form method="post">
<ul class="list-group text-left small mb-3"> <ul class="list-group text-left small mb-3">
{{ range $i, $scope := .requestedScope }} {{ range $i, $scope := .requestedScope }}
@ -26,16 +26,16 @@
{{ end}} {{ end}}
</ul> </ul>
{{ end }} {{ end }}
<p class="text-left">{{ .IntroMoreInformation }}</p> {{ if .HasMoreInformation }}
{{ .IntroMoreInformation }}
{{ end }}
{{ .csrfField }} {{ .csrfField }}
<div class="checkbox mb-3"> {{ .LabelConsent }}
<label>
<input type="checkbox" name="consent" id="consent" value="true"/>
{{ .LabelConsent }}</label>
</div>
<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> </form>
</main> </main>
{{ end }} {{ end }}
Loading…
Cancel
Save