Commit f60c265
Eric Bower
·
2025-12-26 10:53:17 -0500 EST
parent f1dc8fc
refactor: use `resolveTopic` to determine topic name
2 files changed,
+158,
-146
+80,
-146
| ... | ... | @@ -9,7 +9,6 @@ import ( | |
| 9 | 9 | "log/slog" | |
| 10 | 10 | "slices" | |
| 11 | 11 | "strings" | |
| 12 | - | "text/tabwriter" | |
| 13 | 12 | "time" | |
| 14 | 13 | ||
| 15 | 14 | "github.com/antoniomika/syncmap" |
| ... | ... | @@ -321,42 +320,46 @@ func (handler *CliHandler) pub(cmd *CliCmd, topic string, clientID string) error | |
| 321 | 320 | topic = uuid.NewString() | |
| 322 | 321 | } | |
| 323 | 322 | ||
| 324 | - | var withoutUser string | |
| 325 | - | var name string | |
| 326 | 323 | msgFlag := "" | |
| 327 | - | ||
| 328 | - | if cmd.isAdmin && strings.HasPrefix(topic, "/") { | |
| 329 | - | name = strings.TrimPrefix(topic, "/") | |
| 330 | - | } else { | |
| 331 | - | name = toTopic(cmd.userName, topic) | |
| 332 | - | if *public { | |
| 333 | - | name = toPublicTopic(topic) | |
| 334 | - | msgFlag = "-p " | |
| 335 | - | withoutUser = name | |
| 336 | - | } else { | |
| 337 | - | withoutUser = topic | |
| 338 | - | } | |
| 324 | + | if *public { | |
| 325 | + | msgFlag = "-p " | |
| 339 | 326 | } | |
| 340 | 327 | ||
| 328 | + | // Initial resolution to get the topic name for access storage | |
| 329 | + | initialResult := resolveTopic(TopicResolveInput{ | |
| 330 | + | UserName: cmd.userName, | |
| 331 | + | Topic: topic, | |
| 332 | + | IsAdmin: cmd.isAdmin, | |
| 333 | + | IsPublic: *public, | |
| 334 | + | }) | |
| 335 | + | name := initialResult.Name | |
| 336 | + | ||
| 341 | 337 | var accessListCreator bool | |
| 342 | 338 | _, loaded := handler.Access.LoadOrStore(name, accessList) | |
| 343 | 339 | if !loaded { | |
| 344 | 340 | defer func() { | |
| 345 | 341 | handler.Access.Delete(name) | |
| 346 | 342 | }() | |
| 347 | - | ||
| 348 | 343 | accessListCreator = true | |
| 349 | 344 | } | |
| 350 | 345 | ||
| 351 | - | if accessList, ok := handler.Access.Load(withoutUser); ok && len(accessList) > 0 && !cmd.isAdmin { | |
| 352 | - | if checkAccess(accessList, cmd.userName, cmd.sesh) || accessListCreator { | |
| 353 | - | name = withoutUser | |
| 354 | - | } else if !*public { | |
| 355 | - | name = toTopic(cmd.userName, withoutUser) | |
| 356 | - | } else { | |
| 357 | - | topic = uuid.NewString() | |
| 358 | - | name = toPublicTopic(topic) | |
| 359 | - | } | |
| 346 | + | // Check for existing access list and resolve final topic name | |
| 347 | + | existingAccessList, hasExistingAccess := handler.Access.Load(initialResult.WithoutUser) | |
| 348 | + | result := resolveTopic(TopicResolveInput{ | |
| 349 | + | UserName: cmd.userName, | |
| 350 | + | Topic: topic, | |
| 351 | + | IsAdmin: cmd.isAdmin, | |
| 352 | + | IsPublic: *public, | |
| 353 | + | ExistingAccessList: existingAccessList, | |
| 354 | + | HasExistingAccess: hasExistingAccess, | |
| 355 | + | IsAccessCreator: accessListCreator, | |
| 356 | + | HasUserAccess: checkAccess(existingAccessList, cmd.userName, cmd.sesh), | |
| 357 | + | }) | |
| 358 | + | name = result.Name | |
| 359 | + | ||
| 360 | + | if result.GenerateNewTopic { | |
| 361 | + | topic = uuid.NewString() | |
| 362 | + | name = toPublicTopic(topic) | |
| 360 | 363 | } | |
| 361 | 364 | ||
| 362 | 365 | if !*clean { |
| ... | ... | @@ -524,20 +527,14 @@ func (handler *CliHandler) sub(cmd *CliCmd, topic string, clientID string) error | |
| 524 | 527 | accessList = parseArgList(*access) | |
| 525 | 528 | } | |
| 526 | 529 | ||
| 527 | - | var withoutUser string | |
| 528 | - | var name string | |
| 529 | - | ||
| 530 | - | if cmd.isAdmin && strings.HasPrefix(topic, "/") { | |
| 531 | - | name = strings.TrimPrefix(topic, "/") | |
| 532 | - | } else { | |
| 533 | - | name = toTopic(cmd.userName, topic) | |
| 534 | - | if *public { | |
| 535 | - | name = toPublicTopic(topic) | |
| 536 | - | withoutUser = name | |
| 537 | - | } else { | |
| 538 | - | withoutUser = topic | |
| 539 | - | } | |
| 540 | - | } | |
| 530 | + | // Initial resolution to get the topic name for access storage | |
| 531 | + | initialResult := resolveTopic(TopicResolveInput{ | |
| 532 | + | UserName: cmd.userName, | |
| 533 | + | Topic: topic, | |
| 534 | + | IsAdmin: cmd.isAdmin, | |
| 535 | + | IsPublic: *public, | |
| 536 | + | }) | |
| 537 | + | name := initialResult.Name | |
| 541 | 538 | ||
| 542 | 539 | var accessListCreator bool | |
| 543 | 540 |
| ... | ... | @@ -549,14 +546,22 @@ func (handler *CliHandler) sub(cmd *CliCmd, topic string, clientID string) error | |
| 549 | 546 | accessListCreator = true | |
| 550 | 547 | } | |
| 551 | 548 | ||
| 552 | - | if accessList, ok := handler.Access.Load(withoutUser); ok && len(accessList) > 0 && !cmd.isAdmin { | |
| 553 | - | if checkAccess(accessList, cmd.userName, cmd.sesh) || accessListCreator { | |
| 554 | - | name = withoutUser | |
| 555 | - | } else if !*public { | |
| 556 | - | name = toTopic(cmd.userName, withoutUser) | |
| 557 | - | } else { | |
| 558 | - | return fmt.Errorf("access denied") | |
| 559 | - | } | |
| 549 | + | // Check for existing access list and resolve final topic name | |
| 550 | + | existingAccessList, hasExistingAccess := handler.Access.Load(initialResult.WithoutUser) | |
| 551 | + | result := resolveTopic(TopicResolveInput{ | |
| 552 | + | UserName: cmd.userName, | |
| 553 | + | Topic: topic, | |
| 554 | + | IsAdmin: cmd.isAdmin, | |
| 555 | + | IsPublic: *public, | |
| 556 | + | ExistingAccessList: existingAccessList, | |
| 557 | + | HasExistingAccess: hasExistingAccess, | |
| 558 | + | IsAccessCreator: accessListCreator, | |
| 559 | + | HasUserAccess: checkAccess(existingAccessList, cmd.userName, cmd.sesh), | |
| 560 | + | }) | |
| 561 | + | name = result.Name | |
| 562 | + | ||
| 563 | + | if result.AccessDenied { | |
| 564 | + | return fmt.Errorf("access denied") | |
| 560 | 565 | } | |
| 561 | 566 | ||
| 562 | 567 | err := handler.PubSub.Sub( |
| ... | ... | @@ -612,23 +617,20 @@ func (handler *CliHandler) pipe(cmd *CliCmd, topic string, clientID string) erro | |
| 612 | 617 | topic = uuid.NewString() | |
| 613 | 618 | } | |
| 614 | 619 | ||
| 615 | - | var withoutUser string | |
| 616 | - | var name string | |
| 617 | 620 | flagMsg := "" | |
| 618 | - | ||
| 619 | - | if cmd.isAdmin && strings.HasPrefix(topic, "/") { | |
| 620 | - | name = strings.TrimPrefix(topic, "/") | |
| 621 | - | } else { | |
| 622 | - | name = toTopic(cmd.userName, topic) | |
| 623 | - | if *public { | |
| 624 | - | name = toPublicTopic(topic) | |
| 625 | - | flagMsg = "-p " | |
| 626 | - | withoutUser = name | |
| 627 | - | } else { | |
| 628 | - | withoutUser = topic | |
| 629 | - | } | |
| 621 | + | if *public { | |
| 622 | + | flagMsg = "-p " | |
| 630 | 623 | } | |
| 631 | 624 | ||
| 625 | + | // Initial resolution to get the topic name for access storage | |
| 626 | + | initialResult := resolveTopic(TopicResolveInput{ | |
| 627 | + | UserName: cmd.userName, | |
| 628 | + | Topic: topic, | |
| 629 | + | IsAdmin: cmd.isAdmin, | |
| 630 | + | IsPublic: *public, | |
| 631 | + | }) | |
| 632 | + | name := initialResult.Name | |
| 633 | + | ||
| 632 | 634 | var accessListCreator bool | |
| 633 | 635 | ||
| 634 | 636 | _, loaded := handler.Access.LoadOrStore(name, accessList) |
| ... | ... | @@ -639,15 +641,23 @@ func (handler *CliHandler) pipe(cmd *CliCmd, topic string, clientID string) erro | |
| 639 | 641 | accessListCreator = true | |
| 640 | 642 | } | |
| 641 | 643 | ||
| 642 | - | if accessList, ok := handler.Access.Load(withoutUser); ok && len(accessList) > 0 && !cmd.isAdmin { | |
| 643 | - | if checkAccess(accessList, cmd.userName, cmd.sesh) || accessListCreator { | |
| 644 | - | name = withoutUser | |
| 645 | - | } else if !*public { | |
| 646 | - | name = toTopic(cmd.userName, withoutUser) | |
| 647 | - | } else { | |
| 648 | - | topic = uuid.NewString() | |
| 649 | - | name = toPublicTopic(topic) | |
| 650 | - | } | |
| 644 | + | // Check for existing access list and resolve final topic name | |
| 645 | + | existingAccessList, hasExistingAccess := handler.Access.Load(initialResult.WithoutUser) | |
| 646 | + | result := resolveTopic(TopicResolveInput{ | |
| 647 | + | UserName: cmd.userName, | |
| 648 | + | Topic: topic, | |
| 649 | + | IsAdmin: cmd.isAdmin, | |
| 650 | + | IsPublic: *public, | |
| 651 | + | ExistingAccessList: existingAccessList, | |
| 652 | + | HasExistingAccess: hasExistingAccess, | |
| 653 | + | IsAccessCreator: accessListCreator, | |
| 654 | + | HasUserAccess: checkAccess(existingAccessList, cmd.userName, cmd.sesh), | |
| 655 | + | }) | |
| 656 | + | name = result.Name | |
| 657 | + | ||
| 658 | + | if result.GenerateNewTopic { | |
| 659 | + | topic = uuid.NewString() | |
| 660 | + | name = toPublicTopic(topic) | |
| 651 | 661 | } | |
| 652 | 662 | ||
| 653 | 663 | if isCreator && !*clean { |
| ... | ... | @@ -740,82 +750,6 @@ func flagCheck(cmd *flag.FlagSet, posArg string, cmdArgs []string) bool { | |
| 740 | 750 | return true | |
| 741 | 751 | } | |
| 742 | 752 | ||
| 743 | - | func NewTabWriter(out io.Writer) *tabwriter.Writer { | |
| 744 | - | return tabwriter.NewWriter(out, 0, 0, 1, ' ', tabwriter.TabIndent) | |
| 745 | - | } | |
| 746 | - | ||
| 747 | - | // scope topic to user by prefixing name. | |
| 748 | - | func toTopic(userName, topic string) string { | |
| 749 | - | if strings.HasPrefix(topic, userName+"/") { | |
| 750 | - | return topic | |
| 751 | - | } | |
| 752 | - | return fmt.Sprintf("%s/%s", userName, topic) | |
| 753 | - | } | |
| 754 | - | ||
| 755 | - | func toPublicTopic(topic string) string { | |
| 756 | - | if strings.HasPrefix(topic, "public/") { | |
| 757 | - | return topic | |
| 758 | - | } | |
| 759 | - | return fmt.Sprintf("public/%s", topic) | |
| 760 | - | } | |
| 761 | - | ||
| 762 | - | // TopicResolveInput contains all inputs needed for topic resolution. | |
| 763 | - | type TopicResolveInput struct { | |
| 764 | - | UserName string | |
| 765 | - | Topic string | |
| 766 | - | IsAdmin bool | |
| 767 | - | IsPublic bool | |
| 768 | - | AccessList []string | |
| 769 | - | ExistingAccessList []string | |
| 770 | - | HasExistingAccess bool | |
| 771 | - | IsAccessCreator bool | |
| 772 | - | HasUserAccess bool | |
| 773 | - | } | |
| 774 | - | ||
| 775 | - | // TopicResolveOutput contains the resolved topic name and any error. | |
| 776 | - | type TopicResolveOutput struct { | |
| 777 | - | Name string | |
| 778 | - | WithoutUser string | |
| 779 | - | AccessDenied bool | |
| 780 | - | GenerateNewTopic bool | |
| 781 | - | } | |
| 782 | - | ||
| 783 | - | // resolveTopic determines the final topic name based on user, flags, and access control. | |
| 784 | - | func resolveTopic(input TopicResolveInput) TopicResolveOutput { | |
| 785 | - | var name string | |
| 786 | - | var withoutUser string | |
| 787 | - | ||
| 788 | - | if input.IsAdmin && strings.HasPrefix(input.Topic, "/") { | |
| 789 | - | name = strings.TrimPrefix(input.Topic, "/") | |
| 790 | - | return TopicResolveOutput{Name: name, WithoutUser: withoutUser} | |
| 791 | - | } | |
| 792 | - | ||
| 793 | - | name = toTopic(input.UserName, input.Topic) | |
| 794 | - | if input.IsPublic { | |
| 795 | - | name = toPublicTopic(input.Topic) | |
| 796 | - | withoutUser = name | |
| 797 | - | } else { | |
| 798 | - | withoutUser = input.Topic | |
| 799 | - | } | |
| 800 | - | ||
| 801 | - | if input.HasExistingAccess && len(input.ExistingAccessList) > 0 && !input.IsAdmin { | |
| 802 | - | if input.HasUserAccess || input.IsAccessCreator { | |
| 803 | - | name = withoutUser | |
| 804 | - | } else if !input.IsPublic { | |
| 805 | - | name = toTopic(input.UserName, withoutUser) | |
| 806 | - | } else { | |
| 807 | - | return TopicResolveOutput{ | |
| 808 | - | Name: name, | |
| 809 | - | WithoutUser: withoutUser, | |
| 810 | - | AccessDenied: true, | |
| 811 | - | GenerateNewTopic: true, | |
| 812 | - | } | |
| 813 | - | } | |
| 814 | - | } | |
| 815 | - | ||
| 816 | - | return TopicResolveOutput{Name: name, WithoutUser: withoutUser} | |
| 817 | - | } | |
| 818 | - | ||
| 819 | 753 | func clientInfo(clients []*psub.Client, isAdmin bool, clientType string) string { | |
| 820 | 754 | if len(clients) == 0 { | |
| 821 | 755 | return "" |
+78,
-0
| ... | ... | @@ -0,0 +1,78 @@ | |
| 1 | + | package pipe | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "fmt" | |
| 5 | + | "strings" | |
| 6 | + | ) | |
| 7 | + | ||
| 8 | + | // toTopic scopes a topic to user by prefixing name. | |
| 9 | + | func toTopic(userName, topic string) string { | |
| 10 | + | if strings.HasPrefix(topic, userName+"/") { | |
| 11 | + | return topic | |
| 12 | + | } | |
| 13 | + | return fmt.Sprintf("%s/%s", userName, topic) | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | func toPublicTopic(topic string) string { | |
| 17 | + | if strings.HasPrefix(topic, "public/") { | |
| 18 | + | return topic | |
| 19 | + | } | |
| 20 | + | return fmt.Sprintf("public/%s", topic) | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | // TopicResolveInput contains all inputs needed for topic resolution. | |
| 24 | + | type TopicResolveInput struct { | |
| 25 | + | UserName string | |
| 26 | + | Topic string | |
| 27 | + | IsAdmin bool | |
| 28 | + | IsPublic bool | |
| 29 | + | AccessList []string | |
| 30 | + | ExistingAccessList []string | |
| 31 | + | HasExistingAccess bool | |
| 32 | + | IsAccessCreator bool | |
| 33 | + | HasUserAccess bool | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | // TopicResolveOutput contains the resolved topic name and any error. | |
| 37 | + | type TopicResolveOutput struct { | |
| 38 | + | Name string | |
| 39 | + | WithoutUser string | |
| 40 | + | AccessDenied bool | |
| 41 | + | GenerateNewTopic bool | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | // resolveTopic determines the final topic name based on user, flags, and access control. | |
| 45 | + | func resolveTopic(input TopicResolveInput) TopicResolveOutput { | |
| 46 | + | var name string | |
| 47 | + | var withoutUser string | |
| 48 | + | ||
| 49 | + | if input.IsAdmin && strings.HasPrefix(input.Topic, "/") { | |
| 50 | + | name = strings.TrimPrefix(input.Topic, "/") | |
| 51 | + | return TopicResolveOutput{Name: name, WithoutUser: withoutUser} | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | name = toTopic(input.UserName, input.Topic) | |
| 55 | + | if input.IsPublic { | |
| 56 | + | name = toPublicTopic(input.Topic) | |
| 57 | + | withoutUser = name | |
| 58 | + | } else { | |
| 59 | + | withoutUser = input.Topic | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | if input.HasExistingAccess && len(input.ExistingAccessList) > 0 && !input.IsAdmin { | |
| 63 | + | if input.HasUserAccess || input.IsAccessCreator { | |
| 64 | + | name = withoutUser | |
| 65 | + | } else if !input.IsPublic { | |
| 66 | + | name = toTopic(input.UserName, withoutUser) | |
| 67 | + | } else { | |
| 68 | + | return TopicResolveOutput{ | |
| 69 | + | Name: name, | |
| 70 | + | WithoutUser: withoutUser, | |
| 71 | + | AccessDenied: true, | |
| 72 | + | GenerateNewTopic: true, | |
| 73 | + | } | |
| 74 | + | } | |
| 75 | + | } | |
| 76 | + | ||
| 77 | + | return TopicResolveOutput{Name: name, WithoutUser: withoutUser} | |
| 78 | + | } |