Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add token/userinfo claims to account read output #1419

Merged
merged 2 commits into from
Jul 26, 2021
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.

## Next

### New and Improved

* OIDC Accounts: When performing a `read` on an `oidc` type account, the
original token and userinfo claims are provided in the output. This can make
it significantly easier to write filters to create [managed
groups](https://www.boundaryproject.io/docs/concepts/filtering/oidc-managed-groups).
([PR](https://github.com/hashicorp/boundary/pull/1419))

### Bug Fixes

* config: Fix error when populating all `kms` purposes in separate blocks (as
Expand Down
10 changes: 6 additions & 4 deletions api/accounts/oidc_account_attributes.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 42 additions & 28 deletions internal/auth/oidc/repository_auth_method.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package oidc

import (
"context"
"encoding/json"
"fmt"
"net/url"
"strings"
Expand Down Expand Up @@ -75,55 +76,68 @@ func (r *Repository) upsertAccount(ctx context.Context, am *AuthMethod, IdTokenC
values := []interface{}{pubId, am.PublicId, iss, sub}
var conflictClauses, fieldMasks, nullMasks []string

var foundEmail, foundName interface{}
{
marshaledTokenClaims, err := json.Marshal(IdTokenClaims)
if err != nil {
return nil, errors.Wrap(err, op)
}
columns, values = append(columns, "token_claims"), append(values, string(marshaledTokenClaims))
conflictClauses = append(conflictClauses, fmt.Sprintf("token_claims = $%d", len(values)))
fieldMasks = append(fieldMasks, TokenClaimsField)
}
{
marshaledAccessTokenClaims, err := json.Marshal(AccessTokenClaims)
if err != nil {
return nil, errors.Wrap(err, op)
}
columns, values = append(columns, "userinfo_claims"), append(values, string(marshaledAccessTokenClaims))
conflictClauses = append(conflictClauses, fmt.Sprintf("userinfo_claims = $%d", len(values)))
fieldMasks = append(fieldMasks, UserinfoClaimsField)
}

issAsUrl, err := url.Parse(iss)
if err != nil {
return nil, errors.New(errors.Unknown, op, "unable to parse issuer", errors.WithWrap(err))
}
acctForOplog, err := NewAccount(am.PublicId, sub, WithIssuer(issAsUrl))
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create new acct for oplog"))
}

var foundName interface{}
switch {
case AccessTokenClaims[fromName] != nil:
foundName = AccessTokenClaims[fromName]
columns, values = append(columns, "full_name"), append(values, foundName)
case IdTokenClaims[fromName] != nil:
foundName = IdTokenClaims[fromName]
columns, values = append(columns, "full_name"), append(values, foundName)
default:
}
if foundName != nil {
acctForOplog.FullName = foundName.(string)
conflictClauses = append(conflictClauses, fmt.Sprintf("full_name = $%d", len(values)))
fieldMasks = append(fieldMasks, NameField)
} else {
conflictClauses = append(conflictClauses, "full_name = NULL")
nullMasks = append(nullMasks, NameField)
}

var foundEmail interface{}
switch {
case AccessTokenClaims[fromEmail] != nil:
foundEmail = AccessTokenClaims[fromEmail]
columns, values = append(columns, "email"), append(values, foundEmail)
case IdTokenClaims[fromEmail] != nil:
foundEmail = IdTokenClaims[fromEmail]
columns, values = append(columns, "email"), append(values, foundEmail)
default:
conflictClauses = append(conflictClauses, "email = NULL")
nullMasks = append(nullMasks, "Email")
}

if foundName != nil {
values = append(values, foundName)
conflictClauses = append(conflictClauses, fmt.Sprintf("full_name = $%d", len(values)))
fieldMasks = append(fieldMasks, NameField)
}
if foundEmail != nil {
values = append(values, foundEmail)
acctForOplog.Email = foundEmail.(string)
conflictClauses = append(conflictClauses, fmt.Sprintf("email = $%d", len(values)))
fieldMasks = append(fieldMasks, "Email")
}

issAsUrl, err := url.Parse(iss)
if err != nil {
return nil, errors.New(errors.Unknown, op, "unable to parse issuer", errors.WithWrap(err))
}
acctForOplog, err := NewAccount(am.PublicId, sub, WithIssuer(issAsUrl))
if err != nil {
return nil, errors.Wrap(err, op, errors.WithMsg("unable to create new acct for oplog"))
}

if foundName != nil {
acctForOplog.FullName = foundName.(string)
}
if foundEmail != nil {
acctForOplog.Email = foundEmail.(string)
} else {
conflictClauses = append(conflictClauses, "email = NULL")
nullMasks = append(nullMasks, "Email")
}

placeHolders := make([]string, 0, len(columns))
Expand Down
40 changes: 24 additions & 16 deletions internal/auth/oidc/repository_auth_method_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,11 @@ func Test_upsertAccount(t *testing.T) {
idClaims: map[string]interface{}{"iss": "https://alice-active-priv.com", "sub": "success-defaults"},
atClaims: map[string]interface{}{},
wantAcct: &Account{Account: &store.Account{
AuthMethodId: amActivePriv.PublicId,
Issuer: "https://alice-active-priv.com",
Subject: "success-defaults",
AuthMethodId: amActivePriv.PublicId,
Issuer: "https://alice-active-priv.com",
Subject: "success-defaults",
TokenClaims: `{"iss":"https://alice-active-priv.com","sub":"success-defaults"}`,
UserinfoClaims: "{}",
}},
},
{
Expand All @@ -72,11 +74,13 @@ func Test_upsertAccount(t *testing.T) {
idClaims: map[string]interface{}{"iss": "https://alice-active-priv.com", "sub": "success-atTk-full-name-and-email"},
atClaims: map[string]interface{}{"name": "alice eve-smith", "email": "[email protected]"},
wantAcct: &Account{Account: &store.Account{
AuthMethodId: amActivePriv.PublicId,
Issuer: "https://alice-active-priv.com",
Subject: "success-atTk-full-name-and-email",
Email: "[email protected]",
FullName: "alice eve-smith",
AuthMethodId: amActivePriv.PublicId,
Issuer: "https://alice-active-priv.com",
Subject: "success-atTk-full-name-and-email",
Email: "[email protected]",
FullName: "alice eve-smith",
TokenClaims: `{"iss":"https://alice-active-priv.com","sub":"success-atTk-full-name-and-email"}`,
UserinfoClaims: `{"email":"[email protected]","name":"alice eve-smith"}`,
}},
},
{
Expand All @@ -85,11 +89,13 @@ func Test_upsertAccount(t *testing.T) {
idClaims: map[string]interface{}{"iss": "https://alice-active-priv.com", "sub": "success-idTk-full-name-and-email", "name": "alice eve-smith", "email": "[email protected]"},
atClaims: map[string]interface{}{},
wantAcct: &Account{Account: &store.Account{
AuthMethodId: amActivePriv.PublicId,
Issuer: "https://alice-active-priv.com",
Subject: "success-idTk-full-name-and-email",
Email: "[email protected]",
FullName: "alice eve-smith",
AuthMethodId: amActivePriv.PublicId,
Issuer: "https://alice-active-priv.com",
Subject: "success-idTk-full-name-and-email",
Email: "[email protected]",
FullName: "alice eve-smith",
TokenClaims: `{"email":"[email protected]","iss":"https://alice-active-priv.com","name":"alice eve-smith","sub":"success-idTk-full-name-and-email"}`,
UserinfoClaims: `{}`,
}},
},
{
Expand All @@ -98,9 +104,11 @@ func Test_upsertAccount(t *testing.T) {
idClaims: map[string]interface{}{"iss": "https://alice-active-priv.com", "sub": "success-defaults", "oid": "success-map"},
atClaims: map[string]interface{}{},
wantAcct: &Account{Account: &store.Account{
AuthMethodId: amWithMapping.PublicId,
Issuer: "https://alice-active-priv.com",
Subject: "success-map",
AuthMethodId: amWithMapping.PublicId,
Issuer: "https://alice-active-priv.com",
Subject: "success-map",
TokenClaims: `{"iss":"https://alice-active-priv.com","oid":"success-map","sub":"success-defaults"}`,
UserinfoClaims: `{}`,
}},
},
{
Expand Down
4 changes: 3 additions & 1 deletion internal/auth/oidc/repository_auth_method_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const (
CertificatesField = "Certificates"
ClaimsScopesField = "ClaimsScopes"
AccountClaimMapsField = "AccountClaimMaps"
TokenClaimsField = "TokenClaims"
UserinfoClaimsField = "UserinfoClaims"
)

// UpdateAuthMethod will retrieve the auth method from the repository,
Expand Down Expand Up @@ -654,7 +656,7 @@ func applyUpdate(new, orig *AuthMethod, fieldMaskPaths []string) *AuthMethod {
// (and associated data) are validated with the retrieved document. The issuer and
// id token signing algorithm in the configuration are validated with the
// retrieved document. ValidateDiscoveryInfo also verifies the authorization, token,
// and user_info endpoints by connecting to each and uses any certificates in the
// and userinfo endpoints by connecting to each and uses any certificates in the
// configuration as trust anchors to confirm connectivity.
//
// Options supported are: WithPublicId, WithAuthMethod
Expand Down
Loading