@@ -3,6 +3,8 @@ package thand
33import (
44 "errors"
55 "fmt"
6+ "slices"
7+ "time"
68
79 cloudevents "github.com/cloudevents/sdk-go/v2"
810 "github.com/serverlessworkflow/sdk-go/v3/model"
@@ -21,8 +23,26 @@ func (t *thandTask) executeApprovalsTask(
2123 call * taskModel.ThandTask ,
2224 input any ) (any , error ) {
2325
26+ elevationRequest , err := workflowTask .GetContextAsElevationRequest ()
27+
28+ if err != nil {
29+ logrus .WithError (err ).WithFields (logrus.Fields {
30+ "taskName" : taskName ,
31+ }).Error ("Failed to get elevation request from context" )
32+
33+ return nil , err
34+ }
35+
2436 var notifyReq NotifyRequest
25- common .ConvertInterfaceToInterface (call .With , & notifyReq )
37+ err = common .ConvertInterfaceToInterface (call .With , & notifyReq )
38+
39+ if err != nil {
40+ logrus .WithError (err ).WithFields (logrus.Fields {
41+ "taskName" : taskName ,
42+ }).Error ("Failed to parse notification request" )
43+
44+ return nil , err
45+ }
2646
2747 if ! notifyReq .IsValid () {
2848 return nil , errors .New ("invalid notification request" )
@@ -82,6 +102,10 @@ func (t *thandTask) executeApprovalsTask(
82102 return nil , err
83103 }
84104
105+ defaultFlowState := model.FlowDirective {
106+ Value : taskName , // loop back to await more approvals
107+ }
108+
85109 // Set the context to hold all the approvals
86110 /*
87111 output:
@@ -97,37 +121,76 @@ func (t *thandTask) executeApprovalsTask(
97121
98122 workflowContext := workflowTask .GetContextAsMap ()
99123
100- approvals , ok := workflowContext ["approvals" ].([ ]any )
124+ approvals , ok := workflowContext ["approvals" ].(map [ string ]any )
101125
102126 if ! ok {
103- approvals = [ ]any {}
127+ approvals = map [ string ]any {}
104128 }
105129
106130 var approvalData map [string ]any
107131
108132 if approvalEvent , ok := approval .(* cloudevents.Event ); ok {
109133
110134 approvalEvent .DataAs (& approvalData )
135+ extensions := approvalEvent .Extensions ()
136+
137+ userIdentity , userExists := extensions [models .VarsContextUser ].(string )
138+
139+ if ! userExists {
140+ logrus .Warn ("Approval event missing user extension" )
141+ return & defaultFlowState , nil
142+ }
143+
144+ // Check if self-approval is disabled and the approver is the requester or one of the elevated identities
145+ if ! notifyReq .SelfApprove {
146+ requesterIdentity := elevationRequest .User .GetIdentity ()
147+
148+ // Check if approver is the requester
149+ if userIdentity == requesterIdentity {
150+ logrus .WithFields (logrus.Fields {
151+ "taskName" : taskName ,
152+ "userIdentity" : userIdentity ,
153+ "requesterIdentity" : requesterIdentity ,
154+ }).Warn ("Self-approval is disabled; ignoring approval from requester" )
155+
156+ // Return to the default flow state to await more approvals
157+ return & defaultFlowState , nil
158+ }
159+
160+ // Check if approver is one of the identities being elevated
161+ if slices .Contains (elevationRequest .Identities , userIdentity ) {
162+ logrus .WithFields (logrus.Fields {
163+ "taskName" : taskName ,
164+ "userIdentity" : userIdentity ,
165+ }).Warn ("Self-approval is disabled; ignoring approval from identity being elevated" )
166+
167+ // Return to the default flow state to await more approvals
168+ return & defaultFlowState , nil
169+ }
170+ }
111171
112172 if approved , exists := approvalData ["approved" ]; exists {
113- approvals = append (approvals , map [string ]any {
114- "approved" : approved ,
115- })
173+ approvals [userIdentity ] = map [string ]any {
174+ "approved" : approved ,
175+ "timestamp" : time .Now ().UTC ().Format (time .RFC3339 ),
176+ }
116177 }
117178 }
118179
119180 workflowTask .SetContextKeyValue ("approvals" , approvals )
120181
121182 /*
122183 # If anyone rejects then reject the entire request
123- # otherwise if there is more than one approval then
124- # authorize
184+ # otherwise if the required number of approvals is met then authorize
185+ # Approvals are stored as a map[identity]approval_data structure
125186 - case1:
126- when: any($context.approvals[]; .approved == false)
187+ when: any($context.approvals | to_entries []; .value .approved == false)
127188 then: denied
128189 - case2:
129- when: '[$context.approvals[] | select(.approved == true)] | length >= 1 '
190+ when: '[$context.approvals | to_entries [] | select(.value. approved == true)] | length >= N '
130191 then: authorize
192+ - default:
193+ then: loop back to task to await more approvals
131194 */
132195
133196 approvedState , foundApprovedState := call .On .GetString ("approved" )
@@ -138,7 +201,43 @@ func (t *thandTask) executeApprovalsTask(
138201 }
139202
140203 // Create the switch task to handle approval or rejection
141- flowDirective , err := runner .SwitchTaskHandler (
204+ flowDirective , err := t .evaluateApprovalSwitch (
205+ workflowTask ,
206+ taskName ,
207+ approvals ,
208+ notifyReq .Approvals ,
209+ approvedState ,
210+ deniedState ,
211+ )
212+
213+ if err != nil {
214+ logrus .WithError (err ).WithFields (logrus.Fields {
215+ "taskName" : taskName ,
216+ }).Error ("Failed to execute switch task for approval logic" )
217+
218+ return nil , err
219+ }
220+
221+ logrus .WithFields (logrus.Fields {
222+ "taskName" : taskName ,
223+ "flowDirective" : flowDirective .Value ,
224+ }).Info ("Completed Thand approvals task" )
225+
226+ return flowDirective , nil
227+ }
228+
229+ // evaluateApprovalSwitch evaluates the approval logic using a switch task
230+ // to determine if the request should be approved, denied, or loop back for more approvals
231+ func (t * thandTask ) evaluateApprovalSwitch (
232+ workflowTask * models.WorkflowTask ,
233+ taskName string ,
234+ approvals map [string ]any ,
235+ requiredApprovals int ,
236+ approvedState string ,
237+ deniedState string ,
238+ ) (* model.FlowDirective , error ) {
239+
240+ return runner .SwitchTaskHandler (
142241 workflowTask ,
143242 map [string ]any {
144243 "approvals" : approvals ,
@@ -149,7 +248,7 @@ func (t *thandTask) executeApprovalsTask(
149248 {
150249 "case1" : model.SwitchCase {
151250 When : & model.RuntimeExpression {
152- Value : "any($context.approvals[]; .approved == false)" ,
251+ Value : "any($context.approvals | to_entries []; .value .approved == false)" ,
153252 },
154253 Then : & model.FlowDirective {
155254 Value : deniedState , // go to denied state
@@ -159,7 +258,7 @@ func (t *thandTask) executeApprovalsTask(
159258 {
160259 "case2" : model.SwitchCase {
161260 When : & model.RuntimeExpression {
162- Value : fmt .Sprintf ("[$context.approvals[] | select(.approved == true)] | length >= %d" , notifyReq . Approvals ),
261+ Value : fmt .Sprintf ("[$context.approvals | to_entries [] | select(.value. approved == true)] | length >= %d" , requiredApprovals ),
163262 },
164263 Then : & model.FlowDirective {
165264 Value : approvedState , // proceed to the next state
@@ -176,19 +275,4 @@ func (t *thandTask) executeApprovalsTask(
176275 },
177276 },
178277 })
179-
180- if err != nil {
181- logrus .WithError (err ).WithFields (logrus.Fields {
182- "taskName" : taskName ,
183- }).Error ("Failed to execute switch task for approval logic" )
184-
185- return nil , err
186- }
187-
188- logrus .WithFields (logrus.Fields {
189- "taskName" : taskName ,
190- "flowDirective" : flowDirective .Value ,
191- }).Info ("Completed Thand approvals task" )
192-
193- return flowDirective , nil
194278}
0 commit comments