feat: more role backend and admin/writer roles additions and adjustments
This commit is contained in:
1 parent
8c6c6456da
commit
de142f6c5f
131 files changed
+2663
-435
No files matched your search
+38
-16
@@ -8,6 +8,7 @@ import (
|
||||
"RenewCMS/internal/domain/article"
|
||||
"RenewCMS/internal/domain/audit"
|
||||
"RenewCMS/internal/domain/image"
|
||||
"RenewCMS/internal/domain/role"
|
||||
"RenewCMS/internal/domain/update"
|
||||
"RenewCMS/internal/domain/user"
|
||||
"RenewCMS/internal/infrastructure/config"
|
||||
@@ -116,6 +117,7 @@ func newAPIKeyEnvRecording(t *testing.T, recorder audit.Recorder) *apiKeyEnv {
|
||||
GetUserUseCase: useCases.NewGetUserUseCase(env.users),
|
||||
GetMyAuthorUseCase: useCases.NewGetMyAuthorUseCase(plainAuthors()),
|
||||
GetMyAccessUseCase: useCases.NewGetMyAccessUseCase(env.roles),
|
||||
CheckPermissionUseCase: useCases.NewCheckPermissionUseCase(env.roles),
|
||||
ListPermissionsUseCase: useCases.NewListPermissionsUseCase(),
|
||||
CreateUserUseCase: useCases.NewCreateUserUseCase(env.users, hasher, mail, "https://cms.example.com", recorder),
|
||||
AuthenticateUserUseCase: useCases.NewAuthenticateUserUseCase(env.users, hasher, recorder),
|
||||
@@ -145,15 +147,19 @@ func newAPIKeyEnvRecording(t *testing.T, recorder audit.Recorder) *apiKeyEnv {
|
||||
CheckForUpdateUseCase: useCases.NewCheckForUpdateUseCase(env.feed, env.updates, apiKeyRunningVersion, clock, recorder),
|
||||
CreateAPIKeyUseCase: useCases.NewCreateAPIKeyUseCase(env.keys, env.generator, clock, recorder),
|
||||
ListAPIKeysUseCase: useCases.NewListAPIKeysUseCase(env.keys, env.users, clock),
|
||||
RevokeAPIKeyUseCase: useCases.NewRevokeAPIKeyUseCase(env.keys, env.users, clock, recorder),
|
||||
RevokeAPIKeyUseCase: useCases.NewRevokeAPIKeyUseCase(env.keys, clock, recorder),
|
||||
AuthenticateAPIKeyUseCase: useCases.NewAuthenticateAPIKeyUseCase(env.keys, env.generator, env.users, clock),
|
||||
}
|
||||
router := chi.NewRouter()
|
||||
router.Mount("/v1", api.InitBackendRoutes(env.container, env.settings))
|
||||
env.router = router
|
||||
env.rebuildRouter()
|
||||
return env
|
||||
}
|
||||
|
||||
func (e *apiKeyEnv) rebuildRouter() {
|
||||
router := chi.NewRouter()
|
||||
router.Mount("/v1", api.InitBackendRoutes(e.container, e.settings))
|
||||
e.router = router
|
||||
}
|
||||
|
||||
func (e *apiKeyEnv) issueKey(userId uint32, name string, mutate ...func(*apikey.APIKey)) (string, uint32) {
|
||||
e.issued++
|
||||
raw, prefix, hashed := e.generator.Generate()
|
||||
@@ -361,7 +367,7 @@ func TestAPIKeysHttp(t *testing.T) {
|
||||
if len(list) != 1 {
|
||||
t.Fatalf("expected one entry, got %d", len(list))
|
||||
}
|
||||
apiKeyAssertExactFields(t, list[0], "id", "user_id", "name", "prefix", "status", "created_at", "last_used_at", "expires_at", "revoked_at")
|
||||
apiKeyAssertExactFields(t, list[0], "id", "user_id", "name", "prefix", "status", "created_at", "last_used_at", "expires_at", "revoked_at", "created_by_name")
|
||||
if strings.Contains(rec.Body.String(), raw) || strings.Contains(rec.Body.String(), env.keys.sorted()[0].HashedKey) {
|
||||
t.Errorf("neither the raw key nor its hash may appear in the body")
|
||||
}
|
||||
@@ -431,7 +437,10 @@ func TestAPIKeysHttp(t *testing.T) {
|
||||
}
|
||||
raw := apiKeyDecodeObject(t, rec)["key"].(string)
|
||||
invite := env.withKey(t, http.MethodPost, "/v1/user/invite", `{"email": "new@example.com"}`, raw)
|
||||
assertAPIKeyMessage(t, invite, http.StatusForbidden, apperr.OwnerRequiredErrorMessage)
|
||||
assertAPIKeyStatus(t, invite, http.StatusCreated)
|
||||
if len(env.invites.invites) != 1 || env.invites.invites[1].InvitedBy != apiKeyDevId {
|
||||
t.Errorf("expected an invite invited by the key's creator, got %+v", env.invites.invites)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Key management needs a verified real session", func(t *testing.T) {
|
||||
@@ -564,13 +573,14 @@ func TestAPIKeysHttp(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("An admin's key can not do owner-only things", func(t *testing.T) {
|
||||
t.Run("A writer's key can not invite", func(t *testing.T) {
|
||||
env := newAPIKeyEnv(t)
|
||||
raw, _ := env.issueKey(apiKeyDevId, "ci")
|
||||
env.addUser(5, "", writerRoleID, true)
|
||||
raw, _ := env.issueKey(5, "ci")
|
||||
|
||||
rec := env.withKey(t, http.MethodPost, "/v1/user/invite", `{"email": "new@example.com"}`, raw)
|
||||
|
||||
assertAPIKeyMessage(t, rec, http.StatusForbidden, apperr.OwnerRequiredErrorMessage)
|
||||
assertAPIKeyMessage(t, rec, http.StatusForbidden, apperr.MissingPermissionErrorMessage)
|
||||
if len(env.invites.invites) != 0 {
|
||||
t.Errorf("expected no invite")
|
||||
}
|
||||
@@ -647,7 +657,8 @@ func TestAPIKeysHttp(t *testing.T) {
|
||||
|
||||
t.Run("A Bearer key wins over the cookie, and a bad key is not rescued by a good cookie", func(t *testing.T) {
|
||||
env := newAPIKeyEnv(t)
|
||||
raw, _ := env.issueKey(apiKeyPatId, "ci")
|
||||
env.addUser(5, "", writerRoleID, true)
|
||||
raw, _ := env.issueKey(5, "ci")
|
||||
|
||||
invite := env.send(t, apiKeyRequest{method: http.MethodPost, path: "/v1/user/invite", body: `{"email": "new@example.com"}`, bearer: raw, cookieUser: apiKeyOwnerId})
|
||||
assertAPIKeyStatus(t, invite, http.StatusForbidden)
|
||||
@@ -854,7 +865,7 @@ func TestAPIKeysHttp(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Listing as an admin shows only that admin's keys", func(t *testing.T) {
|
||||
t.Run("Listing as an admin shows every user's keys", func(t *testing.T) {
|
||||
env := newAPIKeyEnv(t)
|
||||
env.issueKey(apiKeyDevId, "a")
|
||||
env.issueKey(apiKeyDevId, "b")
|
||||
@@ -863,8 +874,8 @@ func TestAPIKeysHttp(t *testing.T) {
|
||||
rec := env.withCookie(t, http.MethodGet, "/v1/api-key", "", apiKeyDevId)
|
||||
|
||||
assertAPIKeyStatus(t, rec, http.StatusOK)
|
||||
if got := strings.Join(apiKeyEntryNames(t, rec), ","); got != "b,a" {
|
||||
t.Errorf("expected exactly b and a, got %s", got)
|
||||
if got := strings.Join(apiKeyEntryNames(t, rec), ","); got != "c,b,a" {
|
||||
t.Errorf("expected c, b and a, got %s", got)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -957,16 +968,26 @@ func TestAPIKeysHttp(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("An admin can't revoke someone else's key, and learns nothing about it", func(t *testing.T) {
|
||||
t.Run("An own-scope caller can't revoke someone else's key, and learns nothing about it", func(t *testing.T) {
|
||||
env := newAPIKeyEnv(t)
|
||||
env.customRoleUser(6, role.Grant{Key: "apikeys.revoke", Scope: role.ScopeOwn})
|
||||
raw, id := env.issueKey(apiKeyPatId, "ci")
|
||||
|
||||
rec := env.withCookie(t, http.MethodDelete, "/v1/api-key/"+strconv.Itoa(int(id)), "", apiKeyDevId)
|
||||
rec := env.withCookie(t, http.MethodDelete, "/v1/api-key/"+strconv.Itoa(int(id)), "", 6)
|
||||
|
||||
assertAPIKeyMessage(t, rec, http.StatusNotFound, apperr.APIKeyNotFoundErrorMessage)
|
||||
assertAPIKeyStatus(t, env.withKey(t, http.MethodGet, "/v1/settings", "", raw), http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("An admin revokes someone else's key", func(t *testing.T) {
|
||||
env := newAPIKeyEnv(t)
|
||||
raw, id := env.issueKey(apiKeyPatId, "ci")
|
||||
|
||||
assertAPIKeyStatus(t, env.withCookie(t, http.MethodDelete, "/v1/api-key/"+strconv.Itoa(int(id)), "", apiKeyDevId), http.StatusNoContent)
|
||||
|
||||
assertAPIKeyStatus(t, env.withKey(t, http.MethodGet, "/v1/settings", "", raw), http.StatusUnauthorized)
|
||||
})
|
||||
|
||||
t.Run("The owner can revoke any key", func(t *testing.T) {
|
||||
env := newAPIKeyEnv(t)
|
||||
raw, id := env.issueKey(apiKeyPatId, "ci")
|
||||
@@ -1056,8 +1077,9 @@ func TestAPIKeysHttp(t *testing.T) {
|
||||
t.Errorf("expected no stored key")
|
||||
}
|
||||
|
||||
env.customRoleUser(6, role.Grant{Key: "apikeys.view", Scope: role.ScopeOwn})
|
||||
env.keys.fail.failNext("ListByUser")
|
||||
assertAPIKeyMessage(t, env.withCookie(t, http.MethodGet, "/v1/api-key", "", apiKeyDevId), http.StatusInternalServerError, "Internal server error")
|
||||
assertAPIKeyMessage(t, env.withCookie(t, http.MethodGet, "/v1/api-key", "", 6), http.StatusInternalServerError, "Internal server error")
|
||||
env.keys.fail.failNext("ListAll")
|
||||
assertAPIKeyMessage(t, env.withCookie(t, http.MethodGet, "/v1/api-key", "", apiKeyOwnerId), http.StatusInternalServerError, "Internal server error")
|
||||
|
||||
|
||||
Reference in new issue
Block a user