diff --git a/sdk/framework/path.go b/sdk/framework/path.go index 067b005e0f..aa8ed99965 100644 --- a/sdk/framework/path.go +++ b/sdk/framework/path.go @@ -268,6 +268,9 @@ type DisplayAttributes struct { // EditType is the optional type of form field needed for a property // This is only necessary for a "textarea" or "file" EditType string `json:"editType,omitempty"` + + // Identifier is the primary field rendered in the UI (e.g. name, path, url, etc.) + Identifier bool `json:"identifier,omitempty"` } // RequestExample is example of request data. diff --git a/sdk/helper/automatedrotationutil/fields.go b/sdk/helper/automatedrotationutil/fields.go index 433327b596..a75169c47d 100644 --- a/sdk/helper/automatedrotationutil/fields.go +++ b/sdk/helper/automatedrotationutil/fields.go @@ -100,25 +100,37 @@ func (p *AutomatedRotationParams) HasNonzeroRotationValues() bool { return p.RotationSchedule != "" || p.RotationPeriod != 0 } -// AddAutomatedRotationFields adds plugin identity token fields to the given -// field schema map. -func AddAutomatedRotationFields(m map[string]*framework.FieldSchema) { +// AddAutomatedRotationFieldsWithGroup adds rotation fields to the given field schema map +// the fields are associated to the provided display attribute group +func AddAutomatedRotationFieldsWithGroup(m map[string]*framework.FieldSchema, group string) { fields := map[string]*framework.FieldSchema{ "rotation_schedule": { Type: framework.TypeString, Description: "CRON-style string that will define the schedule on which rotations should occur. Mutually exclusive with rotation_period", + DisplayAttrs: &framework.DisplayAttributes{ + Group: group, + }, }, "rotation_window": { Type: framework.TypeDurationSecond, Description: "Specifies the amount of time in which the rotation is allowed to occur starting from a given rotation_schedule", + DisplayAttrs: &framework.DisplayAttributes{ + Group: group, + }, }, "rotation_period": { Type: framework.TypeDurationSecond, Description: "TTL for automatic credential rotation of the given username. Mutually exclusive with rotation_schedule", + DisplayAttrs: &framework.DisplayAttributes{ + Group: group, + }, }, "disable_automated_rotation": { Type: framework.TypeBool, Description: "If set to true, will deregister all registered rotation jobs from the RotationManager for the plugin.", + DisplayAttrs: &framework.DisplayAttributes{ + Group: group, + }, }, } @@ -129,3 +141,10 @@ func AddAutomatedRotationFields(m map[string]*framework.FieldSchema) { m[name] = schema } } + +// stubbing original function for compatibility +// AddAutomatedRotationFieldsWithGroup should be used directly +// future utils that define fields should include a group parameter +func AddAutomatedRotationFields(m map[string]*framework.FieldSchema) { + AddAutomatedRotationFieldsWithGroup(m, "default") +} diff --git a/sdk/helper/automatedrotationutil/fields_test.go b/sdk/helper/automatedrotationutil/fields_test.go index 58ced57e27..10b1db67ed 100644 --- a/sdk/helper/automatedrotationutil/fields_test.go +++ b/sdk/helper/automatedrotationutil/fields_test.go @@ -10,24 +10,37 @@ import ( "time" "github.com/hashicorp/vault/sdk/framework" + "github.com/stretchr/testify/assert" ) var schemaMap = map[string]*framework.FieldSchema{ "rotation_schedule": { Type: framework.TypeString, Description: "CRON-style string that will define the schedule on which rotations should occur. Mutually exclusive with rotation_period", + DisplayAttrs: &framework.DisplayAttributes{ + Group: "default", + }, }, "rotation_window": { Type: framework.TypeDurationSecond, Description: "Specifies the amount of time in which the rotation is allowed to occur starting from a given rotation_schedule", + DisplayAttrs: &framework.DisplayAttributes{ + Group: "default", + }, }, "rotation_period": { Type: framework.TypeDurationSecond, Description: "TTL for automatic credential rotation of the given username. Mutually exclusive with rotation_schedule", + DisplayAttrs: &framework.DisplayAttributes{ + Group: "default", + }, }, "disable_automated_rotation": { Type: framework.TypeBool, Description: "If set to true, will deregister all registered rotation jobs from the RotationManager for the plugin.", + DisplayAttrs: &framework.DisplayAttributes{ + Group: "default", + }, }, } @@ -368,3 +381,58 @@ func TestShouldDeregisterRotationJob(t *testing.T) { }) } } + +func TestAddAutomatedRotationFieldsWithGroup(t *testing.T) { + expectedSchemaMap := make(map[string]*framework.FieldSchema) + for k, v := range schemaMap { + newField := *v + newDisplayAttrs := *v.DisplayAttrs + newDisplayAttrs.Group = "Automated Rotation" + newField.DisplayAttrs = &newDisplayAttrs + expectedSchemaMap[k] = &newField + } + testcases := []struct { + name string + group string + input map[string]*framework.FieldSchema + want map[string]*framework.FieldSchema + }{ + { + name: "basic", + input: map[string]*framework.FieldSchema{}, + group: "Automated Rotation", + want: expectedSchemaMap, + }, + } + + for _, tt := range testcases { + t.Run(tt.name, func(t *testing.T) { + got := tt.input + AddAutomatedRotationFieldsWithGroup(got, tt.group) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestAddAutomatedRotationFields(t *testing.T) { + testcases := []struct { + name string + group string + input map[string]*framework.FieldSchema + want map[string]*framework.FieldSchema + }{ + { + name: "basic", + input: map[string]*framework.FieldSchema{}, + want: schemaMap, + }, + } + + for _, tt := range testcases { + t.Run(tt.name, func(t *testing.T) { + got := tt.input + AddAutomatedRotationFields(got) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/sdk/helper/ldaputil/config.go b/sdk/helper/ldaputil/config.go index c84ab42a6c..5b59ce60e2 100644 --- a/sdk/helper/ldaputil/config.go +++ b/sdk/helper/ldaputil/config.go @@ -35,7 +35,8 @@ func ConfigFields() map[string]*framework.FieldSchema { Default: false, Description: "Use anonymous binds when performing LDAP group searches (if true the initial credentials will still be used for the initial connection test).", DisplayAttrs: &framework.DisplayAttributes{ - Name: "Anonymous group search", + Name: "Anonymous group search", + Group: "LDAP Options", }, }, "url": { @@ -43,7 +44,8 @@ func ConfigFields() map[string]*framework.FieldSchema { Default: "ldap://127.0.0.1", Description: "LDAP URL to connect to (default: ldap://127.0.0.1). Multiple URLs can be specified by concatenating them with commas; they will be tried in-order.", DisplayAttrs: &framework.DisplayAttributes{ - Name: "URL", + Name: "URL", + Identifier: true, }, }, @@ -51,7 +53,8 @@ func ConfigFields() map[string]*framework.FieldSchema { Type: framework.TypeString, Description: "LDAP domain to use for users (eg: ou=People,dc=example,dc=org)", DisplayAttrs: &framework.DisplayAttributes{ - Name: "User DN", + Name: "User DN", + Group: "Customize User Search", }, }, @@ -59,7 +62,8 @@ func ConfigFields() map[string]*framework.FieldSchema { Type: framework.TypeString, Description: "LDAP DN for searching for the user DN (optional)", DisplayAttrs: &framework.DisplayAttributes{ - Name: "Name of Object to bind (binddn)", + Name: "Name of Object to bind (binddn)", + Group: "Customize User Search", }, }, @@ -68,6 +72,7 @@ func ConfigFields() map[string]*framework.FieldSchema { Description: "LDAP password for searching for the user DN (optional)", DisplayAttrs: &framework.DisplayAttributes{ Sensitive: true, + Group: "Customize User Search", }, }, @@ -75,7 +80,8 @@ func ConfigFields() map[string]*framework.FieldSchema { Type: framework.TypeString, Description: "LDAP search base to use for group membership search (eg: ou=Groups,dc=example,dc=org)", DisplayAttrs: &framework.DisplayAttributes{ - Name: "Group DN", + Name: "Group DN", + Group: "Customize Group Membership Search", }, }, @@ -87,7 +93,8 @@ The template can access the following context variables: UserDN, Username Example: (&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}})) Default: (|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))`, DisplayAttrs: &framework.DisplayAttributes{ - Name: "Group Filter", + Name: "Group Filter", + Group: "Customize Group Membership Search", }, }, @@ -101,6 +108,7 @@ Default: cn`, DisplayAttrs: &framework.DisplayAttributes{ Name: "Group Attribute", Value: "cn", + Group: "Customize Group Membership Search", }, }, @@ -111,7 +119,8 @@ Default: cn`, The template can access the following context variables: UserAttr, Username Default: ({{.UserAttr}}={{.Username}})`, DisplayAttrs: &framework.DisplayAttributes{ - Name: "User Search Filter", + Name: "User Search Filter", + Group: "Customize User Search", }, }, @@ -119,7 +128,8 @@ Default: ({{.UserAttr}}={{.Username}})`, Type: framework.TypeString, Description: "Enables userPrincipalDomain login with [username]@UPNDomain (optional)", DisplayAttrs: &framework.DisplayAttributes{ - Name: "User Principal (UPN) Domain", + Name: "User Principal (UPN) Domain", + Group: "LDAP Options", }, }, @@ -136,6 +146,7 @@ Default: ({{.UserAttr}}={{.Username}})`, DisplayAttrs: &framework.DisplayAttributes{ Name: "User Attribute", Value: "cn", + Group: "LDAP Options", }, }, @@ -145,6 +156,7 @@ Default: ({{.UserAttr}}={{.Username}})`, DisplayAttrs: &framework.DisplayAttributes{ Name: "CA certificate", EditType: "file", + Group: "LDAP Options", }, }, @@ -154,6 +166,7 @@ Default: ({{.UserAttr}}={{.Username}})`, DisplayAttrs: &framework.DisplayAttributes{ Name: "Client certificate", EditType: "file", + Group: "LDAP Options", }, }, @@ -163,6 +176,7 @@ Default: ({{.UserAttr}}={{.Username}})`, DisplayAttrs: &framework.DisplayAttributes{ Name: "Client key", EditType: "file", + Group: "LDAP Options", }, }, @@ -170,7 +184,8 @@ Default: ({{.UserAttr}}={{.Username}})`, Type: framework.TypeBool, Description: "Use anonymous bind to discover the bind DN of a user (optional)", DisplayAttrs: &framework.DisplayAttributes{ - Name: "Discover DN", + Name: "Discover DN", + Group: "LDAP Options", }, }, @@ -178,7 +193,8 @@ Default: ({{.UserAttr}}={{.Username}})`, Type: framework.TypeBool, Description: "Skip LDAP server SSL Certificate verification - VERY insecure (optional)", DisplayAttrs: &framework.DisplayAttributes{ - Name: "Insecure TLS", + Name: "Insecure TLS", + Group: "LDAP Options", }, }, @@ -186,7 +202,8 @@ Default: ({{.UserAttr}}={{.Username}})`, Type: framework.TypeBool, Description: "Issue a StartTLS command after establishing unencrypted connection (optional)", DisplayAttrs: &framework.DisplayAttributes{ - Name: "Issue StartTLS", + Name: "Issue StartTLS", + Group: "LDAP Options", }, }, @@ -195,7 +212,8 @@ Default: ({{.UserAttr}}={{.Username}})`, Default: "tls12", Description: "Minimum TLS version to use. Accepted values are 'tls10', 'tls11', 'tls12' or 'tls13'. Defaults to 'tls12'", DisplayAttrs: &framework.DisplayAttributes{ - Name: "Minimum TLS Version", + Name: "Minimum TLS Version", + Group: "LDAP Options", }, AllowedValues: []interface{}{"tls10", "tls11", "tls12", "tls13"}, }, @@ -205,7 +223,8 @@ Default: ({{.UserAttr}}={{.Username}})`, Default: "tls12", Description: "Maximum TLS version to use. Accepted values are 'tls10', 'tls11', 'tls12' or 'tls13'. Defaults to 'tls12'", DisplayAttrs: &framework.DisplayAttributes{ - Name: "Maximum TLS Version", + Name: "Maximum TLS Version", + Group: "LDAP Options", }, AllowedValues: []interface{}{"tls10", "tls11", "tls12", "tls13"}, }, @@ -214,6 +233,9 @@ Default: ({{.UserAttr}}={{.Username}})`, Type: framework.TypeBool, Default: true, Description: "Denies an unauthenticated LDAP bind request if the user's password is empty; defaults to true", + DisplayAttrs: &framework.DisplayAttributes{ + Group: "LDAP Options", + }, }, "case_sensitive_names": { @@ -225,6 +247,9 @@ Default: ({{.UserAttr}}={{.Username}})`, Type: framework.TypeBool, Default: false, Description: "If true, use the Active Directory tokenGroups constructed attribute of the user to find the group memberships. This will find all security groups including nested ones.", + DisplayAttrs: &framework.DisplayAttributes{ + Group: "Customize Group Membership Search", + }, }, "use_pre111_group_cn_behavior": { diff --git a/sdk/helper/pluginidentityutil/fields.go b/sdk/helper/pluginidentityutil/fields.go index 3d97537ecc..a9e613e40f 100644 --- a/sdk/helper/pluginidentityutil/fields.go +++ b/sdk/helper/pluginidentityutil/fields.go @@ -38,19 +38,26 @@ func (p *PluginIdentityTokenParams) PopulatePluginIdentityTokenData(m map[string m["identity_token_audience"] = p.IdentityTokenAudience } -// AddPluginIdentityTokenFields adds plugin identity token fields to the given -// field schema map. -func AddPluginIdentityTokenFields(m map[string]*framework.FieldSchema) { +// AddPluginIdentityTokenFields adds plugin identity token fields to the given field schema map +// the fields are associated to the provided display attribute group +func AddPluginIdentityTokenFieldsWithGroup(m map[string]*framework.FieldSchema, group string) { fields := map[string]*framework.FieldSchema{ "identity_token_audience": { Type: framework.TypeString, Description: "Audience of plugin identity tokens", Default: "", + DisplayAttrs: &framework.DisplayAttributes{ + Group: group, + }, }, "identity_token_ttl": { Type: framework.TypeDurationSecond, Description: "Time-to-live of plugin identity tokens", Default: 3600, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Identity token TTL", + Group: group, + }, }, } @@ -61,3 +68,10 @@ func AddPluginIdentityTokenFields(m map[string]*framework.FieldSchema) { m[name] = schema } } + +// stubbing original function for compatibility +// AddPluginIdentityTokenFieldsWithGroup should be used directly +// future utils that define fields should include a group parameter +func AddPluginIdentityTokenFields(m map[string]*framework.FieldSchema) { + AddPluginIdentityTokenFieldsWithGroup(m, "default") +} diff --git a/sdk/helper/pluginidentityutil/fields_test.go b/sdk/helper/pluginidentityutil/fields_test.go index a64f1aca43..44e3dc2f8d 100644 --- a/sdk/helper/pluginidentityutil/fields_test.go +++ b/sdk/helper/pluginidentityutil/fields_test.go @@ -115,25 +115,34 @@ func TestPopulatePluginIdentityTokenData(t *testing.T) { } } -func TestAddPluginIdentityTokenFields(t *testing.T) { +func TestAddPluginIdentityTokenFieldsWithGroup(t *testing.T) { testcases := []struct { name string + group string input map[string]*framework.FieldSchema want map[string]*framework.FieldSchema }{ { name: "basic", input: map[string]*framework.FieldSchema{}, + group: "Tokens", want: map[string]*framework.FieldSchema{ fieldIDTokenAudience: { Type: framework.TypeString, Description: "Audience of plugin identity tokens", Default: "", + DisplayAttrs: &framework.DisplayAttributes{ + Group: "Tokens", + }, }, fieldIDTokenTTL: { Type: framework.TypeDurationSecond, Description: "Time-to-live of plugin identity tokens", Default: 3600, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Identity token TTL", + Group: "Tokens", + }, }, }, }, @@ -146,16 +155,24 @@ func TestAddPluginIdentityTokenFields(t *testing.T) { Default: "default", }, }, + group: "Token Options", want: map[string]*framework.FieldSchema{ fieldIDTokenAudience: { Type: framework.TypeString, Description: "Audience of plugin identity tokens", Default: "", + DisplayAttrs: &framework.DisplayAttributes{ + Group: "Token Options", + }, }, fieldIDTokenTTL: { Type: framework.TypeDurationSecond, Description: "Time-to-live of plugin identity tokens", Default: 3600, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Identity token TTL", + Group: "Token Options", + }, }, "test": { Type: framework.TypeString, @@ -166,6 +183,47 @@ func TestAddPluginIdentityTokenFields(t *testing.T) { }, } + for _, tt := range testcases { + t.Run(tt.name, func(t *testing.T) { + got := tt.input + AddPluginIdentityTokenFieldsWithGroup(got, tt.group) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestAddPluginIdentityTokenFields(t *testing.T) { + testcases := []struct { + name string + group string + input map[string]*framework.FieldSchema + want map[string]*framework.FieldSchema + }{ + { + name: "basic", + input: map[string]*framework.FieldSchema{}, + want: map[string]*framework.FieldSchema{ + fieldIDTokenAudience: { + Type: framework.TypeString, + Description: "Audience of plugin identity tokens", + Default: "", + DisplayAttrs: &framework.DisplayAttributes{ + Group: "default", + }, + }, + fieldIDTokenTTL: { + Type: framework.TypeDurationSecond, + Description: "Time-to-live of plugin identity tokens", + Default: 3600, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Identity token TTL", + Group: "default", + }, + }, + }, + }, + } + for _, tt := range testcases { t.Run(tt.name, func(t *testing.T) { got := tt.input