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
+50 -3
View File
@@ -2,7 +2,10 @@ package api
import (
"context"
"encoding/json"
"errors"
"fmt"
"hash/fnv"
"net/http"
"github.com/grafana/grafana/pkg/api/response"
@@ -40,6 +43,22 @@ func ProvideApi(
return api
}
// generateFNVETag computes a FNV hash-based ETag for the SSOSettings struct
func generateFNVETag(SSOSettings *models.SSOSettings) (string, error) {
hasher := fnv.New64()
data, err := json.Marshal(SSOSettings)
if err != nil {
return "", err
}
_, err = hasher.Write(data)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", hasher.Sum(nil)), nil
}
// RegisterAPIEndpoints Registers Endpoints on Grafana Router
func (api *Api) RegisterAPIEndpoints() {
api.RouteRegister.Group("/api/v1/sso-settings", func(router routing.RouteRegister) {
@@ -91,18 +110,39 @@ func (api *Api) getAuthorizedList(ctx context.Context, identity identity.Request
return authorizedProviders, nil
}
// swagger:route GET /v1/sso-settings/{key} sso_settings getProviderSettings
//
// # Get an SSO Settings entry by Key
//
// You need to have a permission with action `settings:read` with scope `settings:auth.<provider>:*`.
//
// Responses:
// 200: okResponse
// 400: badRequestError
// 401: unauthorisedError
// 403: forbiddenError
// 404: notFoundError
func (api *Api) getProviderSettings(c *contextmodel.ReqContext) response.Response {
key, ok := web.Params(c.Req)[":key"]
if !ok {
return response.Error(http.StatusBadRequest, "Missing key", nil)
}
settings, err := api.SSOSettingsService.GetForProvider(c.Req.Context(), key)
provider, err := api.SSOSettingsService.GetForProviderWithRedactedSecrets(c.Req.Context(), key)
if err != nil {
return response.Error(http.StatusNotFound, "The provider was not found", err)
if errors.Is(err, ssosettings.ErrNotFound) {
return response.Error(http.StatusNotFound, "The provider was not found", err)
}
return response.Error(http.StatusInternalServerError, "Failed to get provider settings", err)
}
return response.JSON(http.StatusOK, settings)
etag, err := generateFNVETag(provider)
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to get provider settings", err)
}
return response.JSON(http.StatusOK, provider).SetHeader("ETag", etag)
}
// swagger:route PUT /v1/sso-settings/{key} sso_settings updateProviderSettings
@@ -172,6 +212,13 @@ func (api *Api) removeProviderSettings(c *contextmodel.ReqContext) response.Resp
return response.JSON(http.StatusNoContent, nil)
}
// swagger:parameters getProviderSettings
type GetProviderSettingsWrapper struct {
// in:path
// required:true
Provider string `json:"key"`
}
// swagger:parameters updateProviderSettings
type UpdateProviderSettingsParams struct {
// in:path