2021-11-11 23:10:24 +08:00
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2022-01-19 17:23:46 +08:00
|
|
|
"encoding/json"
|
2021-11-11 23:10:24 +08:00
|
|
|
"fmt"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/api/routing"
|
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
|
|
|
"github.com/grafana/grafana/pkg/models"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
|
|
|
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
2022-01-21 05:42:05 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
2021-11-11 23:10:24 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/serviceaccounts"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/serviceaccounts/tests"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
2021-12-13 22:56:14 +08:00
|
|
|
"github.com/grafana/grafana/pkg/web"
|
2021-11-11 23:10:24 +08:00
|
|
|
"github.com/stretchr/testify/require"
|
2021-12-16 21:28:16 +08:00
|
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/serviceaccounts/database"
|
2021-11-11 23:10:24 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
2022-01-19 17:23:46 +08:00
|
|
|
serviceaccountIDPath = "/api/org/serviceaccounts/%v"
|
2021-11-11 23:10:24 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// test the accesscontrol endpoints
|
|
|
|
|
// with permissions and without permissions
|
|
|
|
|
func TestServiceAccountsAPI_DeleteServiceAccount(t *testing.T) {
|
|
|
|
|
store := sqlstore.InitTestDB(t)
|
|
|
|
|
svcmock := tests.ServiceAccountMock{}
|
|
|
|
|
|
2021-12-13 22:56:14 +08:00
|
|
|
var requestResponse = func(server *web.Mux, httpMethod, requestpath string) *httptest.ResponseRecorder {
|
2021-11-11 23:10:24 +08:00
|
|
|
req, err := http.NewRequest(httpMethod, requestpath, nil)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
recorder := httptest.NewRecorder()
|
|
|
|
|
server.ServeHTTP(recorder, req)
|
|
|
|
|
return recorder
|
|
|
|
|
}
|
|
|
|
|
t.Run("should be able to delete serviceaccount for with permissions", func(t *testing.T) {
|
|
|
|
|
testcase := struct {
|
|
|
|
|
user tests.TestUser
|
|
|
|
|
acmock *accesscontrolmock.Mock
|
|
|
|
|
expectedCode int
|
|
|
|
|
}{
|
|
|
|
|
|
|
|
|
|
user: tests.TestUser{Login: "servicetest1@admin", IsServiceAccount: true},
|
|
|
|
|
acmock: tests.SetupMockAccesscontrol(
|
|
|
|
|
t,
|
|
|
|
|
func(c context.Context, siu *models.SignedInUser) ([]*accesscontrol.Permission, error) {
|
|
|
|
|
return []*accesscontrol.Permission{{Action: serviceaccounts.ActionDelete, Scope: serviceaccounts.ScopeAll}}, nil
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
),
|
|
|
|
|
expectedCode: http.StatusOK,
|
|
|
|
|
}
|
2022-01-19 17:23:46 +08:00
|
|
|
serviceAccountRequestScenario(t, http.MethodDelete, serviceaccountIDPath, &testcase.user, func(httpmethod string, endpoint string, user *tests.TestUser) {
|
2021-11-11 23:10:24 +08:00
|
|
|
createduser := tests.SetupUserServiceAccount(t, store, testcase.user)
|
2022-01-19 17:23:46 +08:00
|
|
|
server := setupTestServer(t, &svcmock, routing.NewRouteRegister(), testcase.acmock, store)
|
2021-11-11 23:10:24 +08:00
|
|
|
actual := requestResponse(server, httpmethod, fmt.Sprintf(endpoint, fmt.Sprint(createduser.Id))).Code
|
|
|
|
|
require.Equal(t, testcase.expectedCode, actual)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("should be forbidden to delete serviceaccount via accesscontrol on endpoint", func(t *testing.T) {
|
|
|
|
|
testcase := struct {
|
|
|
|
|
user tests.TestUser
|
|
|
|
|
acmock *accesscontrolmock.Mock
|
|
|
|
|
expectedCode int
|
|
|
|
|
}{
|
|
|
|
|
user: tests.TestUser{Login: "servicetest2@admin", IsServiceAccount: true},
|
|
|
|
|
acmock: tests.SetupMockAccesscontrol(
|
|
|
|
|
t,
|
|
|
|
|
func(c context.Context, siu *models.SignedInUser) ([]*accesscontrol.Permission, error) {
|
|
|
|
|
return []*accesscontrol.Permission{}, nil
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
),
|
|
|
|
|
expectedCode: http.StatusForbidden,
|
|
|
|
|
}
|
2022-01-19 17:23:46 +08:00
|
|
|
serviceAccountRequestScenario(t, http.MethodDelete, serviceaccountIDPath, &testcase.user, func(httpmethod string, endpoint string, user *tests.TestUser) {
|
2021-11-11 23:10:24 +08:00
|
|
|
createduser := tests.SetupUserServiceAccount(t, store, testcase.user)
|
2022-01-19 17:23:46 +08:00
|
|
|
server := setupTestServer(t, &svcmock, routing.NewRouteRegister(), testcase.acmock, store)
|
|
|
|
|
actual := requestResponse(server, httpmethod, fmt.Sprintf(endpoint, createduser.Id)).Code
|
2021-11-11 23:10:24 +08:00
|
|
|
require.Equal(t, testcase.expectedCode, actual)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-19 17:23:46 +08:00
|
|
|
func serviceAccountRequestScenario(t *testing.T, httpMethod string, endpoint string, user *tests.TestUser, fn func(httpmethod string, endpoint string, user *tests.TestUser)) {
|
2021-11-11 23:10:24 +08:00
|
|
|
t.Helper()
|
|
|
|
|
fn(httpMethod, endpoint, user)
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-19 17:23:46 +08:00
|
|
|
func setupTestServer(t *testing.T, svc *tests.ServiceAccountMock, routerRegister routing.RouteRegister, acmock *accesscontrolmock.Mock, sqlStore *sqlstore.SQLStore) *web.Mux {
|
|
|
|
|
a := NewServiceAccountsAPI(svc, acmock, routerRegister, database.NewServiceAccountsStore(sqlStore))
|
2022-01-21 05:42:05 +08:00
|
|
|
a.RegisterAPIEndpoints(featuremgmt.WithToggles("service-accounts"))
|
2021-11-11 23:10:24 +08:00
|
|
|
|
2021-12-13 22:56:14 +08:00
|
|
|
m := web.New()
|
2021-11-11 23:10:24 +08:00
|
|
|
signedUser := &models.SignedInUser{
|
|
|
|
|
OrgId: 1,
|
|
|
|
|
OrgRole: models.ROLE_ADMIN,
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-13 22:56:14 +08:00
|
|
|
m.Use(func(c *web.Context) {
|
2021-11-11 23:10:24 +08:00
|
|
|
ctx := &models.ReqContext{
|
|
|
|
|
Context: c,
|
|
|
|
|
IsSignedIn: true,
|
|
|
|
|
SignedInUser: signedUser,
|
|
|
|
|
Logger: log.New("serviceaccounts-test"),
|
|
|
|
|
}
|
|
|
|
|
c.Map(ctx)
|
|
|
|
|
})
|
|
|
|
|
a.RouterRegister.Register(m.Router)
|
|
|
|
|
return m
|
|
|
|
|
}
|
2022-01-19 17:23:46 +08:00
|
|
|
|
|
|
|
|
func TestServiceAccountsAPI_RetrieveServiceAccount(t *testing.T) {
|
|
|
|
|
store := sqlstore.InitTestDB(t)
|
|
|
|
|
svcmock := tests.ServiceAccountMock{}
|
|
|
|
|
type testRetrieveSATestCase struct {
|
|
|
|
|
desc string
|
|
|
|
|
user *tests.TestUser
|
|
|
|
|
expectedCode int
|
|
|
|
|
acmock *accesscontrolmock.Mock
|
|
|
|
|
userID int
|
|
|
|
|
}
|
|
|
|
|
testCases := []testRetrieveSATestCase{
|
|
|
|
|
{
|
|
|
|
|
desc: "should be ok to retrieve serviceaccount with permissions",
|
|
|
|
|
user: &tests.TestUser{Login: "servicetest1@admin", IsServiceAccount: true},
|
|
|
|
|
acmock: tests.SetupMockAccesscontrol(
|
|
|
|
|
t,
|
|
|
|
|
func(c context.Context, siu *models.SignedInUser) ([]*accesscontrol.Permission, error) {
|
|
|
|
|
return []*accesscontrol.Permission{{Action: serviceaccounts.ActionRead, Scope: serviceaccounts.ScopeAll}}, nil
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
),
|
|
|
|
|
expectedCode: http.StatusOK,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
desc: "should be forbidden to retrieve serviceaccount if no permissions",
|
|
|
|
|
user: &tests.TestUser{Login: "servicetest2@admin", IsServiceAccount: true},
|
|
|
|
|
acmock: tests.SetupMockAccesscontrol(
|
|
|
|
|
t,
|
|
|
|
|
func(c context.Context, siu *models.SignedInUser) ([]*accesscontrol.Permission, error) {
|
|
|
|
|
return []*accesscontrol.Permission{}, nil
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
),
|
|
|
|
|
expectedCode: http.StatusForbidden,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
desc: "should be not found when the user doesnt exist",
|
|
|
|
|
user: nil,
|
|
|
|
|
userID: 12,
|
|
|
|
|
acmock: tests.SetupMockAccesscontrol(
|
|
|
|
|
t,
|
|
|
|
|
func(c context.Context, siu *models.SignedInUser) ([]*accesscontrol.Permission, error) {
|
|
|
|
|
return []*accesscontrol.Permission{{Action: serviceaccounts.ActionRead, Scope: serviceaccounts.ScopeAll}}, nil
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
),
|
|
|
|
|
expectedCode: http.StatusNotFound,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var requestResponse = func(server *web.Mux, httpMethod, requestpath string) *httptest.ResponseRecorder {
|
|
|
|
|
req, err := http.NewRequest(httpMethod, requestpath, nil)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
recorder := httptest.NewRecorder()
|
|
|
|
|
server.ServeHTTP(recorder, req)
|
|
|
|
|
return recorder
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
|
|
|
serviceAccountRequestScenario(t, http.MethodGet, serviceaccountIDPath, tc.user, func(httpmethod string, endpoint string, user *tests.TestUser) {
|
|
|
|
|
scopeID := tc.userID
|
|
|
|
|
if tc.user != nil {
|
|
|
|
|
createdUser := tests.SetupUserServiceAccount(t, store, *tc.user)
|
|
|
|
|
scopeID = int(createdUser.Id)
|
|
|
|
|
}
|
|
|
|
|
server := setupTestServer(t, &svcmock, routing.NewRouteRegister(), tc.acmock, store)
|
|
|
|
|
|
|
|
|
|
actual := requestResponse(server, httpmethod, fmt.Sprintf(endpoint, scopeID))
|
|
|
|
|
|
|
|
|
|
actualCode := actual.Code
|
|
|
|
|
require.Equal(t, tc.expectedCode, actualCode)
|
|
|
|
|
|
|
|
|
|
if actualCode == http.StatusOK {
|
|
|
|
|
actualBody := map[string]interface{}{}
|
|
|
|
|
err := json.Unmarshal(actual.Body.Bytes(), &actualBody)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Equal(t, scopeID, int(actualBody["userId"].(float64)))
|
|
|
|
|
require.Equal(t, tc.user.Login, actualBody["login"].(string))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|