Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions model/oauth.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package model

import (
"encoding/json"
"errors"
"strconv"
"strings"
Expand Down Expand Up @@ -112,7 +113,10 @@ type OauthUserBase struct {
type OidcUser struct {
OauthUserBase
Sub string `json:"sub"`
VerifiedEmail bool `json:"email_verified"`
// VerifiedEmail is declared as json.RawMessage because some OIDC providers
// (e.g. Amazon Cognito) return email_verified as a JSON string ("true"/"false")
// instead of a JSON boolean, which violates the OIDC Core spec (section 5.1).
VerifiedEmail json.RawMessage `json:"email_verified"`
PreferredUsername string `json:"preferred_username"`
Picture string `json:"picture"`
}
Expand All @@ -126,12 +130,16 @@ func (ou *OidcUser) ToOauthUser() *OauthUser {
username = strings.ToLower(ou.Email)
}

// email_verified may be a JSON boolean or a JSON string depending on the provider.
// When the field is absent or null, ou.VerifiedEmail is nil and defaults to false.
verifiedEmail := strings.EqualFold(strings.Trim(string(ou.VerifiedEmail), `"`), "true")

return &OauthUser{
OpenId: ou.Sub,
Name: ou.Name,
Username: username,
Email: ou.Email,
VerifiedEmail: ou.VerifiedEmail,
VerifiedEmail: verifiedEmail,
Picture: ou.Picture,
}
}
Expand Down
57 changes: 57 additions & 0 deletions model/oauth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package model

import (
"encoding/json"
"testing"
)

func TestOidcUser_ToOauthUser_EmailVerifiedBoolTrue(t *testing.T) {
u := &OidcUser{VerifiedEmail: json.RawMessage(`true`)}
if !u.ToOauthUser().VerifiedEmail {
t.Fatal("expected true for JSON boolean true")
}
}

func TestOidcUser_ToOauthUser_EmailVerifiedBoolFalse(t *testing.T) {
u := &OidcUser{VerifiedEmail: json.RawMessage(`false`)}
if u.ToOauthUser().VerifiedEmail {
t.Fatal("expected false for JSON boolean false")
}
}

func TestOidcUser_ToOauthUser_EmailVerifiedStringTrue(t *testing.T) {
u := &OidcUser{VerifiedEmail: json.RawMessage(`"true"`)}
if !u.ToOauthUser().VerifiedEmail {
t.Fatal("expected true for JSON string \"true\"")
}
}

func TestOidcUser_ToOauthUser_EmailVerifiedStringFalse(t *testing.T) {
u := &OidcUser{VerifiedEmail: json.RawMessage(`"false"`)}
if u.ToOauthUser().VerifiedEmail {
t.Fatal("expected false for JSON string \"false\"")
}
}

func TestOidcUser_ToOauthUser_EmailVerifiedStringCaseInsensitive(t *testing.T) {
for _, s := range []string{`"True"`, `"TRUE"`} {
u := &OidcUser{VerifiedEmail: json.RawMessage(s)}
if !u.ToOauthUser().VerifiedEmail {
t.Fatalf("expected true for %s", s)
}
}
}

func TestOidcUser_ToOauthUser_EmailVerifiedNull(t *testing.T) {
u := &OidcUser{VerifiedEmail: json.RawMessage(`null`)}
if u.ToOauthUser().VerifiedEmail {
t.Fatal("expected false for JSON null")
}
}

func TestOidcUser_ToOauthUser_EmailVerifiedAbsent(t *testing.T) {
u := &OidcUser{}
if u.ToOauthUser().VerifiedEmail {
t.Fatal("expected false when email_verified field is absent")
}
}