Merge branch 'main' into merge-main-20-seg20-regression
This commit is contained in:
@@ -264,6 +264,29 @@ func (s *Server) RenameGroup(_ context.Context, req *keepassgov1.RenameGroupRequ
|
||||
return &keepassgov1.RenameGroupResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteGroup(_ context.Context, req *keepassgov1.DeleteGroupRequest) (*keepassgov1.DeleteGroupResponse, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.locked {
|
||||
return nil, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
|
||||
if err := s.model.DeleteGroup(req.GetPath()); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, vault.ErrEntryNotFound):
|
||||
return nil, status.Error(codes.NotFound, err.Error())
|
||||
case errors.Is(err, vault.ErrGroupNotEmpty):
|
||||
return nil, status.Error(codes.FailedPrecondition, err.Error())
|
||||
default:
|
||||
return nil, status.Errorf(codes.Internal, "delete group: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
s.dirty = true
|
||||
return &keepassgov1.DeleteGroupResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpsertEntry(_ context.Context, req *keepassgov1.UpsertEntryRequest) (*keepassgov1.UpsertEntryResponse, error) {
|
||||
if req.GetEntry() == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "missing entry")
|
||||
@@ -605,6 +628,7 @@ func entryToProto(entry vault.Entry) *keepassgov1.Entry {
|
||||
Notes: entry.Notes,
|
||||
Tags: append([]string(nil), entry.Tags...),
|
||||
Path: append([]string(nil), entry.Path...),
|
||||
Fields: maps.Clone(entry.Fields),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,6 +642,7 @@ func entryFromProto(entry *keepassgov1.Entry) vault.Entry {
|
||||
Notes: entry.GetNotes(),
|
||||
Tags: append([]string(nil), entry.GetTags()...),
|
||||
Path: append([]string(nil), entry.GetPath()...),
|
||||
Fields: maps.Clone(entry.GetFields()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+78
-6
@@ -378,6 +378,9 @@ func TestVaultServiceListsEntriesForAuthorizedClients(t *testing.T) {
|
||||
if resp.Entries[0].Title != "Vault Console" {
|
||||
t.Fatalf("ListEntries().Entries[0].Title = %q, want %q", resp.Entries[0].Title, "Vault Console")
|
||||
}
|
||||
if got := resp.Entries[0].Fields["X-Role"]; got != "automation" {
|
||||
t.Fatalf("ListEntries().Entries[0].Fields[X-Role] = %q, want %q", got, "automation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVaultServiceListsCreatesAndRenamesGroupsForAuthorizedClients(t *testing.T) {
|
||||
@@ -427,6 +430,42 @@ func TestVaultServiceListsCreatesAndRenamesGroupsForAuthorizedClients(t *testing
|
||||
}
|
||||
}
|
||||
|
||||
func TestVaultServiceDeletesEmptyGroupsAndRejectsNonEmptyGroups(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, _, cleanup := newTestClient(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := metadata.AppendToOutgoingContext(context.Background(), "authorization", "Bearer test-token")
|
||||
if _, err := client.CreateGroup(ctx, &keepassgov1.CreateGroupRequest{
|
||||
ParentPath: []string{"Root"},
|
||||
Name: "Finance",
|
||||
}); err != nil {
|
||||
t.Fatalf("CreateGroup() error = %v", err)
|
||||
}
|
||||
|
||||
if _, err := client.DeleteGroup(ctx, &keepassgov1.DeleteGroupRequest{
|
||||
Path: []string{"Root", "Finance"},
|
||||
}); err != nil {
|
||||
t.Fatalf("DeleteGroup() error = %v, want success for empty group", err)
|
||||
}
|
||||
|
||||
listed, err := client.ListGroups(ctx, &keepassgov1.ListGroupsRequest{Path: []string{"Root"}})
|
||||
if err != nil {
|
||||
t.Fatalf("ListGroups() error = %v", err)
|
||||
}
|
||||
if len(listed.Names) != 2 || listed.Names[0] != "Home Assistant" || listed.Names[1] != "Internet" {
|
||||
t.Fatalf("ListGroups().Names = %#v, want empty Finance group removed", listed.Names)
|
||||
}
|
||||
|
||||
_, err = client.DeleteGroup(ctx, &keepassgov1.DeleteGroupRequest{
|
||||
Path: []string{"Root", "Internet"},
|
||||
})
|
||||
if status.Code(err) != codes.FailedPrecondition {
|
||||
t.Fatalf("DeleteGroup() code = %v, want %v for non-empty group", status.Code(err), codes.FailedPrecondition)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVaultServiceGeneratesPasswordsForAuthorizedClients(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -477,7 +516,10 @@ func TestVaultServiceUpsertsEntriesForAuthorizedClients(t *testing.T) {
|
||||
Username: "codex",
|
||||
Password: "token-2",
|
||||
Url: "https://surveillance.crew.example.invalid",
|
||||
Path: []string{"Root", "Home Assistant"},
|
||||
Fields: map[string]string{
|
||||
"X-Role": "lights-admin",
|
||||
},
|
||||
Path: []string{"Root", "Home Assistant"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -487,6 +529,9 @@ func TestVaultServiceUpsertsEntriesForAuthorizedClients(t *testing.T) {
|
||||
if upserted.Entry.Title != "Surveillance Console" {
|
||||
t.Fatalf("UpsertEntry().Entry.Title = %q, want %q", upserted.Entry.Title, "Surveillance Console")
|
||||
}
|
||||
if got := upserted.Entry.Fields["X-Role"]; got != "lights-admin" {
|
||||
t.Fatalf("UpsertEntry().Entry.Fields[X-Role] = %q, want %q", got, "lights-admin")
|
||||
}
|
||||
|
||||
listed, err := client.ListEntries(ctx, &keepassgov1.ListEntriesRequest{Path: []string{"Root", "Home Assistant"}})
|
||||
if err != nil {
|
||||
@@ -496,6 +541,9 @@ func TestVaultServiceUpsertsEntriesForAuthorizedClients(t *testing.T) {
|
||||
if len(listed.Entries) != 1 || listed.Entries[0].Password != "token-2" {
|
||||
t.Fatalf("ListEntries().Entries = %#v, want persisted Home Assistant entry", listed.Entries)
|
||||
}
|
||||
if got := listed.Entries[0].Fields["X-Role"]; got != "lights-admin" {
|
||||
t.Fatalf("ListEntries().Entries[0].Fields[X-Role] = %q, want %q", got, "lights-admin")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVaultServiceDeletesAndRestoresEntriesForAuthorizedClients(t *testing.T) {
|
||||
@@ -552,6 +600,9 @@ func TestVaultServiceListsAndInstantiatesTemplatesForAuthorizedClients(t *testin
|
||||
if len(templates.Templates) != 1 || templates.Templates[0].Title != "Website Login" {
|
||||
t.Fatalf("ListTemplates().Templates = %#v, want Website Login template", templates.Templates)
|
||||
}
|
||||
if got := templates.Templates[0].Fields["Environment"]; got != "prod" {
|
||||
t.Fatalf("ListTemplates().Templates[0].Fields[Environment] = %q, want %q", got, "prod")
|
||||
}
|
||||
|
||||
instantiated, err := client.InstantiateTemplate(ctx, &keepassgov1.InstantiateTemplateRequest{
|
||||
TemplateId: "website-login",
|
||||
@@ -561,8 +612,11 @@ func TestVaultServiceListsAndInstantiatesTemplatesForAuthorizedClients(t *testin
|
||||
Username: "rustyryan",
|
||||
Password: "hunter2",
|
||||
Url: "https://bellagio.example.invalid",
|
||||
Path: []string{"Root", "Internet"},
|
||||
Tags: []string{"dns"},
|
||||
Fields: map[string]string{
|
||||
"Environment": "staging",
|
||||
},
|
||||
Path: []string{"Root", "Internet"},
|
||||
Tags: []string{"dns"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -572,6 +626,9 @@ func TestVaultServiceListsAndInstantiatesTemplatesForAuthorizedClients(t *testin
|
||||
if instantiated.Entry.Title != "Bellagio" || instantiated.Entry.Notes != "Reusable template for website accounts." {
|
||||
t.Fatalf("InstantiateTemplate().Entry = %#v, want Bellagio entry with template notes", instantiated.Entry)
|
||||
}
|
||||
if got := instantiated.Entry.Fields["Environment"]; got != "staging" {
|
||||
t.Fatalf("InstantiateTemplate().Entry.Fields[Environment] = %q, want %q", got, "staging")
|
||||
}
|
||||
|
||||
listed, err := client.ListEntries(ctx, &keepassgov1.ListEntriesRequest{Path: []string{"Root", "Internet"}})
|
||||
if err != nil {
|
||||
@@ -596,7 +653,10 @@ func TestVaultServiceUpsertsAndDeletesTemplatesForAuthorizedClients(t *testing.T
|
||||
Title: "Website Login Updated",
|
||||
Username: "template-user",
|
||||
Password: "template-password",
|
||||
Path: []string{"Templates", "Web"},
|
||||
Fields: map[string]string{
|
||||
"Environment": "dev",
|
||||
},
|
||||
Path: []string{"Templates", "Web"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -605,6 +665,9 @@ func TestVaultServiceUpsertsAndDeletesTemplatesForAuthorizedClients(t *testing.T
|
||||
if upserted.Template.Title != "Website Login Updated" {
|
||||
t.Fatalf("UpsertTemplate().Template.Title = %q, want updated title", upserted.Template.Title)
|
||||
}
|
||||
if got := upserted.Template.Fields["Environment"]; got != "dev" {
|
||||
t.Fatalf("UpsertTemplate().Template.Fields[Environment] = %q, want %q", got, "dev")
|
||||
}
|
||||
|
||||
listed, err := client.ListTemplates(ctx, &keepassgov1.ListTemplatesRequest{})
|
||||
if err != nil {
|
||||
@@ -613,6 +676,9 @@ func TestVaultServiceUpsertsAndDeletesTemplatesForAuthorizedClients(t *testing.T
|
||||
if len(listed.Templates) != 1 || listed.Templates[0].Title != "Website Login Updated" {
|
||||
t.Fatalf("ListTemplates().Templates = %#v, want updated template", listed.Templates)
|
||||
}
|
||||
if got := listed.Templates[0].Fields["Environment"]; got != "dev" {
|
||||
t.Fatalf("ListTemplates().Templates[0].Fields[Environment] = %q, want %q", got, "dev")
|
||||
}
|
||||
|
||||
if _, err := client.DeleteTemplate(ctx, &keepassgov1.DeleteTemplateRequest{Id: "website-login"}); err != nil {
|
||||
t.Fatalf("DeleteTemplate() error = %v", err)
|
||||
@@ -721,6 +787,9 @@ func newTestClient(t *testing.T) (keepassgov1.VaultServiceClient, *memoryClipboa
|
||||
Username: "dannyocean",
|
||||
Password: "token-1",
|
||||
URL: "https://vault.crew.example.invalid",
|
||||
Fields: map[string]string{
|
||||
"X-Role": "automation",
|
||||
},
|
||||
History: []vault.Entry{
|
||||
{
|
||||
ID: "vault-console-h1",
|
||||
@@ -750,8 +819,11 @@ func newTestClient(t *testing.T) (keepassgov1.VaultServiceClient, *memoryClipboa
|
||||
Password: "template-password",
|
||||
URL: "https://example.com",
|
||||
Notes: "Reusable template for website accounts.",
|
||||
Tags: []string{"template", "web"},
|
||||
Path: []string{"Templates"},
|
||||
Fields: map[string]string{
|
||||
"Environment": "prod",
|
||||
},
|
||||
Tags: []string{"template", "web"},
|
||||
Path: []string{"Templates"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+294
-192
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ service VaultService {
|
||||
rpc ListGroups(ListGroupsRequest) returns (ListGroupsResponse);
|
||||
rpc CreateGroup(CreateGroupRequest) returns (CreateGroupResponse);
|
||||
rpc RenameGroup(RenameGroupRequest) returns (RenameGroupResponse);
|
||||
rpc DeleteGroup(DeleteGroupRequest) returns (DeleteGroupResponse);
|
||||
rpc UpsertEntry(UpsertEntryRequest) returns (UpsertEntryResponse);
|
||||
rpc DeleteEntry(DeleteEntryRequest) returns (DeleteEntryResponse);
|
||||
rpc RestoreEntry(RestoreEntryRequest) returns (RestoreEntryResponse);
|
||||
@@ -88,6 +89,7 @@ message Entry {
|
||||
string notes = 6;
|
||||
repeated string tags = 7;
|
||||
repeated string path = 8;
|
||||
map<string, string> fields = 9;
|
||||
}
|
||||
|
||||
message ListEntriesResponse {
|
||||
@@ -116,6 +118,12 @@ message RenameGroupRequest {
|
||||
|
||||
message RenameGroupResponse {}
|
||||
|
||||
message DeleteGroupRequest {
|
||||
repeated string path = 1;
|
||||
}
|
||||
|
||||
message DeleteGroupResponse {}
|
||||
|
||||
message UpsertEntryRequest {
|
||||
Entry entry = 1;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ const (
|
||||
VaultService_ListGroups_FullMethodName = "/keepassgo.v1.VaultService/ListGroups"
|
||||
VaultService_CreateGroup_FullMethodName = "/keepassgo.v1.VaultService/CreateGroup"
|
||||
VaultService_RenameGroup_FullMethodName = "/keepassgo.v1.VaultService/RenameGroup"
|
||||
VaultService_DeleteGroup_FullMethodName = "/keepassgo.v1.VaultService/DeleteGroup"
|
||||
VaultService_UpsertEntry_FullMethodName = "/keepassgo.v1.VaultService/UpsertEntry"
|
||||
VaultService_DeleteEntry_FullMethodName = "/keepassgo.v1.VaultService/DeleteEntry"
|
||||
VaultService_RestoreEntry_FullMethodName = "/keepassgo.v1.VaultService/RestoreEntry"
|
||||
@@ -60,6 +61,7 @@ type VaultServiceClient interface {
|
||||
ListGroups(ctx context.Context, in *ListGroupsRequest, opts ...grpc.CallOption) (*ListGroupsResponse, error)
|
||||
CreateGroup(ctx context.Context, in *CreateGroupRequest, opts ...grpc.CallOption) (*CreateGroupResponse, error)
|
||||
RenameGroup(ctx context.Context, in *RenameGroupRequest, opts ...grpc.CallOption) (*RenameGroupResponse, error)
|
||||
DeleteGroup(ctx context.Context, in *DeleteGroupRequest, opts ...grpc.CallOption) (*DeleteGroupResponse, error)
|
||||
UpsertEntry(ctx context.Context, in *UpsertEntryRequest, opts ...grpc.CallOption) (*UpsertEntryResponse, error)
|
||||
DeleteEntry(ctx context.Context, in *DeleteEntryRequest, opts ...grpc.CallOption) (*DeleteEntryResponse, error)
|
||||
RestoreEntry(ctx context.Context, in *RestoreEntryRequest, opts ...grpc.CallOption) (*RestoreEntryResponse, error)
|
||||
@@ -185,6 +187,16 @@ func (c *vaultServiceClient) RenameGroup(ctx context.Context, in *RenameGroupReq
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *vaultServiceClient) DeleteGroup(ctx context.Context, in *DeleteGroupRequest, opts ...grpc.CallOption) (*DeleteGroupResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(DeleteGroupResponse)
|
||||
err := c.cc.Invoke(ctx, VaultService_DeleteGroup_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *vaultServiceClient) UpsertEntry(ctx context.Context, in *UpsertEntryRequest, opts ...grpc.CallOption) (*UpsertEntryResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(UpsertEntryResponse)
|
||||
@@ -349,6 +361,7 @@ type VaultServiceServer interface {
|
||||
ListGroups(context.Context, *ListGroupsRequest) (*ListGroupsResponse, error)
|
||||
CreateGroup(context.Context, *CreateGroupRequest) (*CreateGroupResponse, error)
|
||||
RenameGroup(context.Context, *RenameGroupRequest) (*RenameGroupResponse, error)
|
||||
DeleteGroup(context.Context, *DeleteGroupRequest) (*DeleteGroupResponse, error)
|
||||
UpsertEntry(context.Context, *UpsertEntryRequest) (*UpsertEntryResponse, error)
|
||||
DeleteEntry(context.Context, *DeleteEntryRequest) (*DeleteEntryResponse, error)
|
||||
RestoreEntry(context.Context, *RestoreEntryRequest) (*RestoreEntryResponse, error)
|
||||
@@ -404,6 +417,9 @@ func (UnimplementedVaultServiceServer) CreateGroup(context.Context, *CreateGroup
|
||||
func (UnimplementedVaultServiceServer) RenameGroup(context.Context, *RenameGroupRequest) (*RenameGroupResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RenameGroup not implemented")
|
||||
}
|
||||
func (UnimplementedVaultServiceServer) DeleteGroup(context.Context, *DeleteGroupRequest) (*DeleteGroupResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method DeleteGroup not implemented")
|
||||
}
|
||||
func (UnimplementedVaultServiceServer) UpsertEntry(context.Context, *UpsertEntryRequest) (*UpsertEntryResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method UpsertEntry not implemented")
|
||||
}
|
||||
@@ -650,6 +666,24 @@ func _VaultService_RenameGroup_Handler(srv interface{}, ctx context.Context, dec
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _VaultService_DeleteGroup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(DeleteGroupRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(VaultServiceServer).DeleteGroup(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: VaultService_DeleteGroup_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(VaultServiceServer).DeleteGroup(ctx, req.(*DeleteGroupRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _VaultService_UpsertEntry_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpsertEntryRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@@ -967,6 +1001,10 @@ var VaultService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "RenameGroup",
|
||||
Handler: _VaultService_RenameGroup_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteGroup",
|
||||
Handler: _VaultService_DeleteGroup_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UpsertEntry",
|
||||
Handler: _VaultService_UpsertEntry_Handler,
|
||||
|
||||
+13
-12
@@ -7,19 +7,20 @@ import (
|
||||
)
|
||||
|
||||
var ErrEntryNotFound = errors.New("entry not found")
|
||||
var ErrGroupNotEmpty = errors.New("group is not empty")
|
||||
|
||||
type Entry struct {
|
||||
ID string
|
||||
Title string
|
||||
Username string
|
||||
Password string
|
||||
URL string
|
||||
Notes string
|
||||
Tags []string
|
||||
Fields map[string]string
|
||||
ID string
|
||||
Title string
|
||||
Username string
|
||||
Password string
|
||||
URL string
|
||||
Notes string
|
||||
Tags []string
|
||||
Fields map[string]string
|
||||
Attachments map[string][]byte
|
||||
History []Entry
|
||||
Path []string
|
||||
History []Entry
|
||||
Path []string
|
||||
}
|
||||
|
||||
type SearchResult struct {
|
||||
@@ -323,12 +324,12 @@ func (m *Model) MoveTemplate(id string, path []string) error {
|
||||
func (m *Model) DeleteGroup(path []string) error {
|
||||
for _, entry := range m.Entries {
|
||||
if slices.Equal(entry.Path, path) || hasPathPrefix(entry.Path, path) {
|
||||
return errors.New("group is not empty")
|
||||
return ErrGroupNotEmpty
|
||||
}
|
||||
}
|
||||
for _, entry := range m.Templates {
|
||||
if slices.Equal(entry.Path, path) || hasPathPrefix(entry.Path, path) {
|
||||
return errors.New("group is not empty")
|
||||
return ErrGroupNotEmpty
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user