| 1 | package policy |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "maps" |
| 6 | |
| 7 | "github.com/gosuda/portal-tunnel/v2/utils" |
| 8 | ) |
| 9 | |
| 10 | type Mode string |
| 11 | |
| 12 | const ( |
| 13 | ModeAuto Mode = "auto" |
| 14 | ModeManual Mode = "manual" |
| 15 | ) |
| 16 | |
| 17 | type Approver struct { |
| 18 | state *utils.Snapshot[approvalState] |
| 19 | } |
| 20 | |
| 21 | type approvalState struct { |
| 22 | approvedKeys map[string]struct{} |
| 23 | deniedKeys map[string]struct{} |
| 24 | approvalMode Mode |
| 25 | } |
| 26 | |
| 27 | func newApprovalState() approvalState { |
| 28 | return approvalState{ |
| 29 | approvalMode: ModeAuto, |
| 30 | approvedKeys: make(map[string]struct{}), |
| 31 | deniedKeys: make(map[string]struct{}), |
| 32 | } |
| 33 | } |
| 34 | |
| 35 | func (state approvalState) snapshot() approvalState { |
| 36 | state.approvedKeys = maps.Clone(state.approvedKeys) |
| 37 | state.deniedKeys = maps.Clone(state.deniedKeys) |
| 38 | return state |
| 39 | } |
| 40 | |
| 41 | func NewApprover() *Approver { |
| 42 | return &Approver{ |
| 43 | state: utils.NewSnapshot(newApprovalState(), approvalState.snapshot), |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | func (a *Approver) current() approvalState { |
| 48 | if a == nil || a.state == nil { |
| 49 | return newApprovalState() |
| 50 | } |
| 51 | return a.state.Load() |
| 52 | } |
| 53 | |
| 54 | func (a *Approver) Mode() Mode { |
| 55 | return a.current().approvalMode |
| 56 | } |
| 57 | |
| 58 | func (a *Approver) SetMode(mode Mode) error { |
| 59 | if mode != ModeAuto && mode != ModeManual { |
| 60 | return fmt.Errorf("invalid approval mode: %q", mode) |
| 61 | } |
| 62 | if a == nil || a.state == nil { |
| 63 | return nil |
| 64 | } |
| 65 | a.state.UpdateCopy(func(state *approvalState) { |
| 66 | state.approvalMode = mode |
| 67 | }) |
| 68 | return nil |
| 69 | } |
| 70 | |
| 71 | func (a *Approver) IsApproved(key string) bool { |
| 72 | _, ok := a.current().approvedKeys[key] |
| 73 | return ok |
| 74 | } |
| 75 | |
| 76 | func (a *Approver) Approve(key string) { |
| 77 | if a == nil || a.state == nil { |
| 78 | return |
| 79 | } |
| 80 | a.state.UpdateCopy(func(state *approvalState) { |
| 81 | if state.approvedKeys == nil { |
| 82 | state.approvedKeys = make(map[string]struct{}) |
| 83 | } |
| 84 | state.approvedKeys[key] = struct{}{} |
| 85 | delete(state.deniedKeys, key) |
| 86 | }) |
| 87 | } |
| 88 | |
| 89 | func (a *Approver) Revoke(key string) { |
| 90 | if a == nil || a.state == nil { |
| 91 | return |
| 92 | } |
| 93 | a.state.UpdateCopy(func(state *approvalState) { |
| 94 | delete(state.approvedKeys, key) |
| 95 | }) |
| 96 | } |
| 97 | |
| 98 | func (a *Approver) ApprovedKeys() []string { |
| 99 | approvedKeys := a.current().approvedKeys |
| 100 | out := make([]string, 0, len(approvedKeys)) |
| 101 | for key := range approvedKeys { |
| 102 | out = append(out, key) |
| 103 | } |
| 104 | return out |
| 105 | } |
| 106 | |
| 107 | func (a *Approver) IsDenied(key string) bool { |
| 108 | _, ok := a.current().deniedKeys[key] |
| 109 | return ok |
| 110 | } |
| 111 | |
| 112 | func (a *Approver) Deny(key string) { |
| 113 | if a == nil || a.state == nil { |
| 114 | return |
| 115 | } |
| 116 | a.state.UpdateCopy(func(state *approvalState) { |
| 117 | if state.deniedKeys == nil { |
| 118 | state.deniedKeys = make(map[string]struct{}) |
| 119 | } |
| 120 | state.deniedKeys[key] = struct{}{} |
| 121 | delete(state.approvedKeys, key) |
| 122 | }) |
| 123 | } |
| 124 | |
| 125 | func (a *Approver) Undeny(key string) { |
| 126 | if a == nil || a.state == nil { |
| 127 | return |
| 128 | } |
| 129 | a.state.UpdateCopy(func(state *approvalState) { |
| 130 | delete(state.deniedKeys, key) |
| 131 | }) |
| 132 | } |
| 133 | |
| 134 | func (a *Approver) DeniedKeys() []string { |
| 135 | deniedKeys := a.current().deniedKeys |
| 136 | out := make([]string, 0, len(deniedKeys)) |
| 137 | for key := range deniedKeys { |
| 138 | out = append(out, key) |
| 139 | } |
| 140 | return out |
| 141 | } |
| 142 | |
| 143 | func (a *Approver) SetDecisions(approvedKeys, deniedKeys []string) { |
| 144 | if a == nil { |
| 145 | return |
| 146 | } |
| 147 | |
| 148 | approved := make(map[string]struct{}, len(approvedKeys)) |
| 149 | for _, key := range approvedKeys { |
| 150 | if key == "" { |
| 151 | continue |
| 152 | } |
| 153 | approved[key] = struct{}{} |
| 154 | } |
| 155 | |
| 156 | denied := make(map[string]struct{}, len(deniedKeys)) |
| 157 | for _, key := range deniedKeys { |
| 158 | if key == "" { |
| 159 | continue |
| 160 | } |
| 161 | delete(approved, key) |
| 162 | denied[key] = struct{}{} |
| 163 | } |
| 164 | |
| 165 | if a.state == nil { |
| 166 | return |
| 167 | } |
| 168 | a.state.Update(func(state approvalState) approvalState { |
| 169 | state.approvedKeys = approved |
| 170 | state.deniedKeys = denied |
| 171 | return state |
| 172 | }) |
| 173 | } |