package postgres import ( "database/sql" "os" "strconv" "testing" "time" "gardomatic.kleiax.de/internal/auth" "gardomatic.kleiax.de/internal/storage" _ "github.com/lib/pq" ) func TestScopedRolesAndPersistedPermissions(t *testing.T) { dsn := os.Getenv("GARDOMATIC_TEST_DB_DSN") if dsn == "" { t.Skip("set GARDOMATIC_TEST_DB_DSN to run PostgreSQL integration tests") } db, err := sql.Open("postgres", dsn) if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = db.Close() }) if err = db.Ping(); err != nil { t.Fatal(err) } stamp := time.Now().Format("150405.000000000") password := auth.NewPassword([]byte("integration-test-hash")) users := UserModel{DB: db} user, err := users.Insert(storage.User{Name: "Role integration", Email: "roles-" + stamp + "@example.com", Password: *password, Activated: true}) if err != nil { t.Fatal(err) } t.Cleanup(func() { _, _ = db.Exec(`DELETE FROM users WHERE id=$1`, user.ID) }) if user.Role != storage.ApplicationRoleUser || !user.Can(storage.ApplicationPermissionGardensCreate) { t.Fatalf("new user role=%q permissions=%v", user.Role, user.Permissions) } user, err = users.UpdateRole(user.ID, storage.ApplicationRoleAdmin) if err != nil { t.Fatal(err) } if !user.Can(storage.ApplicationPermissionUsersManage) || !user.Can(storage.ApplicationPermissionRolesManage) { t.Fatalf("admin permissions were not loaded: %v", user.Permissions) } var gardenID int if err = db.QueryRow(`INSERT INTO gardens (name) VALUES ($1) RETURNING id`, "Role integration "+stamp).Scan(&gardenID); err != nil { t.Fatal(err) } t.Cleanup(func() { _, _ = db.Exec(`DELETE FROM gardens WHERE id=$1`, gardenID) }) members := GardenMemberModel{DB: db} member, err := members.Insert(storage.GardenMember{GardenID: gardenID, UserID: user.ID, Role: storage.GardenRoleOwner}) if err != nil { t.Fatal(err) } if !member.Can(storage.GardenPermissionGardenDelete) { t.Fatal("owner wildcard permission was not expanded") } if err = (RoleModel{DB: db}).ReplaceGardenOverrides(gardenID, string(storage.GardenRoleOwner), []storage.GardenRolePermissionOverride{{GardenID: gardenID, RoleName: string(storage.GardenRoleOwner), Permission: string(storage.GardenPermissionGardenDelete), Granted: false}}); err != nil { t.Fatal(err) } member, err = members.Get(gardenID, user.ID) if err != nil { t.Fatal(err) } if member.Can(storage.GardenPermissionGardenDelete) || !member.Can(storage.GardenPermissionGardenUpdate) { t.Fatalf("garden override was not applied: %v", member.Permissions) } var otherGardenID int if err = db.QueryRow(`INSERT INTO gardens (name) VALUES ($1) RETURNING id`, "Other role integration "+stamp).Scan(&otherGardenID); err != nil { t.Fatal(err) } t.Cleanup(func() { _, _ = db.Exec(`DELETE FROM gardens WHERE id=$1`, otherGardenID) }) roleName := "garden:" + strconv.Itoa(gardenID) + ":integration" role, err := (RoleModel{DB: db}).Create(storage.Role{ Name: roleName, Scope: storage.RoleScopeGarden, GardenID: &gardenID, Label: "Integration", Permissions: []string{string(storage.GardenPermissionGardenRead)}, }) if err != nil { t.Fatal(err) } if role.GardenID == nil || *role.GardenID != gardenID { t.Fatalf("garden-specific role has garden_id=%v", role.GardenID) } roles, err := (RoleModel{DB: db}).ListForGarden(gardenID) if err != nil { t.Fatal(err) } found := false for _, candidate := range roles { found = found || candidate.Name == roleName } if !found { t.Fatalf("garden-specific role %q is missing", roleName) } if _, err = (RoleModel{DB: db}).GetForGarden(otherGardenID, roleName); err != storage.ErrRecordNotFound { t.Fatalf("role leaked into another garden: %v", err) } }