feat(admin): add device credential isolation
This commit is contained in:
@@ -16,8 +16,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"cmbuyer/admin/internal/deviceauth"
|
||||
core "cmbuyer/admin/internal/evidence"
|
||||
)
|
||||
|
||||
@@ -152,7 +152,7 @@ func (store *Store) Discard(staged core.StagedFile) {
|
||||
}
|
||||
}
|
||||
|
||||
func (store *Store) Commit(ctx context.Context, principal core.DevicePrincipal, metadata core.UploadMetadata, staged core.StagedFile) (core.Asset, bool, error) {
|
||||
func (store *Store) Commit(ctx context.Context, principal deviceauth.Principal, metadata core.UploadMetadata, staged core.StagedFile) (core.Asset, bool, error) {
|
||||
if !store.isStagedPath(staged.Path) || !validPrincipal(principal) || !validMetadata(metadata) || metadata.SHA256 != staged.SHA256 || staged.ContentType != core.PNGContentType || staged.ByteSize < 1 || staged.ByteSize > core.MaxFileBytes || staged.Width < 1 || staged.Height < 1 || staged.Width > core.MaxImageSide || staged.Height > core.MaxImageSide || int64(staged.Width)*int64(staged.Height) > core.MaxImagePixels {
|
||||
store.Discard(staged)
|
||||
return core.Asset{}, false, core.ErrInvalid
|
||||
@@ -447,16 +447,8 @@ func validMetadata(metadata core.UploadMetadata) bool {
|
||||
return validUUID(metadata.UploadKey) && validUUID(metadata.TaskID) && validUUID(metadata.AttemptID) && metadata.Kind == core.KindSKUPanelGate1 && metadata.PrivacyTier == core.PrivacyInternalRaw && validSHA256(metadata.SHA256) && !metadata.CapturedAt.IsZero() && metadata.CapturedAt.Location() == time.UTC
|
||||
}
|
||||
|
||||
func validPrincipal(principal core.DevicePrincipal) bool {
|
||||
if principal.ID == "" || strings.TrimSpace(principal.ID) != principal.ID || len(principal.ID) > 128 {
|
||||
return false
|
||||
}
|
||||
for _, character := range principal.ID {
|
||||
if unicode.IsControl(character) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
func validPrincipal(principal deviceauth.Principal) bool {
|
||||
return deviceauth.ValidDeviceID(principal.ID)
|
||||
}
|
||||
|
||||
func validSHA256(value string) bool {
|
||||
@@ -548,6 +540,6 @@ func scanAsset(row rowScanner) (core.Asset, bool, error) {
|
||||
return asset, true, nil
|
||||
}
|
||||
|
||||
func sameUpload(asset core.Asset, principal core.DevicePrincipal, metadata core.UploadMetadata, staged core.StagedFile) bool {
|
||||
func sameUpload(asset core.Asset, principal deviceauth.Principal, metadata core.UploadMetadata, staged core.StagedFile) bool {
|
||||
return asset.TaskID == metadata.TaskID && asset.AttemptID == metadata.AttemptID && asset.Kind == metadata.Kind && asset.PrivacyTier == metadata.PrivacyTier && asset.SHA256 == metadata.SHA256 && asset.ByteSize == staged.ByteSize && asset.ContentType == staged.ContentType && asset.Width == staged.Width && asset.Height == staged.Height && asset.UploadedByDeviceID == principal.ID && asset.CapturedAt.Equal(metadata.CapturedAt)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cmbuyer/admin/internal/deviceauth"
|
||||
core "cmbuyer/admin/internal/evidence"
|
||||
"cmbuyer/admin/internal/migrations"
|
||||
"cmbuyer/admin/internal/storage/sqlite"
|
||||
@@ -29,6 +30,7 @@ const (
|
||||
testAuthID = "23c9f507-7473-4fa6-8d71-8786c34c6301"
|
||||
testAttemptID = "33c9f507-7473-4fa6-8d71-8786c34c6301"
|
||||
testUploadKey = "43c9f507-7473-4fa6-8d71-8786c34c6301"
|
||||
testDeviceID = "53c9f507-7473-4fa6-8d71-8786c34c6301"
|
||||
)
|
||||
|
||||
func TestStageCommitReplayAndOpen(t *testing.T) {
|
||||
@@ -37,7 +39,7 @@ func TestStageCommitReplayAndOpen(t *testing.T) {
|
||||
pngBytes := makePNG(t, 8, 6)
|
||||
hash := sha256Hex(pngBytes)
|
||||
metadata := core.UploadMetadata{UploadKey: testUploadKey, TaskID: testTaskID, AttemptID: testAttemptID, Kind: core.KindSKUPanelGate1, PrivacyTier: core.PrivacyInternalRaw, SHA256: hash, CapturedAt: time.Date(2026, 8, 4, 1, 2, 3, 0, time.UTC)}
|
||||
principal := core.DevicePrincipal{ID: "device-one"}
|
||||
principal := deviceauth.Principal{ID: testDeviceID}
|
||||
|
||||
staged, err := store.Stage(bytes.NewReader(pngBytes), core.PNGContentType)
|
||||
if err != nil {
|
||||
@@ -101,7 +103,7 @@ func TestConcurrentReplayCreatesOneAsset(t *testing.T) {
|
||||
for index := range staged {
|
||||
go func(index int) {
|
||||
defer wait.Done()
|
||||
assets[index], replays[index], errorsSeen[index] = store.Commit(context.Background(), core.DevicePrincipal{ID: "device"}, metadata, staged[index])
|
||||
assets[index], replays[index], errorsSeen[index] = store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, metadata, staged[index])
|
||||
}(index)
|
||||
}
|
||||
wait.Wait()
|
||||
@@ -206,7 +208,7 @@ func TestCommitSyncsShardAndRenameBeforeDatabaseWrite(t *testing.T) {
|
||||
return os.Rename(oldPath, newPath)
|
||||
}
|
||||
|
||||
if _, replayed, err := store.Commit(context.Background(), core.DevicePrincipal{ID: "device"}, metadata, staged); err != nil || replayed {
|
||||
if _, replayed, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, metadata, staged); err != nil || replayed {
|
||||
t.Fatalf("Commit = replayed %t, err %v", replayed, err)
|
||||
}
|
||||
if got, want := strings.Join(events, ","), "sync-root,sync-root,sync-file,rename,sync-shard"; got != want {
|
||||
@@ -241,7 +243,7 @@ func TestCommitDirectorySyncFailuresNeverWriteDatabase(t *testing.T) {
|
||||
return syncDirectory(path)
|
||||
}
|
||||
|
||||
if _, _, err := store.Commit(context.Background(), core.DevicePrincipal{ID: "device"}, testMetadata(hash), staged); !errors.Is(err, injected) {
|
||||
if _, _, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, testMetadata(hash), staged); !errors.Is(err, injected) {
|
||||
t.Fatalf("Commit error = %v, want injected sync failure", err)
|
||||
}
|
||||
if calls != failAt {
|
||||
@@ -275,7 +277,7 @@ func TestCommitRetriesShardParentSyncAfterPriorFailure(t *testing.T) {
|
||||
}
|
||||
injected := errors.New("injected first shard parent sync failure")
|
||||
store.syncDirectory = func(string) error { return injected }
|
||||
if _, _, err := store.Commit(context.Background(), core.DevicePrincipal{ID: "device"}, testMetadata(hash), firstStage); !errors.Is(err, injected) {
|
||||
if _, _, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, testMetadata(hash), firstStage); !errors.Is(err, injected) {
|
||||
t.Fatalf("first Commit error = %v", err)
|
||||
}
|
||||
if info, err := os.Stat(filepath.Dir(finalPath)); err != nil || !info.IsDir() {
|
||||
@@ -292,7 +294,7 @@ func TestCommitRetriesShardParentSyncAfterPriorFailure(t *testing.T) {
|
||||
paths = append(paths, path)
|
||||
return syncDirectory(path)
|
||||
}
|
||||
if _, replayed, err := store.Commit(context.Background(), core.DevicePrincipal{ID: "device"}, testMetadata(hash), secondStage); err != nil || replayed {
|
||||
if _, replayed, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, testMetadata(hash), secondStage); err != nil || replayed {
|
||||
t.Fatalf("retry Commit = replayed %t, err %v", replayed, err)
|
||||
}
|
||||
if len(paths) != 2 || paths[0] != store.root || paths[1] != filepath.Dir(finalPath) {
|
||||
@@ -323,7 +325,7 @@ func TestCommitPublicationFailuresCleanTempAndNeverWriteDatabase(t *testing.T) {
|
||||
} else {
|
||||
store.renameFile = func(string, string) error { return injected }
|
||||
}
|
||||
if _, _, err := store.Commit(context.Background(), core.DevicePrincipal{ID: "device"}, testMetadata(hash), staged); !errors.Is(err, injected) {
|
||||
if _, _, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, testMetadata(hash), staged); !errors.Is(err, injected) {
|
||||
t.Fatalf("Commit error = %v, want injected publication failure", err)
|
||||
}
|
||||
if _, err := os.Stat(finalPath); !errors.Is(err, os.ErrNotExist) {
|
||||
@@ -377,7 +379,7 @@ func TestCommitDatabaseFailuresAfterDurableRenameLeaveOnlyOrphan(t *testing.T) {
|
||||
return syncDirectory(path)
|
||||
}
|
||||
|
||||
if _, _, err := store.Commit(context.Background(), core.DevicePrincipal{ID: "device"}, testMetadata(hash), staged); err == nil {
|
||||
if _, _, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, testMetadata(hash), staged); err == nil {
|
||||
t.Fatal("Commit unexpectedly succeeded")
|
||||
}
|
||||
if syncCalls != 3 {
|
||||
@@ -436,7 +438,7 @@ func TestCommitRequiresAttemptOwnedByTaskAndLowercaseHash(t *testing.T) {
|
||||
}
|
||||
metadata := base
|
||||
mutate(&metadata)
|
||||
if _, _, err := store.Commit(context.Background(), core.DevicePrincipal{ID: "device"}, metadata, staged); !errors.Is(err, core.ErrInvalid) {
|
||||
if _, _, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, metadata, staged); !errors.Is(err, core.ErrInvalid) {
|
||||
t.Fatalf("Commit error = %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user