Fix scoped gRPC persistence and autosave behavior
This commit is contained in:
+126
-42
@@ -249,6 +249,7 @@ func (s *Server) FindBrowserLogins(ctx context.Context, req *keepassgov1.FindBro
|
||||
if locked {
|
||||
return nil, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
displayModel := visibleModel(model)
|
||||
token, err := s.authenticateRequest(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -259,7 +260,7 @@ func (s *Server) FindBrowserLogins(ctx context.Context, req *keepassgov1.FindBro
|
||||
}
|
||||
|
||||
var matches []rankedBrowserMatch
|
||||
for _, entry := range visibleModel(model).Entries {
|
||||
for _, entry := range displayModel.Entries {
|
||||
quality, score := classifyBrowserEntryMatch(pageHost, entry.URL)
|
||||
if score == 0 {
|
||||
continue
|
||||
@@ -425,11 +426,13 @@ func (s *Server) ListEntries(ctx context.Context, req *keepassgov1.ListEntriesRe
|
||||
if locked {
|
||||
return nil, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
if _, err := s.authorizePathRequest(ctx, apitokens.OperationListEntries, req.GetPath()); err != nil {
|
||||
displayModel := visibleModel(model)
|
||||
internalPath := expandClientPath(displayModel, req.GetPath())
|
||||
if _, err := s.authorizePathRequest(ctx, apitokens.OperationListEntries, internalPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
model = visibleModel(model)
|
||||
model = displayModel
|
||||
var entries []vault.Entry
|
||||
if strings.TrimSpace(req.GetQuery()) != "" {
|
||||
results := model.Search(req.GetQuery())
|
||||
@@ -438,14 +441,14 @@ func (s *Server) ListEntries(ctx context.Context, req *keepassgov1.ListEntriesRe
|
||||
entries = append(entries, result.Entry)
|
||||
}
|
||||
} else {
|
||||
entries = model.EntriesInPath(req.GetPath())
|
||||
entries = model.EntriesInPath(internalPath)
|
||||
}
|
||||
|
||||
resp := &keepassgov1.ListEntriesResponse{
|
||||
Entries: make([]*keepassgov1.Entry, 0, len(entries)),
|
||||
}
|
||||
for _, entry := range entries {
|
||||
resp.Entries = append(resp.Entries, entryToProto(entry))
|
||||
resp.Entries = append(resp.Entries, entryToProtoWithModel(model, entry))
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
@@ -456,44 +459,50 @@ func (s *Server) ListGroups(ctx context.Context, req *keepassgov1.ListGroupsRequ
|
||||
if locked {
|
||||
return nil, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
if _, err := s.authorizePathRequest(ctx, apitokens.OperationListGroups, req.GetPath()); err != nil {
|
||||
displayModel := visibleModel(model)
|
||||
internalPath := expandClientPath(displayModel, req.GetPath())
|
||||
if _, err := s.authorizePathRequest(ctx, apitokens.OperationListGroups, internalPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &keepassgov1.ListGroupsResponse{
|
||||
Names: visibleModel(model).ChildGroups(req.GetPath()),
|
||||
Names: displayModel.ChildGroups(internalPath),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) CreateGroup(ctx context.Context, req *keepassgov1.CreateGroupRequest) (*keepassgov1.CreateGroupResponse, error) {
|
||||
if _, err := s.authorizePathRequest(ctx, apitokens.OperationMutateGroup, req.GetParentPath()); err != nil {
|
||||
model, locked := s.snapshotModel()
|
||||
if locked {
|
||||
return nil, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
parentPath := expandClientPath(visibleModel(model), req.GetParentPath())
|
||||
if _, err := s.authorizePathRequest(ctx, apitokens.OperationMutateGroup, parentPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.locked {
|
||||
return nil, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
|
||||
s.model.CreateGroup(req.GetParentPath(), req.GetName())
|
||||
s.model.CreateGroup(parentPath, req.GetName())
|
||||
s.dirty = true
|
||||
s.syncMutationLocked()
|
||||
return &keepassgov1.CreateGroupResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RenameGroup(ctx context.Context, req *keepassgov1.RenameGroupRequest) (*keepassgov1.RenameGroupResponse, error) {
|
||||
if _, err := s.authorizePathRequest(ctx, apitokens.OperationMutateGroup, req.GetPath()); err != nil {
|
||||
model, locked := s.snapshotModel()
|
||||
if locked {
|
||||
return nil, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
groupPath := expandClientPath(visibleModel(model), req.GetPath())
|
||||
if _, err := s.authorizePathRequest(ctx, apitokens.OperationMutateGroup, groupPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.locked {
|
||||
return nil, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
|
||||
if err := s.model.RenameGroup(req.GetPath(), req.GetNewName()); err != nil {
|
||||
if err := s.model.RenameGroup(groupPath, req.GetNewName()); err != nil {
|
||||
if errors.Is(err, vault.ErrEntryNotFound) {
|
||||
return nil, status.Error(codes.NotFound, err.Error())
|
||||
}
|
||||
@@ -506,17 +515,19 @@ func (s *Server) RenameGroup(ctx context.Context, req *keepassgov1.RenameGroupRe
|
||||
}
|
||||
|
||||
func (s *Server) DeleteGroup(ctx context.Context, req *keepassgov1.DeleteGroupRequest) (*keepassgov1.DeleteGroupResponse, error) {
|
||||
if _, err := s.authorizePathRequest(ctx, apitokens.OperationMutateGroup, req.GetPath()); err != nil {
|
||||
model, locked := s.snapshotModel()
|
||||
if locked {
|
||||
return nil, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
groupPath := expandClientPath(visibleModel(model), req.GetPath())
|
||||
if _, err := s.authorizePathRequest(ctx, apitokens.OperationMutateGroup, groupPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 {
|
||||
if err := s.model.DeleteGroup(groupPath); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, vault.ErrEntryNotFound):
|
||||
return nil, status.Error(codes.NotFound, err.Error())
|
||||
@@ -537,22 +548,22 @@ func (s *Server) UpsertEntry(ctx context.Context, req *keepassgov1.UpsertEntryRe
|
||||
return nil, status.Error(codes.InvalidArgument, "missing entry")
|
||||
}
|
||||
|
||||
entry := entryFromProto(req.GetEntry())
|
||||
if _, err := s.authorizeEntryRequest(ctx, apitokens.OperationMutateEntry, entry); err != nil {
|
||||
model, locked := s.snapshotModel()
|
||||
if locked {
|
||||
return nil, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
entry := entryFromProtoWithModel(visibleModel(model), req.GetEntry())
|
||||
if _, err := s.authorizeUpsertEntryRequest(ctx, entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
if s.locked {
|
||||
s.mu.Unlock()
|
||||
return nil, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
s.model.UpsertEntry(entry)
|
||||
s.dirty = true
|
||||
s.syncMutationLocked()
|
||||
s.mu.Unlock()
|
||||
|
||||
return &keepassgov1.UpsertEntryResponse{Entry: entryToProto(entry)}, nil
|
||||
return &keepassgov1.UpsertEntryResponse{Entry: entryToProtoWithModel(visibleModel(model), entry)}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteEntry(ctx context.Context, req *keepassgov1.DeleteEntryRequest) (*keepassgov1.DeleteEntryResponse, error) {
|
||||
@@ -612,7 +623,7 @@ func (s *Server) RestoreEntry(ctx context.Context, req *keepassgov1.RestoreEntry
|
||||
|
||||
s.dirty = true
|
||||
s.syncMutationLocked()
|
||||
return &keepassgov1.RestoreEntryResponse{Entry: entryToProto(restored)}, nil
|
||||
return &keepassgov1.RestoreEntryResponse{Entry: entryToProtoWithModel(visibleModel(model), restored)}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListEntryHistory(ctx context.Context, req *keepassgov1.ListEntryHistoryRequest) (*keepassgov1.ListEntryHistoryResponse, error) {
|
||||
@@ -633,7 +644,7 @@ func (s *Server) ListEntryHistory(ctx context.Context, req *keepassgov1.ListEntr
|
||||
Entries: make([]*keepassgov1.Entry, 0, len(entry.History)),
|
||||
}
|
||||
for _, historical := range entry.History {
|
||||
resp.Entries = append(resp.Entries, entryToProto(historical))
|
||||
resp.Entries = append(resp.Entries, entryToProtoWithModel(visibleModel(model), historical))
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
@@ -666,7 +677,7 @@ func (s *Server) RestoreEntryHistory(ctx context.Context, req *keepassgov1.Resto
|
||||
}
|
||||
s.dirty = true
|
||||
s.syncMutationLocked()
|
||||
return &keepassgov1.RestoreEntryHistoryResponse{Entry: entryToProto(entry)}, nil
|
||||
return &keepassgov1.RestoreEntryHistoryResponse{Entry: entryToProtoWithModel(visibleModel(s.model), entry)}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListTemplates(ctx context.Context, _ *keepassgov1.ListTemplatesRequest) (*keepassgov1.ListTemplatesResponse, error) {
|
||||
@@ -684,7 +695,7 @@ func (s *Server) ListTemplates(ctx context.Context, _ *keepassgov1.ListTemplates
|
||||
Templates: make([]*keepassgov1.Entry, 0, len(s.model.Templates)),
|
||||
}
|
||||
for _, template := range s.model.Templates {
|
||||
resp.Templates = append(resp.Templates, entryToProto(template))
|
||||
resp.Templates = append(resp.Templates, entryToProtoWithModel(visibleModel(s.model), template))
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
@@ -705,12 +716,12 @@ func (s *Server) UpsertTemplate(ctx context.Context, req *keepassgov1.UpsertTemp
|
||||
return nil, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
|
||||
entry := entryFromProto(req.GetTemplate())
|
||||
entry := entryFromProtoWithModel(visibleModel(s.model), req.GetTemplate())
|
||||
s.model.UpsertTemplate(entry)
|
||||
s.dirty = true
|
||||
s.syncMutationLocked()
|
||||
|
||||
return &keepassgov1.UpsertTemplateResponse{Template: entryToProto(entry)}, nil
|
||||
return &keepassgov1.UpsertTemplateResponse{Template: entryToProtoWithModel(visibleModel(s.model), entry)}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteTemplate(ctx context.Context, req *keepassgov1.DeleteTemplateRequest) (*keepassgov1.DeleteTemplateResponse, error) {
|
||||
@@ -743,7 +754,8 @@ func (s *Server) InstantiateTemplate(ctx context.Context, req *keepassgov1.Insta
|
||||
if _, err := s.authorizeTemplateRequest(ctx, apitokens.OperationListTemplates, req.GetTemplateId()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := s.authorizePathRequest(ctx, apitokens.OperationMutateEntry, req.GetOverrides().GetPath()); err != nil {
|
||||
overridePath := expandClientPath(visibleModel(s.model), req.GetOverrides().GetPath())
|
||||
if _, err := s.authorizePathRequest(ctx, apitokens.OperationMutateEntry, overridePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -754,7 +766,8 @@ func (s *Server) InstantiateTemplate(ctx context.Context, req *keepassgov1.Insta
|
||||
return nil, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
|
||||
entry, err := s.model.InstantiateTemplate(req.GetTemplateId(), entryFromProto(req.GetOverrides()))
|
||||
overrides := entryFromProtoWithModel(visibleModel(s.model), req.GetOverrides())
|
||||
entry, err := s.model.InstantiateTemplate(req.GetTemplateId(), overrides)
|
||||
if err != nil {
|
||||
if errors.Is(err, vault.ErrEntryNotFound) {
|
||||
return nil, status.Error(codes.NotFound, err.Error())
|
||||
@@ -764,7 +777,7 @@ func (s *Server) InstantiateTemplate(ctx context.Context, req *keepassgov1.Insta
|
||||
|
||||
s.dirty = true
|
||||
s.syncMutationLocked()
|
||||
return &keepassgov1.InstantiateTemplateResponse{Entry: entryToProto(entry)}, nil
|
||||
return &keepassgov1.InstantiateTemplateResponse{Entry: entryToProtoWithModel(visibleModel(s.model), entry)}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListAttachments(ctx context.Context, req *keepassgov1.ListAttachmentsRequest) (*keepassgov1.ListAttachmentsResponse, error) {
|
||||
@@ -932,7 +945,7 @@ func (s *Server) GeneratePassword(ctx context.Context, req *keepassgov1.Generate
|
||||
return &keepassgov1.GeneratePasswordResponse{Password: password}, nil
|
||||
}
|
||||
|
||||
func entryToProto(entry vault.Entry) *keepassgov1.Entry {
|
||||
func entryToProtoWithModel(model vault.Model, entry vault.Entry) *keepassgov1.Entry {
|
||||
return &keepassgov1.Entry{
|
||||
Id: entry.ID,
|
||||
Title: entry.Title,
|
||||
@@ -941,12 +954,12 @@ func entryToProto(entry vault.Entry) *keepassgov1.Entry {
|
||||
Url: entry.URL,
|
||||
Notes: entry.Notes,
|
||||
Tags: append([]string(nil), entry.Tags...),
|
||||
Path: append([]string(nil), entry.Path...),
|
||||
Path: collapseInternalPath(model, entry.Path),
|
||||
Fields: maps.Clone(entry.Fields),
|
||||
}
|
||||
}
|
||||
|
||||
func entryFromProto(entry *keepassgov1.Entry) vault.Entry {
|
||||
func entryFromProtoWithModel(model vault.Model, entry *keepassgov1.Entry) vault.Entry {
|
||||
return vault.Entry{
|
||||
ID: entry.GetId(),
|
||||
Title: entry.GetTitle(),
|
||||
@@ -955,11 +968,44 @@ func entryFromProto(entry *keepassgov1.Entry) vault.Entry {
|
||||
URL: entry.GetUrl(),
|
||||
Notes: entry.GetNotes(),
|
||||
Tags: append([]string(nil), entry.GetTags()...),
|
||||
Path: append([]string(nil), entry.GetPath()...),
|
||||
Path: expandClientPath(model, entry.GetPath()),
|
||||
Fields: maps.Clone(entry.GetFields()),
|
||||
}
|
||||
}
|
||||
|
||||
func hiddenVaultRoot(model vault.Model) string {
|
||||
if len(model.EntriesInPath(nil)) != 0 {
|
||||
return ""
|
||||
}
|
||||
groups := model.ChildGroups(nil)
|
||||
if len(groups) != 1 {
|
||||
return ""
|
||||
}
|
||||
return groups[0]
|
||||
}
|
||||
|
||||
func expandClientPath(model vault.Model, path []string) []string {
|
||||
root := hiddenVaultRoot(model)
|
||||
if root == "" {
|
||||
return append([]string(nil), path...)
|
||||
}
|
||||
if len(path) == 0 {
|
||||
return []string{root}
|
||||
}
|
||||
if path[0] == root {
|
||||
return append([]string(nil), path...)
|
||||
}
|
||||
return append([]string{root}, path...)
|
||||
}
|
||||
|
||||
func collapseInternalPath(model vault.Model, path []string) []string {
|
||||
root := hiddenVaultRoot(model)
|
||||
if root == "" || len(path) == 0 || path[0] != root {
|
||||
return append([]string(nil), path...)
|
||||
}
|
||||
return append([]string(nil), path[1:]...)
|
||||
}
|
||||
|
||||
func findEntryByID(model vault.Model, id string) (vault.Entry, error) {
|
||||
for _, entry := range model.Entries {
|
||||
if entry.ID == id {
|
||||
@@ -1103,6 +1149,44 @@ func (s *Server) authorizeEntryRequest(ctx context.Context, op apitokens.Operati
|
||||
return s.authorizeResourceRequest(ctx, token, op, apitokens.Resource{Kind: apitokens.ResourceEntry, EntryID: entry.ID, Path: entry.Path})
|
||||
}
|
||||
|
||||
func (s *Server) authorizeUpsertEntryRequest(ctx context.Context, entry vault.Entry) (apitokens.Token, error) {
|
||||
token, err := s.authenticateRequest(ctx)
|
||||
if err != nil {
|
||||
return apitokens.Token{}, err
|
||||
}
|
||||
model, locked := s.snapshotModel()
|
||||
if locked {
|
||||
return apitokens.Token{}, status.Error(codes.FailedPrecondition, "vault is locked")
|
||||
}
|
||||
existing, err := findEntryByID(model, entry.ID)
|
||||
switch {
|
||||
case err == nil:
|
||||
if _, err := s.authorizeResourceRequest(ctx, token, apitokens.OperationMutateEntry, apitokens.Resource{
|
||||
Kind: apitokens.ResourceEntry,
|
||||
EntryID: existing.ID,
|
||||
Path: existing.Path,
|
||||
}); err != nil {
|
||||
return apitokens.Token{}, err
|
||||
}
|
||||
if !slices.Equal(existing.Path, entry.Path) {
|
||||
if _, err := s.authorizeResourceRequest(ctx, token, apitokens.OperationMutateEntry, apitokens.Resource{
|
||||
Kind: apitokens.ResourceGroup,
|
||||
Path: entry.Path,
|
||||
}); err != nil {
|
||||
return apitokens.Token{}, err
|
||||
}
|
||||
}
|
||||
return token, nil
|
||||
case errors.Is(err, vault.ErrEntryNotFound):
|
||||
return s.authorizeResourceRequest(ctx, token, apitokens.OperationMutateEntry, apitokens.Resource{
|
||||
Kind: apitokens.ResourceGroup,
|
||||
Path: entry.Path,
|
||||
})
|
||||
default:
|
||||
return apitokens.Token{}, status.Errorf(codes.Internal, "lookup existing entry: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) authorizeTemplateRequest(ctx context.Context, op apitokens.Operation, templateID string) (apitokens.Token, error) {
|
||||
token, err := s.authenticateRequest(ctx)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user