OIDC: add support for account claim maps (#1186)
This commit is contained in:
28 files changed
+1456
-200
No files matched your search
@@ -14,6 +14,7 @@ type OidcAuthMethodAttributes struct {
|
||||
IdpCaCerts []string `json:"idp_ca_certs,omitempty"`
|
||||
AllowedAudiences []string `json:"allowed_audiences,omitempty"`
|
||||
ClaimsScopes []string `json:"claims_scopes,omitempty"`
|
||||
AccountClaimMaps []string `json:"account_claim_maps,omitempty"`
|
||||
DisableDiscoveredConfigValidation bool `json:"disable_discovered_config_validation,omitempty"`
|
||||
DryRun bool `json:"dry_run,omitempty"`
|
||||
}
|
||||
@@ -87,6 +87,30 @@ func WithRecursive(recurse bool) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithOidcAuthMethodAccountClaimMaps(inAccountClaimMaps []string) Option {
|
||||
return func(o *options) {
|
||||
raw, ok := o.postMap["attributes"]
|
||||
if !ok {
|
||||
raw = interface{}(map[string]interface{}{})
|
||||
}
|
||||
val := raw.(map[string]interface{})
|
||||
val["account_claim_maps"] = inAccountClaimMaps
|
||||
o.postMap["attributes"] = val
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultOidcAuthMethodAccountClaimMaps() Option {
|
||||
return func(o *options) {
|
||||
raw, ok := o.postMap["attributes"]
|
||||
if !ok {
|
||||
raw = interface{}(map[string]interface{}{})
|
||||
}
|
||||
val := raw.(map[string]interface{})
|
||||
val["account_claim_maps"] = nil
|
||||
o.postMap["attributes"] = val
|
||||
}
|
||||
}
|
||||
|
||||
func WithOidcAuthMethodAllowedAudiences(inAllowedAudiences []string) Option {
|
||||
return func(o *options) {
|
||||
raw, ok := o.postMap["attributes"]
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc/store"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultAcctClaimMapTableName defines the default table name for an AccountClaimMap
|
||||
defaultAcctClaimMapTableName = "auth_oidc_account_claim_map"
|
||||
)
|
||||
|
||||
type AccountToClaim string
|
||||
|
||||
const (
|
||||
ToSubClaim AccountToClaim = "sub"
|
||||
ToEmailClaim AccountToClaim = "email"
|
||||
ToNameClaim AccountToClaim = "name"
|
||||
)
|
||||
|
||||
func ConvertToAccountToClaim(s string) (AccountToClaim, error) {
|
||||
const op = "oidc.(AccountToClaim).convertToAccountToClaim"
|
||||
switch s {
|
||||
case string(ToSubClaim):
|
||||
return ToSubClaim, nil
|
||||
case string(ToEmailClaim):
|
||||
return ToEmailClaim, nil
|
||||
case string(ToNameClaim):
|
||||
return ToNameClaim, nil
|
||||
default:
|
||||
return "", errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is not a valid ToAccountClaim value", s))
|
||||
}
|
||||
}
|
||||
|
||||
// AccountClaimMap defines optional OIDC scope values that are used to request
|
||||
// claims, in addition to the default scope of "openid" (see: DefaultClaimsScope).
|
||||
type AccountClaimMap struct {
|
||||
*store.AccountClaimMap
|
||||
tableName string
|
||||
}
|
||||
|
||||
func NewAccountClaimMap(authMethodId, fromClaim string, toClaim AccountToClaim) (*AccountClaimMap, error) {
|
||||
const op = "oidc.NewAccountClaimMap"
|
||||
cs := &AccountClaimMap{
|
||||
AccountClaimMap: &store.AccountClaimMap{
|
||||
OidcMethodId: authMethodId,
|
||||
FromClaim: fromClaim,
|
||||
ToClaim: string(toClaim),
|
||||
},
|
||||
}
|
||||
if err := cs.validate(op); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
// validate the AccountClaimMap. On success, it will return nil.
|
||||
func (cs *AccountClaimMap) validate(caller errors.Op) error {
|
||||
if cs.OidcMethodId == "" {
|
||||
return errors.New(errors.InvalidParameter, caller, "missing oidc auth method id")
|
||||
}
|
||||
if cs.FromClaim == "" {
|
||||
return errors.New(errors.InvalidParameter, caller, "missing from claim")
|
||||
}
|
||||
if _, err := ConvertToAccountToClaim(cs.ToClaim); err != nil {
|
||||
return errors.Wrap(err, caller)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllocClaimsScope makes an empty one in memory
|
||||
func AllocAccountClaimMap() AccountClaimMap {
|
||||
return AccountClaimMap{
|
||||
AccountClaimMap: &store.AccountClaimMap{},
|
||||
}
|
||||
}
|
||||
|
||||
// Clone a AccountClaimMap
|
||||
func (cs *AccountClaimMap) Clone() *AccountClaimMap {
|
||||
cp := proto.Clone(cs.AccountClaimMap)
|
||||
return &AccountClaimMap{
|
||||
AccountClaimMap: cp.(*store.AccountClaimMap),
|
||||
}
|
||||
}
|
||||
|
||||
// TableName returns the table name.
|
||||
func (s *AccountClaimMap) TableName() string {
|
||||
if s.tableName != "" {
|
||||
return s.tableName
|
||||
}
|
||||
return defaultAcctClaimMapTableName
|
||||
}
|
||||
|
||||
// SetTableName sets the table name.
|
||||
func (s *AccountClaimMap) SetTableName(n string) {
|
||||
s.tableName = n
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func TestAccountClaimMap_Create(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
rw := db.New(conn)
|
||||
|
||||
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
testAuthMethod := TestAuthMethod(t, conn, databaseWrapper, org.PublicId, InactiveState, "alice_rp", "my-dogs-name",
|
||||
WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]))
|
||||
|
||||
type args struct {
|
||||
authMethodId string
|
||||
to AccountToClaim
|
||||
from string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *AccountClaimMap
|
||||
wantErr bool
|
||||
wantIsErr errors.Code
|
||||
create bool
|
||||
wantCreateErr bool
|
||||
wantCreateIsErr errors.Code
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
args: args{
|
||||
authMethodId: testAuthMethod.PublicId,
|
||||
to: ToSubClaim,
|
||||
from: "oid",
|
||||
},
|
||||
create: true,
|
||||
want: func() *AccountClaimMap {
|
||||
want := AllocAccountClaimMap()
|
||||
want.OidcMethodId = testAuthMethod.PublicId
|
||||
want.ToClaim = string(ToSubClaim)
|
||||
want.FromClaim = "oid"
|
||||
return &want
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "dup", // must follow "valid" test. Url must be be unique for an OidcMethodId
|
||||
args: args{
|
||||
authMethodId: testAuthMethod.PublicId,
|
||||
to: ToSubClaim,
|
||||
from: "oid",
|
||||
},
|
||||
create: true,
|
||||
want: func() *AccountClaimMap {
|
||||
want := AllocAccountClaimMap()
|
||||
want.OidcMethodId = testAuthMethod.PublicId
|
||||
want.ToClaim = string(ToSubClaim)
|
||||
want.FromClaim = "oid"
|
||||
return &want
|
||||
}(),
|
||||
wantCreateErr: true,
|
||||
wantCreateIsErr: errors.NotUnique,
|
||||
},
|
||||
{
|
||||
name: "empty-auth-method",
|
||||
args: args{
|
||||
authMethodId: "",
|
||||
to: ToSubClaim,
|
||||
from: "oid",
|
||||
},
|
||||
wantErr: true,
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "empty-to",
|
||||
args: args{
|
||||
authMethodId: testAuthMethod.PublicId,
|
||||
from: "oid",
|
||||
},
|
||||
wantErr: true,
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "empty-from",
|
||||
args: args{
|
||||
authMethodId: testAuthMethod.PublicId,
|
||||
to: ToSubClaim,
|
||||
},
|
||||
wantErr: true,
|
||||
wantIsErr: errors.InvalidParameter,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
got, err := NewAccountClaimMap(tt.args.authMethodId, tt.args.from, tt.args.to)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Equal(tt.want, got)
|
||||
if tt.create {
|
||||
ctx := context.Background()
|
||||
err = rw.Create(ctx, got)
|
||||
if tt.wantCreateErr {
|
||||
assert.Error(err)
|
||||
assert.True(errors.Match(errors.T(tt.wantCreateIsErr), err))
|
||||
return
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
}
|
||||
found := AllocAccountClaimMap()
|
||||
require.NoError(rw.LookupWhere(ctx, &found, "oidc_method_id = ? and to_claim = ?", tt.args.authMethodId, tt.args.to))
|
||||
assert.Equal(got, &found)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountClaimMap_Delete(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
rw := db.New(conn)
|
||||
|
||||
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
|
||||
testAuthMethod :=
|
||||
TestAuthMethod(t, conn, databaseWrapper, org.PublicId, InactiveState, "alice_rp", "my-dogs-name",
|
||||
WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]), WithAudClaims("alice.com")) // seed an extra callback url to just make sure the delete only gets the right num of rows
|
||||
|
||||
testResource := func(authMethodId string, fromClaim string, toClaim AccountToClaim) *AccountClaimMap {
|
||||
c, err := NewAccountClaimMap(authMethodId, fromClaim, toClaim)
|
||||
require.NoError(t, err)
|
||||
return c
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
AccountClaimMap *AccountClaimMap
|
||||
wantRowsDeleted int
|
||||
overrides func(*AccountClaimMap)
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
AccountClaimMap: testResource(testAuthMethod.PublicId, "oid", "sub"),
|
||||
wantErr: false,
|
||||
wantRowsDeleted: 1,
|
||||
},
|
||||
{
|
||||
name: "bad-OidcMethodId",
|
||||
AccountClaimMap: testResource(testAuthMethod.PublicId, "oid", "sub"),
|
||||
overrides: func(c *AccountClaimMap) { c.OidcMethodId = "bad-id"; c.ToClaim = "sub" },
|
||||
wantErr: false,
|
||||
wantRowsDeleted: 0,
|
||||
},
|
||||
{
|
||||
name: "bad-toClaim",
|
||||
AccountClaimMap: testResource(testAuthMethod.PublicId, "oid", "sub"),
|
||||
overrides: func(c *AccountClaimMap) { c.ToClaim = "bad-aud" },
|
||||
wantErr: false,
|
||||
wantRowsDeleted: 0,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := rw.Exec(ctx, "delete from auth_oidc_account_claim_map", nil)
|
||||
require.NoError(err)
|
||||
|
||||
cp := tt.AccountClaimMap.Clone()
|
||||
require.NoError(rw.Create(ctx, &cp))
|
||||
|
||||
if tt.overrides != nil {
|
||||
tt.overrides(cp)
|
||||
}
|
||||
deletedRows, err := rw.Delete(ctx, &cp)
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
if tt.wantRowsDeleted == 0 {
|
||||
assert.Equal(tt.wantRowsDeleted, deletedRows)
|
||||
return
|
||||
}
|
||||
assert.Equal(tt.wantRowsDeleted, deletedRows)
|
||||
found := AllocAccountClaimMap()
|
||||
err = rw.LookupWhere(ctx, &found, "oidc_method_id = ? and to_claim = ?", tt.AccountClaimMap.OidcMethodId, tt.AccountClaimMap.ToClaim)
|
||||
assert.True(errors.IsNotFoundError(err))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountClaimMap_Clone(t *testing.T) {
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(err)
|
||||
m := TestAuthMethod(t, conn, databaseWrapper, org.PublicId, InactiveState, "alice_rp", "my-dogs-name",
|
||||
WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]))
|
||||
orig, err := NewAccountClaimMap(m.PublicId, "oid", ToSubClaim)
|
||||
require.NoError(err)
|
||||
cp := orig.Clone()
|
||||
assert.True(proto.Equal(cp.AccountClaimMap, orig.AccountClaimMap))
|
||||
})
|
||||
t.Run("not-equal", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(err)
|
||||
m := TestAuthMethod(t, conn, databaseWrapper, org.PublicId, InactiveState, "alice_rp", "my-dogs-name",
|
||||
WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]))
|
||||
orig, err := NewAccountClaimMap(m.PublicId, "oid", ToSubClaim)
|
||||
require.NoError(err)
|
||||
orig2, err := NewAccountClaimMap(m.PublicId, "uid", ToSubClaim)
|
||||
require.NoError(err)
|
||||
|
||||
cp := orig.Clone()
|
||||
assert.True(!proto.Equal(cp.AccountClaimMap, orig2.AccountClaimMap))
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccountClaimMap_SetTableName(t *testing.T) {
|
||||
t.Parallel()
|
||||
defaultTableName := defaultAcctClaimMapTableName
|
||||
tests := []struct {
|
||||
name string
|
||||
setNameTo string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "new-name",
|
||||
setNameTo: "new-name",
|
||||
want: "new-name",
|
||||
},
|
||||
{
|
||||
name: "reset to default",
|
||||
setNameTo: "",
|
||||
want: defaultTableName,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
def := AllocAccountClaimMap()
|
||||
require.Equal(defaultTableName, def.TableName())
|
||||
m := AllocAccountClaimMap()
|
||||
m.SetTableName(tt.setNameTo)
|
||||
assert.Equal(tt.want, m.TableName())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"github.com/hashicorp/go-kms-wrapping/structwrapping"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
kvbuilder "github.com/hashicorp/shared-secure-libs/kv-builder"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -107,6 +108,12 @@ func NewAuthMethod(scopeId string, clientId string, clientSecret ClientSecret, o
|
||||
a.SigningAlgs = append(a.SigningAlgs, string(alg))
|
||||
}
|
||||
}
|
||||
if len(opts.withAccountClaimMap) > 0 {
|
||||
a.AccountClaimMaps = make([]string, 0, len(opts.withAccountClaimMap))
|
||||
for k, v := range opts.withAccountClaimMap {
|
||||
a.AccountClaimMaps = append(a.AccountClaimMaps, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
}
|
||||
if a.OperationalState != string(InactiveState) {
|
||||
if err := a.isComplete(); err != nil {
|
||||
return nil, errors.Wrap(err, op, errors.WithMsg("new auth method being created with incomplete data but non-inactive state"))
|
||||
@@ -270,10 +277,11 @@ func (am *AuthMethod) isComplete() error {
|
||||
}
|
||||
|
||||
type convertedValues struct {
|
||||
Algs []interface{}
|
||||
Auds []interface{}
|
||||
Certs []interface{}
|
||||
ClaimsScopes []interface{}
|
||||
Algs []interface{}
|
||||
Auds []interface{}
|
||||
Certs []interface{}
|
||||
ClaimsScopes []interface{}
|
||||
AccountClaimMaps []interface{}
|
||||
}
|
||||
|
||||
// convertValueObjects converts the embedded value objects. It will return an
|
||||
@@ -284,7 +292,7 @@ func (am *AuthMethod) convertValueObjects() (*convertedValues, error) {
|
||||
return nil, errors.New(errors.InvalidPublicId, op, "missing public id")
|
||||
}
|
||||
var err error
|
||||
var addAlgs, addAuds, addCerts, addScopes []interface{}
|
||||
var addAlgs, addAuds, addCerts, addScopes, addAccountClaimMaps []interface{}
|
||||
if addAlgs, err = am.convertSigningAlgs(); err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
@@ -297,11 +305,15 @@ func (am *AuthMethod) convertValueObjects() (*convertedValues, error) {
|
||||
if addScopes, err = am.convertClaimsScopes(); err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
if addAccountClaimMaps, err = am.convertAccountClaimMaps(); err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
return &convertedValues{
|
||||
Algs: addAlgs,
|
||||
Auds: addAuds,
|
||||
Certs: addCerts,
|
||||
ClaimsScopes: addScopes,
|
||||
Algs: addAlgs,
|
||||
Auds: addAuds,
|
||||
Certs: addCerts,
|
||||
ClaimsScopes: addScopes,
|
||||
AccountClaimMaps: addAccountClaimMaps,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -380,3 +392,51 @@ func (am *AuthMethod) convertClaimsScopes() ([]interface{}, error) {
|
||||
}
|
||||
return newInterfaces, nil
|
||||
}
|
||||
|
||||
// convertAccountClaimMaps converts the embedded account claim maps from
|
||||
// []string to []interface{} where each slice element is a *AccountClaimMap. It
|
||||
// will return an error if the AuthMethod's public id is not set or it can
|
||||
// convert the account claim maps.
|
||||
func (am *AuthMethod) convertAccountClaimMaps() ([]interface{}, error) {
|
||||
const op = "oidc.(AuthMethod).convertAccountClaimMaps"
|
||||
if am.PublicId == "" {
|
||||
return nil, errors.New(errors.InvalidPublicId, op, "missing public id")
|
||||
}
|
||||
newInterfaces := make([]interface{}, 0, len(am.AccountClaimMaps))
|
||||
const (
|
||||
from = 0
|
||||
to = 1
|
||||
)
|
||||
acms, err := ParseAccountClaimMaps(am.AccountClaimMaps...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
for from, to := range acms {
|
||||
toClaim, err := ConvertToAccountToClaim(to)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
obj, err := NewAccountClaimMap(am.PublicId, from, toClaim)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
newInterfaces = append(newInterfaces, obj)
|
||||
}
|
||||
return newInterfaces, nil
|
||||
}
|
||||
|
||||
func ParseAccountClaimMaps(m ...string) (map[string]string, error) {
|
||||
const op = "oidc.parseAccountClaimMaps"
|
||||
var b kvbuilder.Builder
|
||||
if err := b.Add(m...); err != nil {
|
||||
return nil, errors.New(errors.InvalidParameter, op, "error parsing map", errors.WithWrap(err))
|
||||
}
|
||||
claimMap := map[string]string{}
|
||||
for k, v := range b.Map() {
|
||||
var ok bool
|
||||
if claimMap[k], ok = v.(string); !ok {
|
||||
return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("account claim map key %s value %q is not a string", k, v))
|
||||
}
|
||||
}
|
||||
return claimMap, nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package oidc
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/auth/oidc/store"
|
||||
@@ -573,6 +574,18 @@ func Test_convertValueObjects(t *testing.T) {
|
||||
testClaimsScopes = append(testClaimsScopes, obj)
|
||||
}
|
||||
|
||||
testClaimMaps := []string{"oid=sub", "display_name=name"}
|
||||
testAccountClaimMaps := make([]interface{}, 0, len(testClaimMaps))
|
||||
acms, err := ParseAccountClaimMaps(testClaimMaps...)
|
||||
require.NoError(t, err)
|
||||
for from, to := range acms {
|
||||
toClaim, err := ConvertToAccountToClaim(to)
|
||||
require.NoError(t, err)
|
||||
obj, err := NewAccountClaimMap(testPublicId, from, toClaim)
|
||||
require.NoError(t, err)
|
||||
testAccountClaimMaps = append(testAccountClaimMaps, obj)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
authMethodId string
|
||||
@@ -580,6 +593,7 @@ func Test_convertValueObjects(t *testing.T) {
|
||||
auds []string
|
||||
certs []string
|
||||
scopes []string
|
||||
maps []string
|
||||
wantValues *convertedValues
|
||||
wantErrMatch *errors.Template
|
||||
wantErrContains string
|
||||
@@ -591,11 +605,13 @@ func Test_convertValueObjects(t *testing.T) {
|
||||
auds: testAuds,
|
||||
certs: testCerts,
|
||||
scopes: testScopes,
|
||||
maps: testClaimMaps,
|
||||
wantValues: &convertedValues{
|
||||
Algs: testSigningAlgs,
|
||||
Auds: testAudiences,
|
||||
Certs: testCertificates,
|
||||
ClaimsScopes: testClaimsScopes,
|
||||
Algs: testSigningAlgs,
|
||||
Auds: testAudiences,
|
||||
Certs: testCertificates,
|
||||
ClaimsScopes: testClaimsScopes,
|
||||
AccountClaimMaps: testAccountClaimMaps,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -609,11 +625,12 @@ func Test_convertValueObjects(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
am := &AuthMethod{
|
||||
AuthMethod: &store.AuthMethod{
|
||||
PublicId: tt.authMethodId,
|
||||
SigningAlgs: tt.algs,
|
||||
AudClaims: tt.auds,
|
||||
Certificates: tt.certs,
|
||||
ClaimsScopes: tt.scopes,
|
||||
PublicId: tt.authMethodId,
|
||||
SigningAlgs: tt.algs,
|
||||
AudClaims: tt.auds,
|
||||
Certificates: tt.certs,
|
||||
ClaimsScopes: tt.scopes,
|
||||
AccountClaimMaps: tt.maps,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -649,6 +666,28 @@ func Test_convertValueObjects(t *testing.T) {
|
||||
assert.Equal(tt.wantValues.ClaimsScopes, convertedScopes)
|
||||
}
|
||||
|
||||
convertedMaps, err := am.convertAccountClaimMaps()
|
||||
if tt.wantErrMatch != nil {
|
||||
require.Error(err)
|
||||
assert.Truef(errors.Match(tt.wantErrMatch, err), "wanted err %q and got: %+v", tt.wantErrMatch.Code, err)
|
||||
} else {
|
||||
want := make([]*AccountClaimMap, 0, len(tt.wantValues.AccountClaimMaps))
|
||||
for _, v := range tt.wantValues.AccountClaimMaps {
|
||||
want = append(want, v.(*AccountClaimMap))
|
||||
}
|
||||
got := make([]*AccountClaimMap, 0, len(convertedMaps))
|
||||
for _, v := range convertedMaps {
|
||||
got = append(got, v.(*AccountClaimMap))
|
||||
}
|
||||
sort.Slice(want, func(a, b int) bool {
|
||||
return want[a].ToClaim < want[b].ToClaim
|
||||
})
|
||||
sort.Slice(got, func(a, b int) bool {
|
||||
return got[a].ToClaim < got[b].ToClaim
|
||||
})
|
||||
assert.Equal(want, got)
|
||||
}
|
||||
|
||||
values, err := am.convertValueObjects()
|
||||
if tt.wantErrMatch != nil {
|
||||
require.Error(err)
|
||||
@@ -659,7 +698,37 @@ func Test_convertValueObjects(t *testing.T) {
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
testSortConverted(t, tt.wantValues)
|
||||
testSortConverted(t, values)
|
||||
assert.Equal(tt.wantValues, values)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type converted []interface{}
|
||||
|
||||
func (a converted) Len() int { return len(a) }
|
||||
func (a converted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a converted) Less(i, j int) bool {
|
||||
switch a[i].(type) {
|
||||
case *SigningAlg:
|
||||
return a[i].(*SigningAlg).GetAlg() < a[j].(*SigningAlg).GetAlg()
|
||||
case *Certificate:
|
||||
return a[i].(*Certificate).GetCert() < a[j].(*Certificate).GetCert()
|
||||
case *AccountClaimMap:
|
||||
return a[i].(*AccountClaimMap).GetToClaim() < a[j].(*AccountClaimMap).GetToClaim()
|
||||
case *AudClaim:
|
||||
return a[i].(*AudClaim).GetAud() < a[j].(*AudClaim).GetAud()
|
||||
case *ClaimsScope:
|
||||
return a[i].(*ClaimsScope).GetScope() < a[j].(*ClaimsScope).GetScope()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func testSortConverted(t *testing.T, c *convertedValues) {
|
||||
sort.Sort(converted(c.Algs))
|
||||
sort.Sort(converted(c.Certs))
|
||||
sort.Sort(converted(c.AccountClaimMaps))
|
||||
sort.Sort(converted(c.Auds))
|
||||
sort.Sort(converted(c.ClaimsScopes))
|
||||
}
|
||||
@@ -41,6 +41,7 @@ type options struct {
|
||||
withKeyId string
|
||||
withIssuer *url.URL
|
||||
withOperationalState AuthMethodState
|
||||
withAccountClaimMap map[string]AccountToClaim
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
@@ -210,3 +211,10 @@ func WithOperationalState(state AuthMethodState) Option {
|
||||
o.withOperationalState = state
|
||||
}
|
||||
}
|
||||
|
||||
// WithAccountClaimMap provides an option for specifying an Account Claim map.
|
||||
func WithAccountClaimMap(acm map[string]AccountToClaim) Option {
|
||||
return func(o *options) {
|
||||
o.withAccountClaimMap = acm
|
||||
}
|
||||
}
|
||||
@@ -183,4 +183,15 @@ func Test_GetOpts(t *testing.T) {
|
||||
opts := getOpts(WithOperationalState(ActivePublicState))
|
||||
assert.Equal(ActivePublicState, opts.withOperationalState)
|
||||
})
|
||||
t.Run("WithAccountClaimMap", func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
acm := map[string]AccountToClaim{
|
||||
"oid": ToSubClaim,
|
||||
"display_name": ToNameClaim,
|
||||
}
|
||||
opts := getOpts(WithAccountClaimMap(acm))
|
||||
testOpts := getDefaultOptions()
|
||||
testOpts.withAccountClaimMap = acm
|
||||
assert.Equal(opts, testOpts)
|
||||
})
|
||||
}
|
||||
@@ -32,13 +32,39 @@ func (r *Repository) upsertAccount(ctx context.Context, am *AuthMethod, IdTokenC
|
||||
if AccessTokenClaims == nil {
|
||||
return nil, errors.New(errors.InvalidParameter, op, "missing Access Token claims")
|
||||
}
|
||||
|
||||
fromSub, fromName, fromEmail := string(ToSubClaim), string(ToNameClaim), string(ToEmailClaim)
|
||||
if len(am.AccountClaimMaps) > 0 {
|
||||
acms, err := ParseAccountClaimMaps(am.AccountClaimMaps...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
for from, to := range acms {
|
||||
toClaim, err := ConvertToAccountToClaim(to)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
switch toClaim {
|
||||
case ToSubClaim:
|
||||
fromSub = from
|
||||
case ToEmailClaim:
|
||||
fromEmail = from
|
||||
case ToNameClaim:
|
||||
fromName = from
|
||||
default:
|
||||
// should never happen, but including it just in case.
|
||||
return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s=%s is not a valid account claim map", from, to))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var iss, sub string
|
||||
var ok bool
|
||||
if iss, ok = IdTokenClaims["iss"].(string); !ok {
|
||||
return nil, errors.New(errors.Unknown, op, "issuer is not present in ID Token, which should not be possible")
|
||||
}
|
||||
if sub, ok = IdTokenClaims["sub"].(string); !ok {
|
||||
return nil, errors.New(errors.Unknown, op, "subject is not present in ID Token, which should not be possible")
|
||||
if sub, ok = IdTokenClaims[fromSub].(string); !ok {
|
||||
return nil, errors.New(errors.Unknown, op, fmt.Sprintf("mapping 'claim' %s to account subject and it is not present in ID Token", fromSub))
|
||||
}
|
||||
pubId, err := newAccountId(am.GetPublicId(), iss, sub)
|
||||
if err != nil {
|
||||
@@ -51,22 +77,22 @@ func (r *Repository) upsertAccount(ctx context.Context, am *AuthMethod, IdTokenC
|
||||
|
||||
var foundEmail, foundName interface{}
|
||||
switch {
|
||||
case AccessTokenClaims["name"] != nil:
|
||||
foundName = AccessTokenClaims["name"]
|
||||
case AccessTokenClaims[fromName] != nil:
|
||||
foundName = AccessTokenClaims[fromName]
|
||||
columns, values = append(columns, "full_name"), append(values, foundName)
|
||||
case IdTokenClaims["name"] != nil:
|
||||
foundName = IdTokenClaims["name"]
|
||||
case IdTokenClaims[fromName] != nil:
|
||||
foundName = IdTokenClaims[fromName]
|
||||
columns, values = append(columns, "full_name"), append(values, foundName)
|
||||
default:
|
||||
conflictClauses = append(conflictClauses, "full_name = NULL")
|
||||
nullMasks = append(nullMasks, NameField)
|
||||
}
|
||||
switch {
|
||||
case AccessTokenClaims["email"] != nil:
|
||||
foundEmail = AccessTokenClaims["email"]
|
||||
case AccessTokenClaims[fromEmail] != nil:
|
||||
foundEmail = AccessTokenClaims[fromEmail]
|
||||
columns, values = append(columns, "email"), append(values, foundEmail)
|
||||
case IdTokenClaims["email"] != nil:
|
||||
foundEmail = IdTokenClaims["email"]
|
||||
case IdTokenClaims[fromEmail] != nil:
|
||||
foundEmail = IdTokenClaims[fromEmail]
|
||||
columns, values = append(columns, "email"), append(values, foundEmail)
|
||||
default:
|
||||
conflictClauses = append(conflictClauses, "email = NULL")
|
||||
|
||||
@@ -112,7 +112,13 @@ func (r *Repository) CreateAuthMethod(ctx context.Context, am *AuthMethod, opt .
|
||||
}
|
||||
msgs = append(msgs, scopesOplogMsgs...)
|
||||
}
|
||||
|
||||
if len(vo.AccountClaimMaps) > 0 {
|
||||
accountClaimMapsOplogMsgs := make([]*oplog.Message, 0, len(vo.AccountClaimMaps))
|
||||
if err := w.CreateItems(ctx, vo.AccountClaimMaps, db.NewOplogMsgs(&accountClaimMapsOplogMsgs)); err != nil {
|
||||
return err
|
||||
}
|
||||
msgs = append(msgs, accountClaimMapsOplogMsgs...)
|
||||
}
|
||||
metadata := am.oplog(oplog.OpType_OP_TYPE_CREATE)
|
||||
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
)
|
||||
|
||||
func TestRepository_CreateAuthMethod(t *testing.T) {
|
||||
@@ -59,6 +61,7 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
|
||||
WithName("alice's restaurant"),
|
||||
WithDescription("it's a good place to eat"),
|
||||
WithClaimsScopes("email", "profile"),
|
||||
WithAccountClaimMap(map[string]AccountToClaim{"display_name": ToNameClaim, "oid": ToSubClaim}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, am.SigningAlgs, convertAlg(algs...))
|
||||
@@ -92,6 +95,7 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
|
||||
WithName("alice's restaurant with a twist"),
|
||||
WithDescription("it's an okay but kinda weird place to eat"),
|
||||
WithClaimsScopes("email", "profile"),
|
||||
WithAccountClaimMap(map[string]AccountToClaim{"display_name": ToNameClaim, "oid": ToSubClaim}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, am.SigningAlgs, convertAlg(algs...))
|
||||
@@ -126,6 +130,7 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
|
||||
WithName("alice's restaurant is bad"),
|
||||
WithDescription("their food is awful"),
|
||||
WithClaimsScopes("email", "profile"),
|
||||
WithAccountClaimMap(map[string]AccountToClaim{"display_name": ToNameClaim, "oid": ToSubClaim}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, am.SigningAlgs, convertAlg(algs...))
|
||||
@@ -211,6 +216,14 @@ func TestRepository_CreateAuthMethod(t *testing.T) {
|
||||
|
||||
err = db.TestVerifyOplog(t, rw, am.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
|
||||
require.NoErrorf(err, "unexpected error verifying oplog entry: %s", err)
|
||||
|
||||
found, err := repo.LookupAuthMethod(ctx, am.PublicId)
|
||||
require.NoError(err)
|
||||
found.CreateTime = got.CreateTime
|
||||
found.UpdateTime = got.UpdateTime
|
||||
found.Version = got.Version
|
||||
TestSortAuthMethods(t, []*AuthMethod{found, am})
|
||||
assert.Empty(cmp.Diff(found.AuthMethod, am.AuthMethod, protocmp.Transform()))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -161,6 +161,9 @@ func (r *Repository) getAuthMethods(ctx context.Context, authMethodId string, sc
|
||||
if agg.ClaimsScopes != "" {
|
||||
am.ClaimsScopes = strings.Split(agg.ClaimsScopes, aggregateDelimiter)
|
||||
}
|
||||
if agg.AccountClaimMaps != "" {
|
||||
am.AccountClaimMaps = strings.Split(agg.AccountClaimMaps, aggregateDelimiter)
|
||||
}
|
||||
authMethods = append(authMethods, &am)
|
||||
}
|
||||
return authMethods, nil
|
||||
@@ -191,6 +194,7 @@ type authMethodAgg struct {
|
||||
Auds string
|
||||
Certs string
|
||||
ClaimsScopes string
|
||||
AccountClaimMaps string
|
||||
}
|
||||
|
||||
// TableName returns the table name for gorm
|
||||
|
||||
@@ -28,12 +28,14 @@ func TestRepository_LookupAuthMethod(t *testing.T) {
|
||||
t,
|
||||
conn, databaseWrapper, org.PublicId, ActivePrivateState,
|
||||
"alice_rp", "alices-dogs-name",
|
||||
WithAccountClaimMap(map[string]AccountToClaim{"oid": ToSubClaim, "display_name": ToNameClaim}),
|
||||
WithApiUrl(TestConvertToUrls(t, "https://alice-active-priv.com/callback")[0]),
|
||||
WithSigningAlgs(RS256))
|
||||
amActivePub := TestAuthMethod(
|
||||
t,
|
||||
conn, databaseWrapper, org.PublicId, ActivePublicState,
|
||||
"alice_rp", "alices-dogs-name",
|
||||
WithAccountClaimMap(map[string]AccountToClaim{"oid": ToSubClaim, "display_name": ToNameClaim}),
|
||||
WithApiUrl(TestConvertToUrls(t, "https://alice-active-pub.com/callback")[0]),
|
||||
WithSigningAlgs(RS256),
|
||||
WithClaimsScopes("email", "profile"),
|
||||
@@ -100,6 +102,12 @@ func TestRepository_LookupAuthMethod(t *testing.T) {
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
if tt.want != nil && tt.want.AccountClaimMaps != nil {
|
||||
sort.Strings(tt.want.AccountClaimMaps)
|
||||
}
|
||||
if got != nil && got.AccountClaimMaps != nil {
|
||||
sort.Strings(got.AccountClaimMaps)
|
||||
}
|
||||
assert.EqualValues(tt.want, got)
|
||||
})
|
||||
}
|
||||
@@ -232,6 +240,7 @@ func TestRepository_getAuthMethods(t *testing.T) {
|
||||
WithSigningAlgs(RS256, ES256),
|
||||
WithCertificates(cert1, cert2),
|
||||
WithClaimsScopes("email", "profile"),
|
||||
WithAccountClaimMap(map[string]AccountToClaim{"oid": ToSubClaim, "display_name": ToNameClaim}),
|
||||
)
|
||||
am1b := TestAuthMethod(t, conn, databaseWrapper, org.PublicId, InactiveState, "alice_rp-2", "alices-cat-name",
|
||||
WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]))
|
||||
|
||||
@@ -38,6 +38,14 @@ func Test_upsertAccount(t *testing.T) {
|
||||
WithApiUrl(TestConvertToUrls(t, "https://alice-active-priv.com/callback")[0]),
|
||||
WithSigningAlgs(RS256))
|
||||
|
||||
amWithMapping := TestAuthMethod(
|
||||
t,
|
||||
conn, databaseWrapper, org.PublicId, ActivePrivateState,
|
||||
"alice_rp", "fido",
|
||||
WithAccountClaimMap(map[string]AccountToClaim{"oid": ToSubClaim}),
|
||||
WithApiUrl(TestConvertToUrls(t, "https://alice-active-priv.com/callback")[0]),
|
||||
WithSigningAlgs(RS256))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
am *AuthMethod
|
||||
@@ -84,6 +92,17 @@ func Test_upsertAccount(t *testing.T) {
|
||||
FullName: "alice eve-smith",
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "success-map-idTk",
|
||||
am: amWithMapping,
|
||||
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",
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "non-existent-auth-method-scope-id",
|
||||
am: func() *AuthMethod { cp := amActivePriv.Clone(); cp.ScopeId = "non-existent-scope-id"; return cp }(),
|
||||
@@ -130,7 +149,7 @@ func Test_upsertAccount(t *testing.T) {
|
||||
idClaims: map[string]interface{}{"iss": "https://alice.com"},
|
||||
atClaims: map[string]interface{}{},
|
||||
wantErrMatch: errors.T(errors.Unknown),
|
||||
wantErrContains: "subject is not present",
|
||||
wantErrContains: "to account subject and it is not present in ID Token",
|
||||
},
|
||||
{
|
||||
name: "missing-id-token-claims",
|
||||
|
||||
@@ -32,6 +32,7 @@ const (
|
||||
AudClaimsField = "AudClaims"
|
||||
CertificatesField = "Certificates"
|
||||
ClaimsScopesField = "ClaimsScopes"
|
||||
AccountClaimMapsField = "AccountClaimMaps"
|
||||
)
|
||||
|
||||
// UpdateAuthMethod will retrieve the auth method from the repository,
|
||||
@@ -89,17 +90,18 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi
|
||||
|
||||
dbMask, nullFields := dbcommon.BuildUpdatePaths(
|
||||
map[string]interface{}{
|
||||
NameField: am.Name,
|
||||
DescriptionField: am.Description,
|
||||
IssuerField: am.Issuer,
|
||||
ClientIdField: am.ClientId,
|
||||
ClientSecretField: am.ClientSecret,
|
||||
MaxAgeField: am.MaxAge,
|
||||
SigningAlgsField: am.SigningAlgs,
|
||||
ApiUrlField: am.ApiUrl,
|
||||
AudClaimsField: am.AudClaims,
|
||||
CertificatesField: am.Certificates,
|
||||
ClaimsScopesField: am.ClaimsScopes,
|
||||
NameField: am.Name,
|
||||
DescriptionField: am.Description,
|
||||
IssuerField: am.Issuer,
|
||||
ClientIdField: am.ClientId,
|
||||
ClientSecretField: am.ClientSecret,
|
||||
MaxAgeField: am.MaxAge,
|
||||
SigningAlgsField: am.SigningAlgs,
|
||||
ApiUrlField: am.ApiUrl,
|
||||
AudClaimsField: am.AudClaims,
|
||||
CertificatesField: am.Certificates,
|
||||
ClaimsScopesField: am.ClaimsScopes,
|
||||
AccountClaimMapsField: am.AccountClaimMaps,
|
||||
},
|
||||
fieldMaskPaths,
|
||||
nil,
|
||||
@@ -164,10 +166,30 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi
|
||||
return nil, db.NoRowsAffected, errors.Wrap(err, op)
|
||||
}
|
||||
|
||||
addMaps, deleteMaps, err := valueObjectChanges(origAm.PublicId, AccountClaimMapsVO, am.AccountClaimMaps, origAm.AccountClaimMaps, dbMask, nullFields)
|
||||
if err != nil {
|
||||
return nil, db.NoRowsAffected, errors.Wrap(err, op)
|
||||
}
|
||||
// we don't allow updates for "sub" claim maps, because we have no way to
|
||||
// determine if the updated "from" claim in the map might create collisions
|
||||
// with any existing account's subject.
|
||||
for _, rawCm := range addMaps {
|
||||
cm := rawCm.(*AccountClaimMap)
|
||||
if cm.ToClaim == string(ToSubClaim) {
|
||||
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, fmt.Sprintf("you cannot update account claim map %s=%s for the \"sub\" claim", cm.FromClaim, cm.ToClaim))
|
||||
}
|
||||
}
|
||||
for _, rawCm := range deleteMaps {
|
||||
cm := rawCm.(*AccountClaimMap)
|
||||
if cm.ToClaim == string(ToSubClaim) {
|
||||
return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, fmt.Sprintf("you cannot update account claim map %s=%s for the \"sub\" claim", cm.FromClaim, cm.ToClaim))
|
||||
}
|
||||
}
|
||||
|
||||
var filteredDbMask, filteredNullFields []string
|
||||
for _, f := range dbMask {
|
||||
switch f {
|
||||
case SigningAlgsField, AudClaimsField, CertificatesField, ClaimsScopesField:
|
||||
case SigningAlgsField, AudClaimsField, CertificatesField, ClaimsScopesField, AccountClaimMapsField:
|
||||
continue
|
||||
default:
|
||||
filteredDbMask = append(filteredDbMask, f)
|
||||
@@ -175,7 +197,7 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi
|
||||
}
|
||||
for _, f := range nullFields {
|
||||
switch f {
|
||||
case SigningAlgsField, AudClaimsField, CertificatesField, ClaimsScopesField:
|
||||
case SigningAlgsField, AudClaimsField, CertificatesField, ClaimsScopesField, AccountClaimMapsField:
|
||||
continue
|
||||
default:
|
||||
filteredNullFields = append(filteredNullFields, f)
|
||||
@@ -192,7 +214,9 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi
|
||||
len(addAuds) == 0 &&
|
||||
len(deleteAuds) == 0 &&
|
||||
len(addScopes) == 0 &&
|
||||
len(deleteScopes) == 0 {
|
||||
len(deleteScopes) == 0 &&
|
||||
len(addMaps) == 0 &&
|
||||
len(deleteMaps) == 0 {
|
||||
return origAm, db.NoRowsAffected, nil
|
||||
}
|
||||
|
||||
@@ -336,6 +360,25 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi
|
||||
msgs = append(msgs, addScopesOplogMsgs...)
|
||||
}
|
||||
|
||||
if len(deleteMaps) > 0 {
|
||||
deleteMapsOplogMsgs := make([]*oplog.Message, 0, len(deleteMaps))
|
||||
rowsDeleted, err := w.DeleteItems(ctx, deleteMaps, db.NewOplogMsgs(&deleteMapsOplogMsgs))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to delete account claim maps"))
|
||||
}
|
||||
if rowsDeleted != len(deleteMaps) {
|
||||
return errors.New(errors.MultipleRecords, op, fmt.Sprintf("account claim maps deleted %d did not match request for %d", rowsDeleted, len(deleteMaps)))
|
||||
}
|
||||
msgs = append(msgs, deleteMapsOplogMsgs...)
|
||||
}
|
||||
if len(addMaps) > 0 {
|
||||
addMapsOplogMsgs := make([]*oplog.Message, 0, len(addMaps))
|
||||
if err := w.CreateItems(ctx, addMaps, db.NewOplogMsgs(&addMapsOplogMsgs)); err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to add account claim maps"))
|
||||
}
|
||||
msgs = append(msgs, addMapsOplogMsgs...)
|
||||
}
|
||||
|
||||
metadata := updatedAm.oplog(oplog.OpType_OP_TYPE_UPDATE)
|
||||
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil {
|
||||
return errors.Wrap(err, op, errors.WithMsg("unable to write oplog"))
|
||||
@@ -371,16 +414,17 @@ func (r *Repository) UpdateAuthMethod(ctx context.Context, am *AuthMethod, versi
|
||||
type voName string
|
||||
|
||||
const (
|
||||
SigningAlgVO voName = "SigningAlgs"
|
||||
CertificateVO voName = "Certificates"
|
||||
AudClaimVO voName = "AudClaims"
|
||||
ClaimsScopesVO voName = "ClaimsScopes"
|
||||
SigningAlgVO voName = "SigningAlgs"
|
||||
CertificateVO voName = "Certificates"
|
||||
AudClaimVO voName = "AudClaims"
|
||||
ClaimsScopesVO voName = "ClaimsScopes"
|
||||
AccountClaimMapsVO voName = "AccountClaimMaps"
|
||||
)
|
||||
|
||||
// validVoName decides if the name is valid
|
||||
func validVoName(name voName) bool {
|
||||
switch name {
|
||||
case SigningAlgVO, CertificateVO, AudClaimVO, ClaimsScopesVO:
|
||||
case SigningAlgVO, CertificateVO, AudClaimVO, ClaimsScopesVO, AccountClaimMapsVO:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@@ -408,6 +452,25 @@ var supportedFactories = map[voName]factoryFunc{
|
||||
str := fmt.Sprintf("%s", i)
|
||||
return NewClaimsScope(publicId, str)
|
||||
},
|
||||
AccountClaimMapsVO: func(publicId string, i interface{}) (interface{}, error) {
|
||||
const op = "oidc.AccountClaimMapsFactory"
|
||||
str := fmt.Sprintf("%s", i)
|
||||
acm, err := ParseAccountClaimMaps(str)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(acm) > 1 {
|
||||
return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("unable to parse account claim map %s", str))
|
||||
}
|
||||
var from, rawTo string
|
||||
for from, rawTo = range acm {
|
||||
}
|
||||
to, err := ConvertToAccountToClaim(rawTo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, op)
|
||||
}
|
||||
return NewAccountClaimMap(publicId, from, to)
|
||||
},
|
||||
}
|
||||
|
||||
// valueObjectChanges takes the new and old list of VOs (value objects) and
|
||||
@@ -455,38 +518,38 @@ func valueObjectChanges(
|
||||
var deletes []interface{}
|
||||
if strutil.StrListContains(nullFields, string(valueObjectName)) {
|
||||
deletes = make([]interface{}, 0, len(oldVOs))
|
||||
for _, a := range oldVOs {
|
||||
alg, err := factory(publicId, a)
|
||||
for _, v := range oldVOs {
|
||||
deleteObj, err := factory(publicId, v)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
deletes = append(deletes, alg)
|
||||
delete(foundVOs, a)
|
||||
deletes = append(deletes, deleteObj)
|
||||
delete(foundVOs, v)
|
||||
}
|
||||
}
|
||||
if strutil.StrListContains(dbMask, string(valueObjectName)) {
|
||||
adds = make([]interface{}, 0, len(newVOs))
|
||||
for _, a := range newVOs {
|
||||
if _, ok := foundVOs[a]; ok {
|
||||
delete(foundVOs, a)
|
||||
for _, v := range newVOs {
|
||||
if _, ok := foundVOs[v]; ok {
|
||||
delete(foundVOs, v)
|
||||
continue
|
||||
}
|
||||
alg, err := factory(publicId, a)
|
||||
obj, err := factory(publicId, v)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
adds = append(adds, alg)
|
||||
delete(foundVOs, a)
|
||||
adds = append(adds, obj)
|
||||
delete(foundVOs, v)
|
||||
}
|
||||
}
|
||||
if len(foundVOs) > 0 {
|
||||
for a := range foundVOs {
|
||||
alg, err := factory(publicId, a)
|
||||
for v := range foundVOs {
|
||||
obj, err := factory(publicId, v)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, op)
|
||||
}
|
||||
deletes = append(deletes, alg)
|
||||
delete(foundVOs, a)
|
||||
deletes = append(deletes, obj)
|
||||
delete(foundVOs, v)
|
||||
}
|
||||
}
|
||||
return adds, deletes, nil
|
||||
@@ -508,6 +571,7 @@ func validateFieldMask(fieldMaskPaths []string) error {
|
||||
case strings.EqualFold(AudClaimsField, f):
|
||||
case strings.EqualFold(CertificatesField, f):
|
||||
case strings.EqualFold(ClaimsScopesField, f):
|
||||
case strings.EqualFold(AccountClaimMapsField, f):
|
||||
default:
|
||||
return errors.New(errors.InvalidParameter, op, fmt.Sprintf("invalid field mask: %s", f))
|
||||
}
|
||||
@@ -566,6 +630,14 @@ func applyUpdate(new, orig *AuthMethod, fieldMaskPaths []string) *AuthMethod {
|
||||
cp.ClaimsScopes = make([]string, 0, len(new.ClaimsScopes))
|
||||
cp.ClaimsScopes = append(cp.ClaimsScopes, new.ClaimsScopes...)
|
||||
}
|
||||
case AccountClaimMapsField:
|
||||
switch {
|
||||
case len(new.AccountClaimMaps) == 0:
|
||||
cp.AccountClaimMaps = nil
|
||||
default:
|
||||
cp.AccountClaimMaps = make([]string, 0, len(new.AccountClaimMaps))
|
||||
cp.AccountClaimMaps = append(cp.AccountClaimMaps, new.AccountClaimMaps...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return cp
|
||||
|
||||
@@ -68,6 +68,7 @@ func Test_UpdateAuthMethod(t *testing.T) {
|
||||
WithCertificates(tpCert[0]),
|
||||
WithSigningAlgs(Alg(tpAlg)),
|
||||
WithClaimsScopes("email", "profile"),
|
||||
WithAccountClaimMap(map[string]AccountToClaim{"display_name": "name"}),
|
||||
WithApiUrl(TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
|
||||
)
|
||||
},
|
||||
@@ -78,9 +79,10 @@ func Test_UpdateAuthMethod(t *testing.T) {
|
||||
am.Description = "the best place to eat"
|
||||
am.AudClaims = []string{"www.alice.com", "www.alice.com/admin"}
|
||||
am.ClientSecret = "This is a new secret"
|
||||
am.AccountClaimMaps = []string{"preferred_name=name"}
|
||||
return &am
|
||||
},
|
||||
fieldMasks: []string{NameField, DescriptionField, AudClaimsField, ClientSecretField},
|
||||
fieldMasks: []string{NameField, DescriptionField, AudClaimsField, ClientSecretField, AccountClaimMapsField},
|
||||
version: 1,
|
||||
want: func(orig, updateWith *AuthMethod) *AuthMethod {
|
||||
am := orig.Clone()
|
||||
@@ -90,6 +92,7 @@ func Test_UpdateAuthMethod(t *testing.T) {
|
||||
am.ClientSecret = updateWith.ClientSecret
|
||||
am.CtClientSecret = updateWith.CtClientSecret
|
||||
am.ClientSecretHmac = updateWith.ClientSecretHmac
|
||||
am.AccountClaimMaps = updateWith.AccountClaimMaps
|
||||
return am
|
||||
},
|
||||
},
|
||||
@@ -368,6 +371,34 @@ func Test_UpdateAuthMethod(t *testing.T) {
|
||||
return am
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "attempt-to-update-sub-account-claim-map",
|
||||
setup: func() *AuthMethod {
|
||||
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase)
|
||||
require.NoError(t, err)
|
||||
return TestAuthMethod(t,
|
||||
conn, databaseWrapper,
|
||||
org.PublicId,
|
||||
InactiveState,
|
||||
"alice-rp", "alice-secret",
|
||||
WithCertificates(tpCert[0]),
|
||||
WithSigningAlgs(Alg(tpAlg)),
|
||||
WithClaimsScopes("email", "profile"),
|
||||
WithAccountClaimMap(map[string]AccountToClaim{"oid": "sub"}),
|
||||
WithApiUrl(TestConvertToUrls(t, "https://www.alice.com/callback")[0]),
|
||||
)
|
||||
},
|
||||
updateWith: func(orig *AuthMethod) *AuthMethod {
|
||||
am := AllocAuthMethod()
|
||||
am.PublicId = orig.PublicId
|
||||
am.AccountClaimMaps = []string{"uid=sub"}
|
||||
return &am
|
||||
},
|
||||
fieldMasks: []string{AccountClaimMapsField},
|
||||
version: 1,
|
||||
wantErrMatch: errors.T(errors.InvalidParameter),
|
||||
},
|
||||
{
|
||||
name: "nil-authMethod",
|
||||
setup: func() *AuthMethod { return nil },
|
||||
@@ -548,6 +579,10 @@ func Test_UpdateAuthMethod(t *testing.T) {
|
||||
err = db.TestVerifyOplog(t, rw, updateWith.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))
|
||||
require.NoErrorf(err, "unexpected error verifying oplog entry: %s", err)
|
||||
}
|
||||
found, err := repo.LookupAuthMethod(ctx, want.PublicId)
|
||||
require.NoError(err)
|
||||
TestSortAuthMethods(t, []*AuthMethod{found})
|
||||
assert.Empty(cmp.Diff(found.AuthMethod, want.AuthMethod, protocmp.Transform()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -117,6 +117,12 @@ type AuthMethod struct {
|
||||
// are operatated on as a complete set.
|
||||
// @inject_tag: `gorm:"-"`
|
||||
ClaimsScopes []string `protobuf:"bytes,200,rep,name=claims_scopes,json=claimsScopes,proto3" json:"claims_scopes,omitempty" gorm:"-"`
|
||||
// account_claim_maps are optional claim maps from custom claims to the
|
||||
// standard claims of sub, name and email. These maps are represented as
|
||||
// key=value where the key equals the from_claim and the value equals the
|
||||
// to_claim. For example "oid=sub".
|
||||
// @inject_tag: `gorm:"-"`
|
||||
AccountClaimMaps []string `protobuf:"bytes,210,rep,name=account_claim_maps,json=accountClaimMaps,proto3" json:"account_claim_maps,omitempty" gorm:"-"`
|
||||
}
|
||||
|
||||
func (x *AuthMethod) Reset() {
|
||||
@@ -305,6 +311,13 @@ func (x *AuthMethod) GetClaimsScopes() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AuthMethod) GetAccountClaimMaps() []string {
|
||||
if x != nil {
|
||||
return x.AccountClaimMaps
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Account represents an OIDC account
|
||||
// the scope_id column is not included here as it is used only to ensure
|
||||
// data integrity in the database between iam users and auth methods.
|
||||
@@ -735,6 +748,87 @@ func (x *ClaimsScope) GetCreateTime() *timestamp.Timestamp {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AccountClaimMap entries are optional from/to account claim maps.
|
||||
type AccountClaimMap struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
OidcMethodId string `protobuf:"bytes,10,opt,name=oidc_method_id,json=oidcMethodId,proto3" json:"oidc_method_id,omitempty" gorm:"primary_key"`
|
||||
// from_claim is the claim from the id_token that you need to map to a
|
||||
// standard account claim.
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
FromClaim string `protobuf:"bytes,20,opt,name=from_claim,json=fromClaim,proto3" json:"from_claim,omitempty" gorm:"not_null"`
|
||||
// to_claim is the standard account claim to map the from_claim to. Valid
|
||||
// values are: sub, name, email
|
||||
// @inject_tag: `gorm:"column:to_claim;primary_key"`
|
||||
ToClaim string `protobuf:"bytes,30,opt,name=to_claim,json=toClaim,proto3" json:"to_claim,omitempty" gorm:"column:to_claim;primary_key"`
|
||||
// The create_time is set by the database.
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
CreateTime *timestamp.Timestamp `protobuf:"bytes,40,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"`
|
||||
}
|
||||
|
||||
func (x *AccountClaimMap) Reset() {
|
||||
*x = AccountClaimMap{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *AccountClaimMap) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AccountClaimMap) ProtoMessage() {}
|
||||
|
||||
func (x *AccountClaimMap) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes[6]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AccountClaimMap.ProtoReflect.Descriptor instead.
|
||||
func (*AccountClaimMap) Descriptor() ([]byte, []int) {
|
||||
return file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *AccountClaimMap) GetOidcMethodId() string {
|
||||
if x != nil {
|
||||
return x.OidcMethodId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AccountClaimMap) GetFromClaim() string {
|
||||
if x != nil {
|
||||
return x.FromClaim
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AccountClaimMap) GetToClaim() string {
|
||||
if x != nil {
|
||||
return x.ToClaim
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AccountClaimMap) GetCreateTime() *timestamp.Timestamp {
|
||||
if x != nil {
|
||||
return x.CreateTime
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_controller_storage_auth_oidc_store_v1_oidc_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDesc = []byte{
|
||||
@@ -751,7 +845,7 @@ var file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDesc = []byte{
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9d, 0x0a, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x4d,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x83, 0x0b, 0x0a, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x4d,
|
||||
0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
|
||||
0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d,
|
||||
@@ -833,78 +927,96 @@ var file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDesc = []byte{
|
||||
0x0a, 0x0c, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x18,
|
||||
0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x63, 0x6c, 0x61, 0x69, 0x6d,
|
||||
0x73, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, 0x0c, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73,
|
||||
0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x22, 0xcd, 0x03, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12,
|
||||
0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x14,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
|
||||
0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||
0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b,
|
||||
0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28,
|
||||
0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x64, 0x0a, 0x12, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e,
|
||||
0x74, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x18, 0xd2, 0x01, 0x20,
|
||||
0x03, 0x28, 0x09, 0x42, 0x35, 0xc2, 0xdd, 0x29, 0x31, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x6f, 0x75,
|
||||
0x6e, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x4d, 0x61, 0x70, 0x73, 0x12, 0x1d, 0x61, 0x74, 0x74,
|
||||
0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f,
|
||||
0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x52, 0x10, 0x61, 0x63, 0x63, 0x6f,
|
||||
0x75, 0x6e, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x4d, 0x61, 0x70, 0x73, 0x22, 0xcd, 0x03, 0x0a,
|
||||
0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c,
|
||||
0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62,
|
||||
0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d,
|
||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d,
|
||||
0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
|
||||
0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d,
|
||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12,
|
||||
0x24, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2,
|
||||
0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52,
|
||||
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a,
|
||||
0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64,
|
||||
0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63,
|
||||
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x46, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x4d,
|
||||
0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65,
|
||||
0x72, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x75, 0x6c,
|
||||
0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x75,
|
||||
0x6c, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18,
|
||||
0x6e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x91, 0x01, 0x0a,
|
||||
0x0a, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x6c, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x6f,
|
||||
0x69, 0x64, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49,
|
||||
0x64, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6c, 0x67, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
|
||||
0x61, 0x6c, 0x67, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69,
|
||||
0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72,
|
||||
0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
|
||||
0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65,
|
||||
0x22, 0x8f, 0x01, 0x0a, 0x08, 0x41, 0x75, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x24, 0x0a,
|
||||
0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f,
|
||||
0x64, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x75, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x03, 0x61, 0x75, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d,
|
||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x22, 0x94, 0x01, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||
0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f,
|
||||
0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x69, 0x64, 0x63,
|
||||
0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74,
|
||||
0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x4b, 0x0a, 0x0b,
|
||||
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73,
|
||||
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||
0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75,
|
||||
0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
|
||||
0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x4e,
|
||||
0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
|
||||
0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32,
|
||||
0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63,
|
||||
0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x3c, 0x20, 0x01,
|
||||
0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0e, 0x61,
|
||||
0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x46, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49,
|
||||
0x64, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x18, 0x50, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62,
|
||||
0x6a, 0x65, 0x63, 0x74, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a,
|
||||
0x65, 0x63, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, 0x6c, 0x6c, 0x4e, 0x61, 0x6d, 0x65,
|
||||
0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x91, 0x01, 0x0a, 0x0a, 0x53, 0x69, 0x67, 0x6e, 0x69,
|
||||
0x6e, 0x67, 0x41, 0x6c, 0x67, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d, 0x65,
|
||||
0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f,
|
||||
0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x61,
|
||||
0x6c, 0x67, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x6c, 0x67, 0x12, 0x4b, 0x0a,
|
||||
0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e,
|
||||
0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
|
||||
0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a,
|
||||
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x8f, 0x01, 0x0a, 0x08, 0x41,
|
||||
0x75, 0x64, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f,
|
||||
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x0c, 0x6f, 0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x10, 0x0a,
|
||||
0x03, 0x61, 0x75, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x75, 0x64, 0x12,
|
||||
0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
|
||||
0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||
0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x94, 0x01, 0x0a,
|
||||
0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e,
|
||||
0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64,
|
||||
0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f,
|
||||
0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63,
|
||||
0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x96, 0x01, 0x0a, 0x0b, 0x43, 0x6c,
|
||||
0x61, 0x69, 0x6d, 0x73, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64,
|
||||
0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x0c, 0x6f, 0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12,
|
||||
0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
|
||||
0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e,
|
||||
0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e,
|
||||
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d,
|
||||
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x22, 0xbe, 0x01, 0x0a, 0x0f, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x6c,
|
||||
0x61, 0x69, 0x6d, 0x4d, 0x61, 0x70, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d,
|
||||
0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
|
||||
0x6f, 0x69, 0x64, 0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a,
|
||||
0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x19, 0x0a, 0x08, 0x74,
|
||||
0x6f, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74,
|
||||
0x6f, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f,
|
||||
0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
|
||||
0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69,
|
||||
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54,
|
||||
0x69, 0x6d, 0x65, 0x22, 0x96, 0x01, 0x0a, 0x0b, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x53, 0x63,
|
||||
0x6f, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x68,
|
||||
0x6f, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x69, 0x64,
|
||||
0x63, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f,
|
||||
0x70, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12,
|
||||
0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x1e,
|
||||
0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65,
|
||||
0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74,
|
||||
0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||
0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x3e, 0x5a, 0x3c,
|
||||
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69,
|
||||
0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e,
|
||||
0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2f, 0x6f, 0x69, 0x64, 0x63,
|
||||
0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x33,
|
||||
0x69, 0x6d, 0x65, 0x42, 0x3e, 0x5a, 0x3c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
|
||||
0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e,
|
||||
0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x75,
|
||||
0x74, 0x68, 0x2f, 0x6f, 0x69, 0x64, 0x63, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74,
|
||||
0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -919,7 +1031,7 @@ func file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDescGZIP() []byte
|
||||
return file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
||||
var file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
|
||||
var file_controller_storage_auth_oidc_store_v1_oidc_proto_goTypes = []interface{}{
|
||||
(*AuthMethod)(nil), // 0: controller.storage.auth.oidc.store.v1.AuthMethod
|
||||
(*Account)(nil), // 1: controller.storage.auth.oidc.store.v1.Account
|
||||
@@ -927,22 +1039,24 @@ var file_controller_storage_auth_oidc_store_v1_oidc_proto_goTypes = []interface{
|
||||
(*AudClaim)(nil), // 3: controller.storage.auth.oidc.store.v1.AudClaim
|
||||
(*Certificate)(nil), // 4: controller.storage.auth.oidc.store.v1.Certificate
|
||||
(*ClaimsScope)(nil), // 5: controller.storage.auth.oidc.store.v1.ClaimsScope
|
||||
(*timestamp.Timestamp)(nil), // 6: controller.storage.timestamp.v1.Timestamp
|
||||
(*AccountClaimMap)(nil), // 6: controller.storage.auth.oidc.store.v1.AccountClaimMap
|
||||
(*timestamp.Timestamp)(nil), // 7: controller.storage.timestamp.v1.Timestamp
|
||||
}
|
||||
var file_controller_storage_auth_oidc_store_v1_oidc_proto_depIdxs = []int32{
|
||||
6, // 0: controller.storage.auth.oidc.store.v1.AuthMethod.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
6, // 1: controller.storage.auth.oidc.store.v1.AuthMethod.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
6, // 2: controller.storage.auth.oidc.store.v1.Account.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
6, // 3: controller.storage.auth.oidc.store.v1.Account.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
6, // 4: controller.storage.auth.oidc.store.v1.SigningAlg.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
6, // 5: controller.storage.auth.oidc.store.v1.AudClaim.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
6, // 6: controller.storage.auth.oidc.store.v1.Certificate.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
6, // 7: controller.storage.auth.oidc.store.v1.ClaimsScope.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
8, // [8:8] is the sub-list for method output_type
|
||||
8, // [8:8] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
7, // 0: controller.storage.auth.oidc.store.v1.AuthMethod.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 1: controller.storage.auth.oidc.store.v1.AuthMethod.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 2: controller.storage.auth.oidc.store.v1.Account.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 3: controller.storage.auth.oidc.store.v1.Account.update_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 4: controller.storage.auth.oidc.store.v1.SigningAlg.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 5: controller.storage.auth.oidc.store.v1.AudClaim.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 6: controller.storage.auth.oidc.store.v1.Certificate.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 7: controller.storage.auth.oidc.store.v1.ClaimsScope.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
7, // 8: controller.storage.auth.oidc.store.v1.AccountClaimMap.create_time:type_name -> controller.storage.timestamp.v1.Timestamp
|
||||
9, // [9:9] is the sub-list for method output_type
|
||||
9, // [9:9] is the sub-list for method input_type
|
||||
9, // [9:9] is the sub-list for extension type_name
|
||||
9, // [9:9] is the sub-list for extension extendee
|
||||
0, // [0:9] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_controller_storage_auth_oidc_store_v1_oidc_proto_init() }
|
||||
@@ -1023,6 +1137,18 @@ func file_controller_storage_auth_oidc_store_v1_oidc_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_controller_storage_auth_oidc_store_v1_oidc_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*AccountClaimMap); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
@@ -1030,7 +1156,7 @@ func file_controller_storage_auth_oidc_store_v1_oidc_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_controller_storage_auth_oidc_store_v1_oidc_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 6,
|
||||
NumMessages: 7,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -108,7 +108,16 @@ func TestAuthMethod(
|
||||
require.NoError(err)
|
||||
require.Equal(len(opts.withClaimsScopes), len(authMethod.ClaimsScopes))
|
||||
}
|
||||
|
||||
if len(opts.withAccountClaimMap) > 0 {
|
||||
newAccountClaimMaps := make([]interface{}, 0, len(opts.withAccountClaimMap))
|
||||
for k, v := range opts.withAccountClaimMap {
|
||||
acm, err := NewAccountClaimMap(authMethod.PublicId, k, v)
|
||||
require.NoError(err)
|
||||
newAccountClaimMaps = append(newAccountClaimMaps, acm)
|
||||
}
|
||||
require.NoError(rw.CreateItems(ctx, newAccountClaimMaps))
|
||||
require.Equal(len(opts.withAccountClaimMap), len(authMethod.AccountClaimMaps))
|
||||
}
|
||||
authMethod.OperationalState = string(state)
|
||||
rowsUpdated, err := rw.Update(ctx, authMethod, []string{OperationalStateField}, nil)
|
||||
require.NoError(err)
|
||||
@@ -140,6 +149,9 @@ func TestSortAuthMethods(t *testing.T, methods []*AuthMethod) {
|
||||
sort.Slice(am.ClaimsScopes, func(a, b int) bool {
|
||||
return am.ClaimsScopes[a] < am.ClaimsScopes[b]
|
||||
})
|
||||
sort.Slice(am.AccountClaimMaps, func(a, b int) bool {
|
||||
return am.AccountClaimMaps[a] < am.AccountClaimMaps[b]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ type extraOidcCmdVars struct {
|
||||
flagIdpCaCerts []string
|
||||
flagAllowedAudiences []string
|
||||
flagClaimsScopes []string
|
||||
flagAccountClaimMaps []string
|
||||
flagDisableDiscoveredConfigValidation bool
|
||||
flagDryRun bool
|
||||
}
|
||||
@@ -42,6 +43,7 @@ const (
|
||||
caCertFlagName = "idp-ca-cert"
|
||||
allowedAudienceFlagName = "allowed-audience"
|
||||
claimsScopes = "claims-scopes"
|
||||
accountClaimMaps = "account-claim-maps"
|
||||
stateFlagName = "state"
|
||||
disableDiscoveredConfigValidationFlagName = "disable-discovered-config-validation"
|
||||
dryRunFlagName = "dry-run"
|
||||
@@ -59,6 +61,7 @@ func extraOidcActionsFlagsMapFuncImpl() map[string][]string {
|
||||
caCertFlagName,
|
||||
allowedAudienceFlagName,
|
||||
claimsScopes,
|
||||
accountClaimMaps,
|
||||
},
|
||||
"change-state": {
|
||||
idFlagName,
|
||||
@@ -129,6 +132,12 @@ func extraOidcFlagsFuncImpl(c *OidcCommand, set *base.FlagSets, _ *base.FlagSet)
|
||||
Target: &c.flagClaimsScopes,
|
||||
Usage: `The optional claims scope requested. May be specified multiple times.`,
|
||||
})
|
||||
case accountClaimMaps:
|
||||
f.StringSliceVar(&base.StringSliceVar{
|
||||
Name: accountClaimMaps,
|
||||
Target: &c.flagAccountClaimMaps,
|
||||
Usage: `The optional account claim maps from custom claims to the standard claims of sub, name and email. These maps are represented as key=value where the key equals the Provider from-claim and the value equals the Boundary to-claim. For example "oid=sub". May be specified multiple times for different to-claims.`,
|
||||
})
|
||||
case stateFlagName:
|
||||
f.StringVar(&base.StringVar{
|
||||
Name: stateFlagName,
|
||||
@@ -258,6 +267,13 @@ func extraOidcFlagHandlingFuncImpl(c *OidcCommand, f *base.FlagSets, opts *[]aut
|
||||
default:
|
||||
*opts = append(*opts, authmethods.WithOidcAuthMethodClaimsScopes(c.flagClaimsScopes))
|
||||
}
|
||||
switch {
|
||||
case len(c.flagAccountClaimMaps) == 0:
|
||||
case len(c.flagAccountClaimMaps) == 1 && c.flagAccountClaimMaps[0] == "null":
|
||||
*opts = append(*opts, authmethods.DefaultOidcAuthMethodAccountClaimMaps())
|
||||
default:
|
||||
*opts = append(*opts, authmethods.WithOidcAuthMethodAccountClaimMaps(c.flagAccountClaimMaps))
|
||||
}
|
||||
if c.flagDisableDiscoveredConfigValidation {
|
||||
*opts = append(*opts, authmethods.WithOidcAuthMethodDisableDiscoveredConfigValidation(c.flagDisableDiscoveredConfigValidation))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
begin;
|
||||
|
||||
-- auth_oidc_account_claim_map entries are the optional claim maps from custom
|
||||
-- claims to the standard claims of sub, name and email. There can be 0 or more
|
||||
-- for each parent oidc auth method.
|
||||
create table auth_oidc_account_claim_map (
|
||||
create_time wt_timestamp,
|
||||
oidc_method_id wt_public_id
|
||||
constraint auth_oidc_method_fkey
|
||||
references auth_oidc_method(public_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
from_claim text not null
|
||||
constraint from_claim_must_not_be_empty
|
||||
check(length(trim(from_claim)) > 0)
|
||||
constraint from_claim_must_be_less_than_1024_chars
|
||||
check(length(trim(from_claim)) < 1024),
|
||||
to_claim text not null
|
||||
constraint to_claim_valid_values
|
||||
check (to_claim in ('sub', 'name', 'email')), -- intentionally case-sensitive matching
|
||||
primary key(oidc_method_id, to_claim)
|
||||
);
|
||||
comment on table auth_oidc_account_claim_map is
|
||||
'auth_oidc_account_claim_map entries are the optional claim maps from custom claims to the standard claims of sub, name and email. There can be 0 or more for each parent oidc auth method.';
|
||||
|
||||
|
||||
create trigger
|
||||
default_create_time_column
|
||||
before
|
||||
insert on auth_oidc_account_claim_map
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
|
||||
create trigger
|
||||
immutable_columns
|
||||
before
|
||||
update on auth_oidc_account_claim_map
|
||||
for each row execute procedure immutable_columns('oidc_method_id', 'from_claim', 'to_claim', 'create_time');
|
||||
|
||||
|
||||
-- we will drop the oidc_auth_method_with_value_obj view, so we can recreate it
|
||||
-- and add the oidc claim's scopes to the returned set.
|
||||
drop view oidc_auth_method_with_value_obj;
|
||||
|
||||
-- oidc_auth_method_with_value_obj is useful for reading an oidc auth method
|
||||
-- with its associated value objects (algs, auds, certs, claims scopes and
|
||||
-- account claim maps) as columns with | delimited values. The use of the
|
||||
-- postgres string_agg(...) to aggregate the value objects into a column works
|
||||
-- because we are only pulling in one column from the associated tables and that
|
||||
-- value is part of the primary key and unique. This view will make things like
|
||||
-- recursive listing of oidc auth methods fairly straightforward to implement
|
||||
-- for the oidc repo. The view also includes an is_primary_auth_method bool
|
||||
create view oidc_auth_method_with_value_obj as
|
||||
select
|
||||
case when s.primary_auth_method_id is not null then
|
||||
true
|
||||
else false end
|
||||
as is_primary_auth_method,
|
||||
am.public_id,
|
||||
am.scope_id,
|
||||
am.name,
|
||||
am.description,
|
||||
am.create_time,
|
||||
am.update_time,
|
||||
am.version,
|
||||
am.state,
|
||||
am.api_url,
|
||||
am.disable_discovered_config_validation,
|
||||
am.issuer,
|
||||
am.client_id,
|
||||
am.client_secret,
|
||||
am.client_secret_hmac,
|
||||
am.key_id,
|
||||
am.max_age,
|
||||
-- the string_agg(..) column will be null if there are no associated value objects
|
||||
string_agg(distinct alg.signing_alg_name, '|') as algs,
|
||||
string_agg(distinct aud.aud_claim, '|') as auds,
|
||||
string_agg(distinct cert.certificate, '|') as certs,
|
||||
string_agg(distinct cs.scope, '|') as claims_scopes,
|
||||
string_agg(distinct concat_ws('=', acm.from_claim, acm.to_claim), '|') as account_claim_maps
|
||||
from
|
||||
auth_oidc_method am
|
||||
left outer join iam_scope s on am.public_id = s.primary_auth_method_id
|
||||
left outer join auth_oidc_signing_alg alg on am.public_id = alg.oidc_method_id
|
||||
left outer join auth_oidc_aud_claim aud on am.public_id = aud.oidc_method_id
|
||||
left outer join auth_oidc_certificate cert on am.public_id = cert.oidc_method_id
|
||||
left outer join auth_oidc_scope cs on am.public_id = cs.oidc_method_id
|
||||
left outer join auth_oidc_account_claim_map acm on am.public_id = acm.oidc_method_id
|
||||
group by am.public_id, is_primary_auth_method; -- there can be only one public_id + is_primary_auth_method, so group by isn't a problem.
|
||||
comment on view oidc_auth_method_with_value_obj is
|
||||
'oidc auth method with its associated value objects (algs, auds, certs, scopes) as columns with | delimited values';
|
||||
|
||||
|
||||
commit;
|
||||
@@ -4,7 +4,7 @@ package schema
|
||||
|
||||
func init() {
|
||||
migrationStates["postgres"] = migrationState{
|
||||
binarySchemaVersion: 5001,
|
||||
binarySchemaVersion: 6001,
|
||||
upMigrations: map[int][]byte{
|
||||
1: []byte(`
|
||||
create domain wt_public_id as text
|
||||
@@ -6087,6 +6087,97 @@ from
|
||||
group by am.public_id, is_primary_auth_method; -- there can be only one public_id + is_primary_auth_method, so group by isn't a problem.
|
||||
comment on view oidc_auth_method_with_value_obj is
|
||||
'oidc auth method with its associated value objects (algs, auds, certs, scopes) as columns with | delimited values';
|
||||
`),
|
||||
6001: []byte(`
|
||||
-- auth_oidc_account_claim_map entries are the optional claim maps from custom
|
||||
-- claims to the standard claims of sub, name and email. There can be 0 or more
|
||||
-- for each parent oidc auth method.
|
||||
create table auth_oidc_account_claim_map (
|
||||
create_time wt_timestamp,
|
||||
oidc_method_id wt_public_id
|
||||
constraint auth_oidc_method_fkey
|
||||
references auth_oidc_method(public_id)
|
||||
on delete cascade
|
||||
on update cascade,
|
||||
from_claim text not null
|
||||
constraint from_claim_must_not_be_empty
|
||||
check(length(trim(from_claim)) > 0)
|
||||
constraint from_claim_must_be_less_than_1024_chars
|
||||
check(length(trim(from_claim)) < 1024),
|
||||
to_claim text not null
|
||||
constraint to_claim_valid_values
|
||||
CHECK (to_claim IN ('sub', 'name', 'email')), -- intentionally case-sensitive matching
|
||||
primary key(oidc_method_id, to_claim)
|
||||
);
|
||||
comment on table auth_oidc_account_claim_map is
|
||||
'auth_oidc_account_claim_map entries are the optional claim maps from custom claims to the standard claims of sub, name and email. There can be 0 or more for each parent oidc auth method.';
|
||||
|
||||
|
||||
create trigger
|
||||
default_create_time_column
|
||||
before
|
||||
insert on auth_oidc_account_claim_map
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
|
||||
create trigger
|
||||
immutable_columns
|
||||
before
|
||||
update on auth_oidc_account_claim_map
|
||||
for each row execute procedure immutable_columns('oidc_method_id', 'from_claim', 'to_claim', 'create_time');
|
||||
|
||||
|
||||
-- we will drop the oidc_auth_method_with_value_obj view, so we can recreate it
|
||||
-- and add the oidc claim's scopes to the returned set.
|
||||
drop view oidc_auth_method_with_value_obj;
|
||||
|
||||
-- oidc_auth_method_with_value_obj is useful for reading an oidc auth method
|
||||
-- with its associated value objects (algs, auds, certs, claims scopes and
|
||||
-- account claim maps) as columns with | delimited values. The use of the
|
||||
-- postgres string_agg(...) to aggregate the value objects into a column works
|
||||
-- because we are only pulling in one column from the associated tables and that
|
||||
-- value is part of the primary key and unique. This view will make things like
|
||||
-- recursive listing of oidc auth methods fairly straightforward to implement
|
||||
-- for the oidc repo. The view also includes an is_primary_auth_method bool
|
||||
create view oidc_auth_method_with_value_obj as
|
||||
select
|
||||
case when s.primary_auth_method_id is not null then
|
||||
true
|
||||
else false end
|
||||
as is_primary_auth_method,
|
||||
am.public_id,
|
||||
am.scope_id,
|
||||
am.name,
|
||||
am.description,
|
||||
am.create_time,
|
||||
am.update_time,
|
||||
am.version,
|
||||
am.state,
|
||||
am.api_url,
|
||||
am.disable_discovered_config_validation,
|
||||
am.issuer,
|
||||
am.client_id,
|
||||
am.client_secret,
|
||||
am.client_secret_hmac,
|
||||
am.key_id,
|
||||
am.max_age,
|
||||
-- the string_agg(..) column will be null if there are no associated value objects
|
||||
string_agg(distinct alg.signing_alg_name, '|') as algs,
|
||||
string_agg(distinct aud.aud_claim, '|') as auds,
|
||||
string_agg(distinct cert.certificate, '|') as certs,
|
||||
string_agg(distinct cs.scope, '|') as claims_scopes,
|
||||
string_agg(distinct concat_ws('=', acm.from_claim, acm.to_claim), '|') as account_claim_maps
|
||||
from
|
||||
auth_oidc_method am
|
||||
left outer join iam_scope s on am.public_id = s.primary_auth_method_id
|
||||
left outer join auth_oidc_signing_alg alg on am.public_id = alg.oidc_method_id
|
||||
left outer join auth_oidc_aud_claim aud on am.public_id = aud.oidc_method_id
|
||||
left outer join auth_oidc_certificate cert on am.public_id = cert.oidc_method_id
|
||||
left outer join auth_oidc_scope cs on am.public_id = cs.oidc_method_id
|
||||
left outer join auth_oidc_account_claim_map acm on am.public_id = acm.oidc_method_id
|
||||
group by am.public_id, is_primary_auth_method; -- there can be only one public_id + is_primary_auth_method, so group by isn't a problem.
|
||||
comment on view oidc_auth_method_with_value_obj is
|
||||
'oidc auth method with its associated value objects (algs, auds, certs, scopes) as columns with | delimited values';
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -285,6 +285,11 @@ type OidcAuthMethodAttributes struct {
|
||||
// Optional claims scopes that will be requested during authentication.
|
||||
// see: https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims
|
||||
ClaimsScopes []string `protobuf:"bytes,112,rep,name=claims_scopes,proto3" json:"claims_scopes,omitempty"`
|
||||
// account_claim_maps are optional claim maps from custom claims to the
|
||||
// standard claims of sub, name and email. These maps are represented as
|
||||
// key=value where the key equals the from_claim and the value equals the
|
||||
// to_claim. For example "oid=sub".
|
||||
AccountClaimMaps []string `protobuf:"bytes,113,rep,name=account_claim_maps,proto3" json:"account_claim_maps,omitempty"`
|
||||
// If the Authorization Server's discovered configuration contains values
|
||||
// that do not match the configuration set on this auth method, this can be
|
||||
// set to force the local configuration to override the discovered values.
|
||||
@@ -419,6 +424,13 @@ func (x *OidcAuthMethodAttributes) GetClaimsScopes() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *OidcAuthMethodAttributes) GetAccountClaimMaps() []string {
|
||||
if x != nil {
|
||||
return x.AccountClaimMaps
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *OidcAuthMethodAttributes) GetDisableDiscoveredConfigValidation() bool {
|
||||
if x != nil {
|
||||
return x.DisableDiscoveredConfigValidation
|
||||
@@ -815,7 +827,7 @@ var file_controller_api_resources_authmethods_v1_auth_method_proto_rawDesc = []b
|
||||
0x6e, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74,
|
||||
0x68, 0x12, 0x11, 0x4d, 0x69, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x4c, 0x65,
|
||||
0x6e, 0x67, 0x74, 0x68, 0x52, 0x13, 0x6d, 0x69, 0x6e, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f,
|
||||
0x72, 0x64, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0xfb, 0x08, 0x0a, 0x18, 0x4f, 0x69,
|
||||
0x72, 0x64, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x22, 0xe6, 0x09, 0x0a, 0x18, 0x4f, 0x69,
|
||||
0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x74, 0x74, 0x72,
|
||||
0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18,
|
||||
0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x59, 0x0a, 0x06,
|
||||
@@ -879,54 +891,60 @@ var file_controller_api_resources_authmethods_v1_auth_method_proto_rawDesc = []b
|
||||
0x01, 0xc2, 0xdd, 0x29, 0x28, 0x0a, 0x18, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
|
||||
0x73, 0x2e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12,
|
||||
0x0c, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x52, 0x0d, 0x63,
|
||||
0x6c, 0x61, 0x69, 0x6d, 0x73, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x58, 0x0a, 0x24,
|
||||
0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72,
|
||||
0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x78, 0x20, 0x01, 0x28, 0x08, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01,
|
||||
0x52, 0x24, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76,
|
||||
0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x69,
|
||||
0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75,
|
||||
0x6e, 0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x08, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x07,
|
||||
0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x22, 0x61, 0x0a, 0x27, 0x4f, 0x69, 0x64, 0x63, 0x41,
|
||||
0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74,
|
||||
0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0a,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x75, 0x72, 0x6c, 0x12, 0x1a,
|
||||
0x0a, 0x08, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x08, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0xb7, 0x01, 0x0a, 0x29, 0x4f,
|
||||
0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74,
|
||||
0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63,
|
||||
0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65,
|
||||
0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05,
|
||||
0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61,
|
||||
0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x1e, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2c, 0x0a, 0x11, 0x65, 0x72, 0x72, 0x6f,
|
||||
0x72, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x28, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x11, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72,
|
||||
0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f,
|
||||
0x75, 0x72, 0x69, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72,
|
||||
0x5f, 0x75, 0x72, 0x69, 0x22, 0x5c, 0x0a, 0x2a, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68,
|
||||
0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61,
|
||||
0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x64, 0x69,
|
||||
0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12,
|
||||
0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75,
|
||||
0x72, 0x6c, 0x22, 0x44, 0x0a, 0x26, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65,
|
||||
0x6c, 0x61, 0x69, 0x6d, 0x73, 0x5f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x12, 0x69, 0x0a, 0x12,
|
||||
0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x6d, 0x61,
|
||||
0x70, 0x73, 0x18, 0x71, 0x20, 0x03, 0x28, 0x09, 0x42, 0x39, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd,
|
||||
0x29, 0x31, 0x0a, 0x1d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x61,
|
||||
0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x5f, 0x6d, 0x61, 0x70,
|
||||
0x73, 0x12, 0x10, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x4d,
|
||||
0x61, 0x70, 0x73, 0x52, 0x12, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x6c, 0x61,
|
||||
0x69, 0x6d, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x12, 0x58, 0x0a, 0x24, 0x64, 0x69, 0x73, 0x61, 0x62,
|
||||
0x6c, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x6f,
|
||||
0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18,
|
||||
0x78, 0x20, 0x01, 0x28, 0x08, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x24, 0x64, 0x69, 0x73,
|
||||
0x61, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x65, 0x64, 0x5f,
|
||||
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x12, 0x1f, 0x0a, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72, 0x75, 0x6e, 0x18, 0x82, 0x01, 0x20,
|
||||
0x01, 0x28, 0x08, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x07, 0x64, 0x72, 0x79, 0x5f, 0x72,
|
||||
0x75, 0x6e, 0x22, 0x61, 0x0a, 0x27, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65,
|
||||
0x74, 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65,
|
||||
0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08,
|
||||
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
|
||||
0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0x41, 0x0a, 0x27, 0x4f, 0x69, 0x64, 0x63,
|
||||
0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e,
|
||||
0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x5d, 0x5a, 0x5b, 0x67,
|
||||
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63,
|
||||
0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f,
|
||||
0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
|
||||
0x65, 0x73, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x3b, 0x61,
|
||||
0x75, 0x74, 0x68, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
0x53, 0x74, 0x61, 0x72, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a,
|
||||
0x08, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x5f, 0x69, 0x64, 0x22, 0xb7, 0x01, 0x0a, 0x29, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75,
|
||||
0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69,
|
||||
0x63, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65,
|
||||
0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a,
|
||||
0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72,
|
||||
0x72, 0x6f, 0x72, 0x12, 0x2c, 0x0a, 0x11, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x73,
|
||||
0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11,
|
||||
0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x32,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x75, 0x72, 0x69, 0x22,
|
||||
0x5c, 0x0a, 0x2a, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f,
|
||||
0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x61, 0x6c,
|
||||
0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a,
|
||||
0x12, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f,
|
||||
0x75, 0x72, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x66, 0x69, 0x6e, 0x61, 0x6c,
|
||||
0x5f, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x22, 0x44, 0x0a,
|
||||
0x26, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x41,
|
||||
0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x5f, 0x69, 0x64, 0x22, 0x41, 0x0a, 0x27, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x75, 0x74, 0x68, 0x4d,
|
||||
0x65, 0x74, 0x68, 0x6f, 0x64, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74,
|
||||
0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16,
|
||||
0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
||||
0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x5d, 0x5a, 0x5b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62,
|
||||
0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
|
||||
0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f,
|
||||
0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x61, 0x75,
|
||||
0x74, 0x68, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x3b, 0x61, 0x75, 0x74, 0x68, 0x6d, 0x65,
|
||||
0x74, 0x68, 0x6f, 0x64, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -123,6 +123,13 @@ message OidcAuthMethodAttributes {
|
||||
repeated string claims_scopes = 112
|
||||
[json_name = "claims_scopes", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "attributes.claims_scopes" that: "ClaimsScopes" }];
|
||||
|
||||
// account_claim_maps are optional claim maps from custom claims to the
|
||||
// standard claims of sub, name and email. These maps are represented as
|
||||
// key=value where the key equals the from_claim and the value equals the
|
||||
// to_claim. For example "oid=sub".
|
||||
repeated string account_claim_maps = 113
|
||||
[json_name = "account_claim_maps", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "attributes.account_claim_maps" that: "AccountClaimMaps" }];
|
||||
|
||||
// If the Authorization Server's discovered configuration contains values
|
||||
// that do not match the configuration set on this auth method, this can be
|
||||
// set to force the local configuration to override the discovered values.
|
||||
|
||||
@@ -117,6 +117,13 @@ message AuthMethod {
|
||||
// are operatated on as a complete set.
|
||||
// @inject_tag: `gorm:"-"`
|
||||
repeated string claims_scopes = 200 [(custom_options.v1.mask_mapping) = { this: "ClaimsScopes" that: "attributes.claims_scopes" }];
|
||||
|
||||
// account_claim_maps are optional claim maps from custom claims to the
|
||||
// standard claims of sub, name and email. These maps are represented as
|
||||
// key=value where the key equals the from_claim and the value equals the
|
||||
// to_claim. For example "oid=sub".
|
||||
// @inject_tag: `gorm:"-"`
|
||||
repeated string account_claim_maps = 210 [(custom_options.v1.mask_mapping) = { this: "AccountClaimMaps" that: "attributes.account_claim_maps" }];
|
||||
}
|
||||
|
||||
// Account represents an OIDC account
|
||||
@@ -224,4 +231,24 @@ message ClaimsScope {
|
||||
// The create_time is set by the database.
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
timestamp.v1.Timestamp create_time = 30;
|
||||
}
|
||||
|
||||
// AccountClaimMap entries are optional from/to account claim maps.
|
||||
message AccountClaimMap {
|
||||
// @inject_tag: `gorm:"primary_key"`
|
||||
string oidc_method_id = 10;
|
||||
|
||||
// from_claim is the claim from the id_token that you need to map to a
|
||||
// standard account claim.
|
||||
// @inject_tag: `gorm:"not_null"`
|
||||
string from_claim = 20;
|
||||
|
||||
// to_claim is the standard account claim to map the from_claim to. Valid
|
||||
// values are: sub, name, email
|
||||
// @inject_tag: `gorm:"column:to_claim;primary_key"`
|
||||
string to_claim = 30;
|
||||
|
||||
// The create_time is set by the database.
|
||||
// @inject_tag: `gorm:"default:current_timestamp"`
|
||||
timestamp.v1.Timestamp create_time = 40;
|
||||
}
|
||||
@@ -678,6 +678,7 @@ func toAuthMethodProto(in auth.AuthMethod, opt ...handlers.Option) (*pb.AuthMeth
|
||||
SigningAlgorithms: i.GetSigningAlgs(),
|
||||
AllowedAudiences: i.GetAudClaims(),
|
||||
ClaimsScopes: i.GetClaimsScopes(),
|
||||
AccountClaimMaps: i.GetAccountClaimMaps(),
|
||||
}
|
||||
if i.DisableDiscoveredConfigValidation {
|
||||
attrs.DisableDiscoveredConfigValidation = true
|
||||
@@ -814,6 +815,19 @@ func validateCreateRequest(req *pbs.CreateAuthMethodRequest) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(attrs.GetAccountClaimMaps()) > 0 {
|
||||
acm, err := oidc.ParseAccountClaimMaps(attrs.GetAccountClaimMaps()...)
|
||||
if err != nil {
|
||||
badFields[accountClaimMapsField] = fmt.Sprintf("Contains invalid map %q", err.Error())
|
||||
}
|
||||
foundTo := make(map[string]bool, len(attrs.GetAccountClaimMaps()))
|
||||
for from, rawTo := range acm {
|
||||
if foundTo[rawTo] {
|
||||
badFields[accountClaimMapsField] = fmt.Sprintf("%s=%s contains invalid map - multiple maps to the same Boundary to-claim %s", from, rawTo, rawTo)
|
||||
}
|
||||
foundTo[rawTo] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
badFields[typeField] = fmt.Sprintf("This is a required field and must be %q.", auth.PasswordSubtype.String())
|
||||
@@ -922,6 +936,30 @@ func validateUpdateRequest(req *pbs.UpdateAuthMethodRequest) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(attrs.GetAccountClaimMaps()) > 0 {
|
||||
acm, err := oidc.ParseAccountClaimMaps(attrs.GetAccountClaimMaps()...)
|
||||
if err != nil {
|
||||
badFields[accountClaimMapsField] = fmt.Sprintf("Contains invalid map %q", err.Error())
|
||||
} else {
|
||||
foundTo := make(map[string]bool, len(attrs.GetAccountClaimMaps()))
|
||||
for from, rawTo := range acm {
|
||||
if foundTo[rawTo] {
|
||||
badFields[accountClaimMapsField] = fmt.Sprintf("%s=%s contains invalid map - multiple maps to the same Boundary to-claim %s", from, rawTo, rawTo)
|
||||
}
|
||||
foundTo[rawTo] = true
|
||||
|
||||
to, err := oidc.ConvertToAccountToClaim(rawTo)
|
||||
if err != nil {
|
||||
badFields[accountClaimMapsField] = fmt.Sprintf("%s=%s contains invalid map %q", from, rawTo, err.Error())
|
||||
break
|
||||
}
|
||||
if to == oidc.ToSubClaim {
|
||||
badFields[accountClaimMapsField] = fmt.Sprintf("%s=%s contains invalid map: not allowed to update sub claim maps", from, rawTo)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
badFields["id"] = "Incorrectly formatted identifier."
|
||||
}
|
||||
|
||||
@@ -572,6 +572,10 @@ func TestCreate(t *testing.T) {
|
||||
lv, _ := structpb.NewList([]interface{}{"email", "profile"})
|
||||
return structpb.NewListValue(lv)
|
||||
}(),
|
||||
"account_claim_maps": func() *structpb.Value {
|
||||
lv, _ := structpb.NewList([]interface{}{"display_name=name", "oid=sub"})
|
||||
return structpb.NewListValue(lv)
|
||||
}(),
|
||||
}},
|
||||
}},
|
||||
idPrefix: oidc.AuthMethodPrefix + "_",
|
||||
@@ -600,6 +604,10 @@ func TestCreate(t *testing.T) {
|
||||
lv, _ := structpb.NewList([]interface{}{"email", "profile"})
|
||||
return structpb.NewListValue(lv)
|
||||
}(),
|
||||
"account_claim_maps": func() *structpb.Value {
|
||||
lv, _ := structpb.NewList([]interface{}{"display_name=name", "oid=sub"})
|
||||
return structpb.NewListValue(lv)
|
||||
}(),
|
||||
}},
|
||||
AuthorizedActions: oidcAuthorizedActions,
|
||||
AuthorizedCollectionActions: authorizedCollectionActions,
|
||||
@@ -989,7 +997,7 @@ func TestCreate(t *testing.T) {
|
||||
delete(tc.res.Item.Attributes.Fields, "callback_url")
|
||||
}
|
||||
}
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "CreateAuthMethod(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "CreateAuthMethod(%q) got response %q, wanted %q", tc.req, got, tc.res)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ const (
|
||||
roundtripPayloadAttributesField = "attributes.roundtrip_payload"
|
||||
codeField = "attributes.code"
|
||||
claimsScopesField = "attributes.claims_scopes"
|
||||
accountClaimMapsField = "attributes.account_claim_maps"
|
||||
)
|
||||
|
||||
var oidcMaskManager handlers.MaskManager
|
||||
@@ -472,6 +473,28 @@ func toStorageOidcAuthMethod(scopeId string, in *pb.AuthMethod) (out *oidc.AuthM
|
||||
opts = append(opts, oidc.WithClaimsScopes(attrs.GetClaimsScopes()...))
|
||||
}
|
||||
|
||||
if len(attrs.GetAccountClaimMaps()) > 0 {
|
||||
claimsMap := make(map[string]oidc.AccountToClaim, len(attrs.GetAccountClaimMaps()))
|
||||
for _, v := range attrs.GetAccountClaimMaps() {
|
||||
acm, err := oidc.ParseAccountClaimMaps(v)
|
||||
if err != nil {
|
||||
return nil, false, false, errors.Wrap(err, op)
|
||||
}
|
||||
if len(acm) > 1 {
|
||||
return nil, false, false, errors.New(errors.InvalidParameter, op, fmt.Sprintf("unable to parse account claim map %s", v))
|
||||
}
|
||||
var from, rawTo string
|
||||
for from, rawTo = range acm {
|
||||
}
|
||||
to, err := oidc.ConvertToAccountToClaim(rawTo)
|
||||
if err != nil {
|
||||
return nil, false, false, errors.Wrap(err, op)
|
||||
}
|
||||
claimsMap[from] = to
|
||||
}
|
||||
opts = append(opts, oidc.WithAccountClaimMap(claimsMap))
|
||||
}
|
||||
|
||||
u, err := oidc.NewAuthMethod(scopeId, clientId, clientSecret, opts...)
|
||||
if err != nil {
|
||||
return nil, false, false, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build auth method: %v.", err)
|
||||
|
||||
@@ -905,6 +905,64 @@ func TestUpdate_OIDC(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Change Account Claim Maps",
|
||||
req: &pbs.UpdateAuthMethodRequest{
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"attributes.account_claim_maps"},
|
||||
},
|
||||
Item: &pb.AuthMethod{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"account_claim_maps": func() *structpb.Value {
|
||||
lv, _ := structpb.NewList([]interface{}{"display_name=name"})
|
||||
return structpb.NewListValue(lv)
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
res: &pbs.UpdateAuthMethodResponse{
|
||||
Item: &pb.AuthMethod{
|
||||
ScopeId: o.GetPublicId(),
|
||||
Name: &wrapperspb.StringValue{Value: "default"},
|
||||
Description: &wrapperspb.StringValue{Value: "default"},
|
||||
Type: auth.OidcSubtype.String(),
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: func() map[string]*structpb.Value {
|
||||
f := defaultReadAttributeFields()
|
||||
f["account_claim_maps"] = func() *structpb.Value {
|
||||
lv, _ := structpb.NewList([]interface{}{"display_name=name"})
|
||||
return structpb.NewListValue(lv)
|
||||
}()
|
||||
return f
|
||||
}(),
|
||||
},
|
||||
Scope: defaultScopeInfo,
|
||||
AuthorizedActions: oidcAuthorizedActions,
|
||||
AuthorizedCollectionActions: authorizedCollectionActions,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Attempt to Change Account Claim Maps for sub",
|
||||
req: &pbs.UpdateAuthMethodRequest{
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"attributes.account_claim_maps"},
|
||||
},
|
||||
Item: &pb.AuthMethod{
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"account_claim_maps": func() *structpb.Value {
|
||||
lv, _ := structpb.NewList([]interface{}{"oid=sub"})
|
||||
return structpb.NewListValue(lv)
|
||||
}(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Unset Issuer Is Incomplete",
|
||||
req: &pbs.UpdateAuthMethodRequest{
|
||||
@@ -996,7 +1054,6 @@ func TestUpdate_OIDC(t *testing.T) {
|
||||
tc.res.Item.Id = am.GetId()
|
||||
tc.res.Item.CreatedTime = am.GetCreatedTime()
|
||||
}
|
||||
|
||||
got, gErr := tested.UpdateAuthMethod(auth.DisabledAuthTestContext(iamRepoFn, o.GetPublicId()), tc.req)
|
||||
// TODO: When handlers move to domain errors remove wantErr and rely errors.Match here.
|
||||
if tc.err != nil || tc.wantErr {
|
||||
|
||||
Reference in new issue
Block a user