Auth: Implement the SSO Settings GET endpoint (#79144)

* Return data in camelCase from the OAuth fb strategy

* changes

* wip

* Add defaults for oauth fb strategy

* revert other changes

* basic includeDefaults query param implementation

* basic secret removal and etag implementation

* correct imports

* rebase

* move default settings filter to models

* only replace ClientSecret value if set

* first GetForProvider test & use FNV for ETag to avoid Blocklisted import error

* add tests

* add annotation for the openapi spec & generate spec

* remove TODO

* use IsSecret, improve tests, remove DefaultOAuthSettings

* add comment explaining generateFNVETag

* add error handling for generateFNVETag

* run go generate

* Update pkg/services/ssosettings/api/api.go

Co-authored-by: Mihai Doarna <mihai.doarna@grafana.com>

* move isSecret to service, create GetForProviderWithRedactedSecrets func

* add unit test for GetForProviderWithRedactedSecrets & remove duplicated code

* regen openapi/swagger

* revert dependency bumps

---------

Co-authored-by: Mihaly Gyongyosi <mgyongyosi@users.noreply.github.com>
Co-authored-by: Mihai Doarna <mihai.doarna@grafana.com>
This commit is contained in:
colin-stuart
2024-01-08 09:35:14 -05:00
committed by GitHub
co-authored by Mihai Doarna Mihaly Gyongyosi
parent 505196bcd5
commit 062e772bb2
11 changed files with 440 additions and 6 deletions
+114
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
@@ -253,6 +254,119 @@ func TestSSOSettingsAPI_Delete(t *testing.T) {
}
}
func TestSSOSettingsAPI_GetForProvider(t *testing.T) {
type TestCase struct {
desc string
key string
action string
scope string
expectedResult *models.SSOSettings
expectedError error
expectedServiceCall bool
expectedStatusCode int
}
tests := []TestCase{
{
desc: "successfully gets SSO settings",
key: "azuread",
action: "settings:read",
scope: "settings:auth.azuread:*",
expectedResult: &models.SSOSettings{
ID: "1",
Provider: "azuread",
Settings: make(map[string]interface{}),
Created: time.Now(),
Updated: time.Now(),
IsDeleted: false,
Source: models.DB,
},
expectedError: nil,
expectedServiceCall: true,
expectedStatusCode: http.StatusOK,
},
{
desc: "fails when action doesn't match",
key: "azuread",
action: "settings:write",
scope: "settings:auth.azuread:*",
expectedResult: nil,
expectedError: nil,
expectedServiceCall: false,
expectedStatusCode: http.StatusForbidden,
},
{
desc: "fails when scope doesn't match",
key: "azuread",
action: "settings:read",
scope: "settings:auth.azuread:write",
expectedResult: nil,
expectedError: nil,
expectedServiceCall: false,
expectedStatusCode: http.StatusForbidden,
},
{
desc: "fails when scope contains another provider",
key: "azuread",
action: "settings:read",
scope: "settings:auth.github:*",
expectedResult: nil,
expectedError: nil,
expectedServiceCall: false,
expectedStatusCode: http.StatusForbidden,
},
{
desc: "fails with not found when key was not found",
key: "nonexistant",
action: "settings:read",
scope: "settings:auth.nonexistant:*",
expectedResult: nil,
expectedError: ssosettings.ErrNotFound,
expectedServiceCall: true,
expectedStatusCode: http.StatusNotFound,
},
{
desc: "fails with internal server error when service returns an error",
key: "azuread",
action: "settings:read",
scope: "settings:auth.azuread:*",
expectedResult: nil,
expectedError: errors.New("something went wrong"),
expectedServiceCall: true,
expectedStatusCode: http.StatusInternalServerError,
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
service := ssosettingstests.NewMockService(t)
if tt.expectedServiceCall {
service.On("GetForProviderWithRedactedSecrets", mock.AnythingOfType("*context.valueCtx"), tt.key).Return(tt.expectedResult, tt.expectedError).Once()
}
server := setupTests(t, service)
path := fmt.Sprintf("/api/v1/sso-settings/%s", tt.key)
req := server.NewRequest(http.MethodGet, path, nil)
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
OrgRole: org.RoleEditor,
OrgID: 1,
Permissions: getPermissionsForActionAndScope(tt.action, tt.scope),
})
res, err := server.SendJSON(req)
require.NoError(t, err)
require.Equal(t, tt.expectedStatusCode, res.StatusCode)
if tt.expectedError == nil {
var data models.SSOSettings
require.NoError(t, json.NewDecoder(res.Body).Decode(&data))
}
require.NoError(t, res.Body.Close())
})
}
}
func getPermissionsForActionAndScope(action, scope string) map[int64]map[string][]string {
return map[int64]map[string][]string{
1: accesscontrol.GroupScopesByAction([]accesscontrol.Permission{{