Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

openapi: Add display attributes for Okta auth #19391

Merged
merged 43 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
857ec3f
add prefix & suffix display attributes
averche Feb 21, 2023
beed664
add DisplayAttrs to PathParameters
averche Feb 22, 2023
df763a5
add constructOperationID func
averche Feb 22, 2023
72c5c47
Fixes & comments
averche Feb 22, 2023
ce4ca4a
Add test and fix logic
averche Feb 23, 2023
fcdd2d3
fix existing test data
averche Feb 23, 2023
f3a4dbe
ommitempty
averche Feb 23, 2023
5c72769
changelog
averche Feb 23, 2023
d3e16aa
better suffix disambiguation
averche Feb 26, 2023
22a1e74
Update comment
averche Feb 26, 2023
7003717
hyphenate instead of TitleCase
averche Feb 27, 2023
4713d81
fmt
averche Feb 27, 2023
1b5afe3
User OperationVerb since Action conflicts
averche Feb 27, 2023
54ad97a
reorder vars
averche Feb 27, 2023
5ac38b3
openapi: Add display attributes for Okta auth
averche Feb 28, 2023
caf8352
add verify display attrs
averche Feb 28, 2023
3200825
changelog
averche Feb 28, 2023
1644e43
Merge branch 'main' into ui/openapi-naming-strategy
averche Feb 28, 2023
efce4eb
Merge branch 'ui/openapi-naming-strategy' into display-attributes-okta
averche Mar 1, 2023
3483f88
rm changelog
averche Mar 1, 2023
26b144e
allow verb-only
averche Mar 1, 2023
49eda18
better comments
averche Mar 1, 2023
72c4acd
more comments, better example
averche Mar 3, 2023
d6c2a45
better name for helper
averche Mar 12, 2023
99c30f4
Merge branch 'main' into ui/openapi-naming-strategy
averche Mar 13, 2023
f687714
config -> configure
averche Mar 13, 2023
22fa3e9
config -> configure
averche Mar 13, 2023
7c6e87a
Merge branch 'ui/openapi-naming-strategy' into display-attributes-okta
averche Mar 13, 2023
156d623
configuration
averche Mar 21, 2023
2c504ae
log-in
averche Mar 21, 2023
af41a73
allow empty multi-field suffixes
averche Mar 22, 2023
23f731d
Merge branch 'main' into ui/openapi-naming-strategy
averche Mar 23, 2023
243c898
add withoutOperationHints
averche Mar 23, 2023
d37caaa
nil check
averche Mar 23, 2023
cb5aa04
empty obj check
averche Mar 23, 2023
7aacc2c
write -> create-or-update
averche Mar 30, 2023
f05e5e1
Merge branch 'main' into ui/openapi-naming-strategy
averche Mar 30, 2023
ed3a56d
Revert "write -> create-or-update"
averche Mar 31, 2023
0748a1a
title case response/request names
averche Apr 4, 2023
91b5fb7
Merge branch 'ui/openapi-naming-strategy' into display-attributes-okta
averche Apr 4, 2023
b2cdb43
Merge branch 'main' into display-attributes-okta
averche Apr 4, 2023
00d59f3
Merge branch 'main' into display-attributes-okta
averche Apr 5, 2023
8c9fd23
Merge branch 'main' into display-attributes-okta
averche Apr 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions builtin/credential/okta/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import (
)

const (
mfaPushMethod = "push"
mfaTOTPMethod = "token:software:totp"
operationPrefixOkta = "okta"
mfaPushMethod = "push"
mfaTOTPMethod = "token:software:totp"
)

func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
Expand Down
10 changes: 7 additions & 3 deletions builtin/credential/okta/path_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ const (
func pathConfig(b *backend) *framework.Path {
p := &framework.Path{
Pattern: `config`,

DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixOkta,
OperationSuffix: "config",
Action: "Configure",
},

Fields: map[string]*framework.FieldSchema{
"organization": {
Type: framework.TypeString,
Expand Down Expand Up @@ -89,9 +96,6 @@ func pathConfig(b *backend) *framework.Path {
ExistenceCheck: b.pathConfigExistenceCheck,

HelpSynopsis: pathConfigHelp,
DisplayAttrs: &framework.DisplayAttributes{
Action: "Configure",
},
}

tokenutil.AddTokenFields(p.Fields)
Expand Down
23 changes: 15 additions & 8 deletions builtin/credential/okta/path_groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,33 @@ func pathGroupsList(b *backend) *framework.Path {
return &framework.Path{
Pattern: "groups/?$",

DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixOkta,
OperationSuffix: "groups",
Navigation: true,
ItemType: "Group",
},

Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ListOperation: b.pathGroupList,
},

HelpSynopsis: pathGroupHelpSyn,
HelpDescription: pathGroupHelpDesc,
DisplayAttrs: &framework.DisplayAttributes{
Navigation: true,
ItemType: "Group",
},
}
}

func pathGroups(b *backend) *framework.Path {
return &framework.Path{
Pattern: `groups/(?P<name>.+)`,

DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixOkta,
OperationSuffix: "group",
Action: "Create",
ItemType: "Group",
},

Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Expand All @@ -49,10 +60,6 @@ func pathGroups(b *backend) *framework.Path {

HelpSynopsis: pathGroupHelpSyn,
HelpDescription: pathGroupHelpDesc,
DisplayAttrs: &framework.DisplayAttributes{
Action: "Create",
ItemType: "Group",
},
}
}

Expand Down
10 changes: 10 additions & 0 deletions builtin/credential/okta/path_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ const (
func pathLogin(b *backend) *framework.Path {
return &framework.Path{
Pattern: `login/(?P<username>.+)`,

DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixOkta,
OperationVerb: "login",
},

Fields: map[string]*framework.FieldSchema{
"username": {
Type: framework.TypeString,
Expand Down Expand Up @@ -189,6 +195,10 @@ func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *f
func pathVerify(b *backend) *framework.Path {
return &framework.Path{
Pattern: `verify/(?P<nonce>.+)`,
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixOkta,
OperationVerb: "verify",
},
Fields: map[string]*framework.FieldSchema{
"nonce": {
Type: framework.TypeString,
Expand Down
23 changes: 15 additions & 8 deletions builtin/credential/okta/path_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,33 @@ func pathUsersList(b *backend) *framework.Path {
return &framework.Path{
Pattern: "users/?$",

DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixOkta,
OperationSuffix: "users",
Navigation: true,
ItemType: "User",
},

Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ListOperation: b.pathUserList,
},

HelpSynopsis: pathUserHelpSyn,
HelpDescription: pathUserHelpDesc,
DisplayAttrs: &framework.DisplayAttributes{
Navigation: true,
ItemType: "User",
},
}
}

func pathUsers(b *backend) *framework.Path {
return &framework.Path{
Pattern: `users/(?P<name>.+)`,

DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixOkta,
OperationSuffix: "user",
Action: "Create",
ItemType: "User",
},

Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Expand All @@ -52,10 +63,6 @@ func pathUsers(b *backend) *framework.Path {

HelpSynopsis: pathUserHelpSyn,
HelpDescription: pathUserHelpDesc,
DisplayAttrs: &framework.DisplayAttributes{
Action: "Create",
ItemType: "User",
},
}
}

Expand Down
3 changes: 3 additions & 0 deletions changelog/19319.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
openapi: Improve operationId/request/response naming strategy
```
156 changes: 124 additions & 32 deletions sdk/framework/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import (
"github.com/hashicorp/vault/sdk/helper/wrapping"
"github.com/hashicorp/vault/sdk/logical"
"github.com/mitchellh/mapstructure"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)

// OpenAPI specification (OAS): https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md
Expand Down Expand Up @@ -244,7 +242,7 @@ func documentPath(p *Path, specialPaths *logical.Paths, requestResponsePrefix st
}
}

for _, path := range paths {
for pathIndex, path := range paths {
// Construct a top level PathItem which will be populated as the path is processed.
pi := OASPathItem{
Description: cleanString(p.HelpSynopsis),
Expand Down Expand Up @@ -331,9 +329,19 @@ func documentPath(p *Path, specialPaths *logical.Paths, requestResponsePrefix st

op := NewOASOperation()

operationID := constructOperationID(
path,
pathIndex,
p.DisplayAttrs,
opType,
props.DisplayAttrs,
requestResponsePrefix,
)

op.Summary = props.Summary
op.Description = props.Description
op.Deprecated = props.Deprecated
op.OperationID = operationID

// Add any fields not present in the path as body parameters for POST.
if opType == logical.CreateOperation || opType == logical.UpdateOperation {
Expand Down Expand Up @@ -381,7 +389,7 @@ func documentPath(p *Path, specialPaths *logical.Paths, requestResponsePrefix st

// Set the final request body. Only JSON request data is supported.
if len(s.Properties) > 0 || s.Example != nil {
requestName := constructRequestResponseName(path, requestResponsePrefix, "Request")
requestName := operationID + "-request"
doc.Components.Schemas[requestName] = s
op.RequestBody = &OASRequestBody{
Required: true,
Expand Down Expand Up @@ -488,7 +496,7 @@ func documentPath(p *Path, specialPaths *logical.Paths, requestResponsePrefix st
}

if len(resp.Fields) != 0 {
responseName := constructRequestResponseName(path, requestResponsePrefix, "Response")
responseName := operationID + "-response"
doc.Components.Schemas[responseName] = responseSchema
content = OASContent{
"application/json": &OASMediaTypeObject{
Expand Down Expand Up @@ -520,33 +528,6 @@ func documentPath(p *Path, specialPaths *logical.Paths, requestResponsePrefix st
return nil
}

// constructRequestResponseName joins the given path with prefix & suffix into
// a CamelCase request or response name.
//
// For example, path=/config/lease/{name}, prefix="secret", suffix="request"
// will result in "SecretConfigLeaseRequest"
func constructRequestResponseName(path, prefix, suffix string) string {
var b strings.Builder

title := cases.Title(language.English)

b.WriteString(title.String(prefix))

// split the path by / _ - separators
for _, token := range strings.FieldsFunc(path, func(r rune) bool {
return r == '/' || r == '_' || r == '-'
}) {
// exclude request fields
if !strings.ContainsAny(token, "{}") {
b.WriteString(title.String(token))
}
}

b.WriteString(suffix)

return b.String()
}

func specialPathMatch(path string, specialPaths []string) bool {
// Test for exact or prefix match of special paths.
for _, sp := range specialPaths {
Expand All @@ -558,6 +539,110 @@ func specialPathMatch(path string, specialPaths []string) bool {
return false
}

// constructOperationID joins the given inputs into a hyphen-separated
// lower-case operation id, which is also used as a prefix for request and
// response names.
//
// The OperationPrefix / -Verb / -Suffix found in display attributes will be
// used, if provided. Otherwise, the function falls back to using the path and
// the operation.
//
// Examples of generated operation identifiers:
// - kvv2-write
// - kvv2-read
// - google-cloud-login
// - google-cloud-write-role
func constructOperationID(
path string,
pathIndex int,
pathAttributes *DisplayAttributes,
operation logical.Operation,
operationAttributes *DisplayAttributes,
defaultPrefix string,
) string {
var (
prefix string
verb string
suffix string
)

if operationAttributes != nil {
prefix = operationAttributes.OperationPrefix
verb = operationAttributes.OperationVerb
suffix = operationAttributes.OperationSuffix
}

if pathAttributes != nil {
if prefix == "" {
prefix = pathAttributes.OperationPrefix
}
if verb == "" {
verb = pathAttributes.OperationVerb
}
if suffix == "" {
suffix = pathAttributes.OperationSuffix
}
}

// A single suffix string can contain multiple pipe-delimited strings. To
// determine the actual suffix, we attempt to match it by the index of the
// paths returned from `expandPattern(...)`. For example:
//
// aws/
// Pattern: `^(creds|sts)/(?P<name>\w(([\w-.@]+)?\w)?)$`
// DisplayAttrs: {
// OperationSuffix: "credentials|sts-credentials"
// }
//
// Will expand into two paths and corresponding suffixes:
//
// path 0: "creds/{name}" suffix: credentials
// path 1: "sts/{name}" suffix: sts-credentials
//
if suffixes := strings.Split(suffix, "|"); len(suffixes) > 1 || pathIndex > 0 {
// if the index is out of bounds, fall back to the old logic
if pathIndex >= len(suffixes) {
suffix = ""
} else {
suffix = suffixes[pathIndex]
}
}

// hyphenate is a helper that hyphenates the given slice except the empty elements
hyphenate := func(parts []string) string {
filtered := make([]string, 0, len(parts))
for _, e := range parts {
if e != "" {
filtered = append(filtered, e)
}
}
return strings.ToLower(strings.Join(filtered, "-"))
}

// fall back to using the path + operation to construct the operation id
needPrefix := prefix == "" && (suffix == "" || verb == "")
needVerb := verb == ""
needSuffix := suffix == "" && (prefix == "" || verb == "" || pathIndex > 0)

if needPrefix {
prefix = defaultPrefix
}

if needVerb {
if operation == logical.UpdateOperation {
verb = "write"
} else {
verb = string(operation)
}
}

if needSuffix {
suffix = hyphenate(nonWordRe.Split(strings.ToLower(path), -1))
}

return hyphenate([]string{prefix, verb, suffix})
}

// expandPattern expands a regex pattern by generating permutations of any optional parameters
// and changing named parameters into their {openapi} equivalents.
func expandPattern(pattern string) ([]string, error) {
Expand Down Expand Up @@ -883,6 +968,9 @@ func cleanResponse(resp *logical.Response) *cleanedResponse {
// postSysToolsRandomUrlbytes_2
//
// An optional user-provided suffix ("context") may also be appended.
//
// Deprecated: operationID's are now populated using `constructOperationID`.
// This function is here for backwards compatibility with older plugins.
func (d *OASDocument) CreateOperationIDs(context string) {
opIDCount := make(map[string]int)
var paths []string
Expand Down Expand Up @@ -910,6 +998,10 @@ func (d *OASDocument) CreateOperationIDs(context string) {
continue
}

if oasOperation.OperationID != "" {
continue
}

// Discard "_mount_path" from any {thing_mount_path} parameters
path = strings.Replace(path, "_mount_path", "", 1)

Expand Down
Loading