OIDC Client API: add more test coverage (#12392)

* initial commit

* add read and delete operations

* fix bug in delete and add list unit test

* func doc typo fix

* add existence check for assignment

* remove locking on the assignment resource

It is not needed at this time.

* convert Callbacks to Operations

- convert Callbacks to Operations
- add test case for update operations

* add CRUD operations and test cases

* add client api and tests

* remove use of oidcCache

* remove use of oidcCache

* add template validation and update tests

* remove usage of oidcCache

* refactor struct and var names

* harmonize test name conventions

* refactor struct and var names

* add changelog and refactor

- add changelog
- be more explicit in the case where we do not recieve a path field

* refactor

be more explicit in the case where a field is not provided

* remove extra period from changelog

* update scope path to be OIDC provider specific

* refactor naming conventions

* update assignment path

* update scope path

* enforce key existence on client creation

* removed unused name field

* removed unused name field

* removed unused name field

* prevent assignment deletion when ref'ed by a client

* enfoce assignment existence on client create/update

* update scope template description

* error when attempting to created scope with openid reserved name

* fix UT failures after requiring assignment existence

* disallow key deletion when ref'ed by existing client

* generate client_id and client_secret on CreateOp

* do not allow key modification on client update

* return client_id and client_secret on read ops

* small refactor

* fix bug in delete assignment op

* remove client secret get call

* OIDC Client API: add more test coverage

* change name convention in tests
This commit is contained in:
John-Michael Faircloth 2021-08-23 14:08:03 -05:00 committed by GitHub
parent 35d5901ac0
commit 924cab3b06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 253 additions and 5 deletions

View File

@ -1,10 +1,13 @@
package vault
import (
"encoding/base64"
"fmt"
"testing"
"github.com/go-test/deep"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
@ -416,6 +419,66 @@ func TestOIDC_Path_OIDC_ProviderClient_List(t *testing.T) {
expectStrings(t, respListClientAfterDelete.Data["keys"].([]string), expectedStrings)
}
// TestOIDC_pathOIDCClientExistenceCheck tests pathOIDCClientExistenceCheck
func TestOIDC_pathOIDCClientExistenceCheck(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ctx := namespace.RootContext(nil)
storage := &logical.InmemStorage{}
clientName := "test"
// Expect nil with empty storage
exists, err := c.identityStore.pathOIDCClientExistenceCheck(
ctx,
&logical.Request{
Storage: storage,
},
&framework.FieldData{
Raw: map[string]interface{}{"name": clientName},
Schema: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
},
},
},
)
if err != nil {
t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
}
if exists {
t.Fatalf("Expected existence check to return false but instead returned: %t", exists)
}
// Populte storage with a client
client := &client{}
entry, _ := logical.StorageEntryJSON(clientPath+clientName, client)
if err := storage.Put(ctx, entry); err != nil {
t.Fatalf("writing to in mem storage failed")
}
// Expect true with a populated storage
exists, err = c.identityStore.pathOIDCClientExistenceCheck(
ctx,
&logical.Request{
Storage: storage,
},
&framework.FieldData{
Raw: map[string]interface{}{"name": clientName},
Schema: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
},
},
},
)
if err != nil {
t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
}
if !exists {
t.Fatalf("Expected existence check to return true but instead returned: %t", exists)
}
}
// TestOIDC_Path_OIDC_ProviderScope_ReservedName tests that the reserved name
// "openid" cannot be used when creating a scope
func TestOIDC_Path_OIDC_ProviderScope_ReservedName(t *testing.T) {
@ -437,6 +500,67 @@ func TestOIDC_Path_OIDC_ProviderScope_ReservedName(t *testing.T) {
expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
}
// TestOIDC_Path_OIDC_ProviderScope_TemplateValidation tests that the template
// validation does not allow restricted claims
func TestOIDC_Path_OIDC_ProviderScope_TemplateValidation(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ctx := namespace.RootContext(nil)
storage := &logical.InmemStorage{}
testCases := []struct {
templ string
restrictedKey string
}{
{
templ: `{"aud": "client-12345", "other": "test"}`,
restrictedKey: "aud",
},
{
templ: `{"exp": 1311280970, "other": "test"}`,
restrictedKey: "exp",
},
{
templ: `{"iat": 1311280970, "other": "test"}`,
restrictedKey: "iat",
},
{
templ: `{"iss": "https://openid.c2id.com", "other": "test"}`,
restrictedKey: "iss",
},
{
templ: `{"namespace": "n-0S6_WzA2Mj", "other": "test"}`,
restrictedKey: "namespace",
},
{
templ: `{"sub": "alice", "other": "test"}`,
restrictedKey: "sub",
},
}
for _, tc := range testCases {
encodedTempl := base64.StdEncoding.EncodeToString([]byte(tc.templ))
// Create a test scope "test-scope" -- should fail
resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "oidc/scope/test-scope",
Operation: logical.CreateOperation,
Storage: storage,
Data: map[string]interface{}{
"template": encodedTempl,
"description": "my-description",
},
})
expectError(t, resp, err)
errString := fmt.Sprintf(
"top level key %q not allowed. Restricted keys: iat, aud, exp, iss, sub, namespace",
tc.restrictedKey,
)
// validate error message
expectedStrings := map[string]interface{}{
errString: true,
}
expectStrings(t, []string{resp.Data["error"].(string)}, expectedStrings)
}
}
// TestOIDC_Path_OIDC_ProviderScope tests CRUD operations for scopes
func TestOIDC_Path_OIDC_ProviderScope(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
@ -466,12 +590,14 @@ func TestOIDC_Path_OIDC_ProviderScope(t *testing.T) {
t.Fatal(diff)
}
templ := `{ "groups": {{identity.entity.groups.names}} }`
encodedTempl := base64.StdEncoding.EncodeToString([]byte(templ))
// Update "test-scope" -- should succeed
resp, err = c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "oidc/scope/test-scope",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"template": "eyAiZ3JvdXBzIjoge3tpZGVudGl0eS5lbnRpdHkuZ3JvdXBzLm5hbWVzfX0gfQ==",
"template": encodedTempl,
"description": "my-description",
},
Storage: storage,
@ -486,7 +612,7 @@ func TestOIDC_Path_OIDC_ProviderScope(t *testing.T) {
})
expectSuccess(t, resp, err)
expected = map[string]interface{}{
"template": "{ \"groups\": {{identity.entity.groups.names}} }",
"template": templ,
"description": "my-description",
}
if diff := deep.Equal(expected, resp.Data); diff != nil {
@ -518,13 +644,15 @@ func TestOIDC_Path_OIDC_ProviderScope_Update(t *testing.T) {
ctx := namespace.RootContext(nil)
storage := &logical.InmemStorage{}
templ := `{ "groups": {{identity.entity.groups.names}} }`
encodedTempl := base64.StdEncoding.EncodeToString([]byte(templ))
// Create a test scope "test-scope" -- should succeed
resp, err := c.identityStore.HandleRequest(ctx, &logical.Request{
Path: "oidc/scope/test-scope",
Operation: logical.CreateOperation,
Storage: storage,
Data: map[string]interface{}{
"template": "eyAiZ3JvdXBzIjoge3tpZGVudGl0eS5lbnRpdHkuZ3JvdXBzLm5hbWVzfX0gfQ==",
"template": encodedTempl,
"description": "my-description",
},
})
@ -538,7 +666,7 @@ func TestOIDC_Path_OIDC_ProviderScope_Update(t *testing.T) {
})
expectSuccess(t, resp, err)
expected := map[string]interface{}{
"template": "{ \"groups\": {{identity.entity.groups.names}} }",
"template": templ,
"description": "my-description",
}
if diff := deep.Equal(expected, resp.Data); diff != nil {
@ -550,7 +678,7 @@ func TestOIDC_Path_OIDC_ProviderScope_Update(t *testing.T) {
Path: "oidc/scope/test-scope",
Operation: logical.UpdateOperation,
Data: map[string]interface{}{
"template": "eyAiZ3JvdXBzIjoge3tpZGVudGl0eS5lbnRpdHkuZ3JvdXBzLm5hbWVzfX0gfQ==",
"template": encodedTempl,
"description": "my-description-2",
},
Storage: storage,
@ -624,6 +752,66 @@ func TestOIDC_Path_OIDC_ProviderScope_List(t *testing.T) {
expectStrings(t, respListScopeAfterDelete.Data["keys"].([]string), expectedStrings)
}
// TestOIDC_pathOIDCScopeExistenceCheck tests pathOIDCScopeExistenceCheck
func TestOIDC_pathOIDCScopeExistenceCheck(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ctx := namespace.RootContext(nil)
storage := &logical.InmemStorage{}
scopeName := "test"
// Expect nil with empty storage
exists, err := c.identityStore.pathOIDCScopeExistenceCheck(
ctx,
&logical.Request{
Storage: storage,
},
&framework.FieldData{
Raw: map[string]interface{}{"name": scopeName},
Schema: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
},
},
},
)
if err != nil {
t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
}
if exists {
t.Fatalf("Expected existence check to return false but instead returned: %t", exists)
}
// Populte storage with a scope
scope := &scope{}
entry, _ := logical.StorageEntryJSON(scopePath+scopeName, scope)
if err := storage.Put(ctx, entry); err != nil {
t.Fatalf("writing to in mem storage failed")
}
// Expect true with a populated storage
exists, err = c.identityStore.pathOIDCScopeExistenceCheck(
ctx,
&logical.Request{
Storage: storage,
},
&framework.FieldData{
Raw: map[string]interface{}{"name": scopeName},
Schema: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
},
},
},
)
if err != nil {
t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
}
if !exists {
t.Fatalf("Expected existence check to return true but instead returned: %t", exists)
}
}
// TestOIDC_Path_OIDC_ProviderAssignment tests CRUD operations for assignments
func TestOIDC_Path_OIDC_ProviderAssignment(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
@ -876,3 +1064,63 @@ func TestOIDC_Path_OIDC_ProviderAssignment_List(t *testing.T) {
delete(expectedStrings, "test-assignment2")
expectStrings(t, respListAssignmentAfterDelete.Data["keys"].([]string), expectedStrings)
}
// TestOIDC_pathOIDCAssignmentExistenceCheck tests pathOIDCAssignmentExistenceCheck
func TestOIDC_pathOIDCAssignmentExistenceCheck(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)
ctx := namespace.RootContext(nil)
storage := &logical.InmemStorage{}
assignmentName := "test"
// Expect nil with empty storage
exists, err := c.identityStore.pathOIDCAssignmentExistenceCheck(
ctx,
&logical.Request{
Storage: storage,
},
&framework.FieldData{
Raw: map[string]interface{}{"name": assignmentName},
Schema: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
},
},
},
)
if err != nil {
t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
}
if exists {
t.Fatalf("Expected existence check to return false but instead returned: %t", exists)
}
// Populte storage with a assignment
assignment := &assignment{}
entry, _ := logical.StorageEntryJSON(assignmentPath+assignmentName, assignment)
if err := storage.Put(ctx, entry); err != nil {
t.Fatalf("writing to in mem storage failed")
}
// Expect true with a populated storage
exists, err = c.identityStore.pathOIDCAssignmentExistenceCheck(
ctx,
&logical.Request{
Storage: storage,
},
&framework.FieldData{
Raw: map[string]interface{}{"name": assignmentName},
Schema: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
},
},
},
)
if err != nil {
t.Fatalf("Error during existence check on an expected nil entry, err:\n%#v", err)
}
if !exists {
t.Fatalf("Expected existence check to return true but instead returned: %t", exists)
}
}