mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 18:17:55 +00:00
identity/oidc: allow filtering the list providers response by an allowed_client_id (#16181)
* identity/oidc: allow filtering the list providers response by an allowed_client_id * adds changelog * adds api documentation * use identity store view in list provider test
This commit is contained in:
3
changelog/16181.txt
Normal file
3
changelog/16181.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:improvement
|
||||||
|
identity/oidc: allows filtering the list providers response by an allowed_client_id
|
||||||
|
```
|
||||||
@@ -178,6 +178,8 @@ func buildLogicalRequestNoAuth(perfStandby bool, w http.ResponseWriter, r *http.
|
|||||||
path += "/"
|
path += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data = parseQuery(r.URL.Query())
|
||||||
|
|
||||||
case "OPTIONS", "HEAD":
|
case "OPTIONS", "HEAD":
|
||||||
default:
|
default:
|
||||||
return nil, nil, http.StatusMethodNotAllowed, nil
|
return nil, nil, http.StatusMethodNotAllowed, nil
|
||||||
|
|||||||
@@ -135,6 +135,19 @@ type provider struct {
|
|||||||
effectiveIssuer string
|
effectiveIssuer string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allowedClientID returns true if the given client ID is in
|
||||||
|
// the provider's set of allowed client IDs or its allowed client
|
||||||
|
// IDs contains the wildcard "*" char.
|
||||||
|
func (p *provider) allowedClientID(clientID string) bool {
|
||||||
|
for _, allowedID := range p.AllowedClientIDs {
|
||||||
|
switch allowedID {
|
||||||
|
case "*", clientID:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type providerDiscovery struct {
|
type providerDiscovery struct {
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
Keys string `json:"jwks_uri"`
|
Keys string `json:"jwks_uri"`
|
||||||
@@ -356,6 +369,14 @@ func oidcProviderPaths(i *IdentityStore) []*framework.Path {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Pattern: "oidc/provider/?$",
|
Pattern: "oidc/provider/?$",
|
||||||
|
Fields: map[string]*framework.FieldSchema{
|
||||||
|
"allowed_client_id": {
|
||||||
|
Type: framework.TypeString,
|
||||||
|
Description: "Filters the list of OIDC providers to those " +
|
||||||
|
"that allow the given client ID in their set of allowed_client_ids.",
|
||||||
|
Query: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
Operations: map[logical.Operation]framework.OperationHandler{
|
Operations: map[logical.Operation]framework.OperationHandler{
|
||||||
logical.ListOperation: &framework.PathOperation{
|
logical.ListOperation: &framework.PathOperation{
|
||||||
Callback: i.pathOIDCListProvider,
|
Callback: i.pathOIDCListProvider,
|
||||||
@@ -1301,6 +1322,34 @@ func (i *IdentityStore) pathOIDCListProvider(ctx context.Context, req *logical.R
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If allowed_client_id is provided as a query parameter, filter the set of
|
||||||
|
// returned OIDC providers to those that allow the given value in their set
|
||||||
|
// of allowed_client_ids.
|
||||||
|
if clientIDRaw, ok := d.GetOk("allowed_client_id"); ok {
|
||||||
|
clientID := clientIDRaw.(string)
|
||||||
|
if clientID == "" {
|
||||||
|
return logical.ListResponse(providers), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered := make([]string, 0)
|
||||||
|
for _, name := range providers {
|
||||||
|
provider, err := i.getOIDCProvider(ctx, req.Storage, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if provider == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider.allowedClientID(clientID) {
|
||||||
|
filtered = append(filtered, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
providers = filtered
|
||||||
|
}
|
||||||
|
|
||||||
return logical.ListResponse(providers), nil
|
return logical.ListResponse(providers), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1592,8 +1641,7 @@ func (i *IdentityStore) pathOIDCAuthorize(ctx context.Context, req *logical.Requ
|
|||||||
if client == nil {
|
if client == nil {
|
||||||
return authResponse("", state, ErrAuthInvalidClientID, "client with client_id not found")
|
return authResponse("", state, ErrAuthInvalidClientID, "client with client_id not found")
|
||||||
}
|
}
|
||||||
if !strutil.StrListContains(provider.AllowedClientIDs, "*") &&
|
if !provider.allowedClientID(clientID) {
|
||||||
!strutil.StrListContains(provider.AllowedClientIDs, clientID) {
|
|
||||||
return authResponse("", state, ErrAuthUnauthorizedClient, "client is not authorized to use the provider")
|
return authResponse("", state, ErrAuthUnauthorizedClient, "client is not authorized to use the provider")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1812,8 +1860,7 @@ func (i *IdentityStore) pathOIDCToken(ctx context.Context, req *logical.Request,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate that the client is authorized to use the provider
|
// Validate that the client is authorized to use the provider
|
||||||
if !strutil.StrListContains(provider.AllowedClientIDs, "*") &&
|
if !provider.allowedClientID(clientID) {
|
||||||
!strutil.StrListContains(provider.AllowedClientIDs, clientID) {
|
|
||||||
return tokenResponse(nil, ErrTokenInvalidClient, "client is not authorized to use the provider")
|
return tokenResponse(nil, ErrTokenInvalidClient, "client is not authorized to use the provider")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2133,8 +2180,7 @@ func (i *IdentityStore) pathOIDCUserInfo(ctx context.Context, req *logical.Reque
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate that the client is authorized to use the provider
|
// Validate that the client is authorized to use the provider
|
||||||
if !strutil.StrListContains(provider.AllowedClientIDs, "*") &&
|
if !provider.allowedClientID(clientID) {
|
||||||
!strutil.StrListContains(provider.AllowedClientIDs, clientID) {
|
|
||||||
return userInfoResponse(nil, ErrUserInfoAccessDenied, "client is not authorized to use the provider")
|
return userInfoResponse(nil, ErrUserInfoAccessDenied, "client is not authorized to use the provider")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3350,6 +3350,89 @@ func TestOIDC_Path_OIDC_Provider_List(t *testing.T) {
|
|||||||
expectStrings(t, respListProvidersAfterDelete.Data["keys"].([]string), expectedStrings)
|
expectStrings(t, respListProvidersAfterDelete.Data["keys"].([]string), expectedStrings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOIDC_Path_OIDC_Provider_List_Filter(t *testing.T) {
|
||||||
|
c, _, _ := TestCoreUnsealed(t)
|
||||||
|
ctx := namespace.RootContext(nil)
|
||||||
|
|
||||||
|
// Create providers with different allowed_client_ids values
|
||||||
|
providers := []struct {
|
||||||
|
name string
|
||||||
|
allowedClientIDs []string
|
||||||
|
}{
|
||||||
|
{name: "p0", allowedClientIDs: []string{"*"}},
|
||||||
|
{name: "p1", allowedClientIDs: []string{"abc"}},
|
||||||
|
{name: "p2", allowedClientIDs: []string{"abc", "def"}},
|
||||||
|
{name: "p3", allowedClientIDs: []string{"abc", "def", "ghi"}},
|
||||||
|
{name: "p4", allowedClientIDs: []string{"ghi"}},
|
||||||
|
{name: "p5", allowedClientIDs: []string{"jkl"}},
|
||||||
|
}
|
||||||
|
for _, p := range providers {
|
||||||
|
resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
|
||||||
|
Path: "oidc/provider/" + p.name,
|
||||||
|
Operation: logical.CreateOperation,
|
||||||
|
Storage: c.identityStore.view,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"allowed_client_ids": p.allowedClientIDs,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expectSuccess(t, resp, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
clientIDFilter string
|
||||||
|
expectedProviders []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "list providers with client_id filter subset 1",
|
||||||
|
clientIDFilter: "abc",
|
||||||
|
expectedProviders: []string{"default", "p0", "p1", "p2", "p3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list providers with client_id filter subset 2",
|
||||||
|
clientIDFilter: "def",
|
||||||
|
expectedProviders: []string{"default", "p0", "p2", "p3"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list providers with client_id filter subset 3",
|
||||||
|
clientIDFilter: "ghi",
|
||||||
|
expectedProviders: []string{"default", "p0", "p3", "p4"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list providers with client_id filter subset 4",
|
||||||
|
clientIDFilter: "jkl",
|
||||||
|
expectedProviders: []string{"default", "p0", "p5"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list providers with client_id filter only matching glob",
|
||||||
|
clientIDFilter: "globmatch_only",
|
||||||
|
expectedProviders: []string{"default", "p0"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list providers with empty client_id filter returns all",
|
||||||
|
clientIDFilter: "",
|
||||||
|
expectedProviders: []string{"default", "p0", "p1", "p2", "p3", "p4", "p5"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
// List providers with the allowed_client_id query parameter
|
||||||
|
resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
|
||||||
|
Path: "oidc/provider",
|
||||||
|
Operation: logical.ListOperation,
|
||||||
|
Storage: c.identityStore.view,
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"allowed_client_id": tc.clientIDFilter,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expectSuccess(t, resp, err)
|
||||||
|
|
||||||
|
// Assert the filtered set of providers is returned
|
||||||
|
require.Equal(t, tc.expectedProviders, resp.Data["keys"])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestOIDC_Path_OpenIDProviderConfig tests read operations for the
|
// TestOIDC_Path_OpenIDProviderConfig tests read operations for the
|
||||||
// openid-configuration path
|
// openid-configuration path
|
||||||
func TestOIDC_Path_OpenIDProviderConfig(t *testing.T) {
|
func TestOIDC_Path_OpenIDProviderConfig(t *testing.T) {
|
||||||
|
|||||||
@@ -84,6 +84,11 @@ This endpoint returns a list of all OIDC providers.
|
|||||||
| :----- | :------------------------------ |
|
| :----- | :------------------------------ |
|
||||||
| `LIST` | `/identity/oidc/provider` |
|
| `LIST` | `/identity/oidc/provider` |
|
||||||
|
|
||||||
|
### Query Parameters
|
||||||
|
|
||||||
|
- `allowed_client_id` `(string: <optional>)` – Filters the list of OIDC providers to those
|
||||||
|
that allow the given client ID in their set of [allowed_client_ids](/api-docs/secret/identity/oidc-provider#allowed_client_ids).
|
||||||
|
|
||||||
### Sample Request
|
### Sample Request
|
||||||
|
|
||||||
```shell-session
|
```shell-session
|
||||||
|
|||||||
Reference in New Issue
Block a user