@@ -269,6 +269,323 @@ app.register_authorization(authorization_instance)
269
269
app .register_graphql_authorization (graphql_authorization_instance )
270
270
```
271
271
272
+ ## Authorization and Workflows
273
+
274
+ !!! Warning
275
+ Role -based access control for workflows is currently in beta .
276
+ Initial support has been added to the backend , but the feature is not fully communicated through the UI yet .
277
+
278
+ Certain `orchestrator -core ` decorators accept authorization callbacks of type `type Authorizer = Callable [OIDCUserModel , bool ]`, which return True when the input user is authorized , otherwise False .
279
+
280
+ A table (below ) is available for comparing possible configuration states with the policy that will be enforced .
281
+
282
+ ### `@workflow `
283
+ The `@workflow ` decorator accepts the optional parameters `auth : Authorizer ` and `retry_auth : Authorizer `.
284
+
285
+ `auth ` will be used to determine the authorization of a user to start the workflow .
286
+ If `auth ` is omitted , the workflow is authorized for any logged in user .
287
+
288
+ `retry_auth ` will be used to determine the authorization of a user to start , resume , or retry the workflow from a failed step .
289
+ If `retry_auth ` is omitted , then `auth ` is used to authorize .
290
+
291
+ (This does not percolate past an `@inputstep ` that specifies `resume_auth ` or `retry_auth `.)
292
+
293
+ Examples :
294
+
295
+ * `auth =None , retry_auth =None `: any user may run the workflow .
296
+ * `auth =A , retry_auth =B `: users authorized by A may start the workflow . Users authorized by B may retry on failure .
297
+ * Example : starting the workflow is a decision that must be made by a product owner . Retrying can be made by an on -call member of the operations team .
298
+ * `auth =None , retry_auth =B `: any user can start the workflow , but only users authorized by B may retry on failure .
299
+
300
+ ### `@inputstep `
301
+ The `@inputstep ` decorator accepts the optional parameters `resume_auth : Authorizer ` and `retry_auth : Authorizer `.
302
+
303
+ `resume_auth ` will be used to determine the authorization of a user to resume the workflow when suspended at this inputstep .
304
+ If `resume_auth ` is omitted , then the workflow 's `auth ` will be used .
305
+
306
+ `retry_auth ` will be used to determine the authorization of a user to retry the workflow from a failed step following the inputstep .
307
+ If `retry_auth ` is omitted , then `resume_auth ` is used to authorize retries .
308
+ If `resume_auth ` is also omitted , then the workflow ’s `retry_auth ` is checked , and then the workflow ’s `auth `.
309
+
310
+ In summary :
311
+
312
+ * A workflow establishes `auth ` for starting , resuming , or retrying .
313
+ * The workflow can also establish `retry_auth `, which will override `auth ` for retries .
314
+ * An inputstep can override the existing `auth ` with `resume_auth ` and the existing `retry_auth ` with its own `retry_auth `.
315
+ * Subsequent inputsteps can do the same , but any None will not overwrite a previous not -None .
316
+
317
+ ### Policy resolutions
318
+ Below is an exhaustive table of how policies (implemented as callbacks `A `, `B `, `C `, and `D `)
319
+ are prioritized in different workflow and inputstep configurations .
320
+
321
+ <table >
322
+ <thead >
323
+ <tr >
324
+ <th colspan = 4 >Configuration </th >
325
+ <th colspan = 4 >Enforcement </th >
326
+ <th >Notes </th >
327
+ </tr >
328
+ <tr >
329
+ <th colspan = 2 >@workflow </th >
330
+ <th colspan = 2 >@inputstep </th >
331
+ <th colspan = 2 >before @inputstep </th >
332
+ <th colspan = 2 >@inputstep and after </th >
333
+ <th ></th >
334
+ </tr >
335
+ <tr >
336
+ <th >auth </th >
337
+ <th >retry_auth </th >
338
+ <th >resume_auth </th >
339
+ <th >retry_auth </th >
340
+ <th >start </th >
341
+ <th >retry </th >
342
+ <th >resume </th >
343
+ <th >retry </th >
344
+ <th ></th >
345
+ </tr >
346
+ </thead >
347
+ <tbody >
348
+ <tr >
349
+ <td >None </td >
350
+ <td >None </td >
351
+ <td >None </td >
352
+ <td >None </td >
353
+ <td >Anyone </td >
354
+ <td >Anyone </td >
355
+ <td >Anyone </td >
356
+ <td >Anyone </td >
357
+ <td >Default </td >
358
+ </tr >
359
+ <tr >
360
+ <td >A </td >
361
+ <td >None </td >
362
+ <td >None </td >
363
+ <td >None </td >
364
+ <td >A </td >
365
+ <td >A </td >
366
+ <td >A </td >
367
+ <td >A </td >
368
+ <td >Broadly restrict the workflow to a specific authorizer .</td >
369
+ </tr >
370
+ <tr >
371
+ <td >None </td >
372
+ <td >B </td >
373
+ <td >None </td >
374
+ <td >None </td >
375
+ <td >Anyone </td >
376
+ <td >B </td >
377
+ <td >Anyone </td >
378
+ <td >B </td >
379
+ <td >original retry_auth is maintained if nothing supercedes it . Weird choice , but this provides a "we specifically want to limit retries " route .</td >
380
+ </tr >
381
+ <tr >
382
+ <td >A </td >
383
+ <td >B </td >
384
+ <td >None </td >
385
+ <td >None </td >
386
+ <td >A </td >
387
+ <td >B </td >
388
+ <td >A </td >
389
+ <td >B </td >
390
+ <td >Workflow -level auth and retry . Allows A or B to be tighter or distinct , as needed .</td >
391
+ </tr >
392
+ <tr >
393
+ <td >None </td >
394
+ <td >None </td >
395
+ <td >C </td >
396
+ <td >None </td >
397
+ <td >Anyone </td >
398
+ <td >Anyone </td >
399
+ <td >C </td >
400
+ <td >C </td >
401
+ <td >Anyone can start this workflow , but only C can continue it .</td >
402
+ </tr >
403
+ <tr >
404
+ <td >A </td >
405
+ <td >None </td >
406
+ <td >C </td >
407
+ <td >None </td >
408
+ <td >A </td >
409
+ <td >A </td >
410
+ <td >C </td >
411
+ <td >C </td >
412
+ <td >Subsequent retries use C , not A ! Override with retry_auth =A if desired .</td >
413
+ </tr >
414
+ <tr >
415
+ <td >None </td >
416
+ <td >B </td >
417
+ <td >C </td >
418
+ <td >None </td >
419
+ <td >Anyone </td >
420
+ <td >B </td >
421
+ <td >C </td >
422
+ <td >C </td >
423
+ <td >Subsequent retries use C , not B ! Override with retry_auth =B if desired .</td >
424
+ </tr >
425
+ <tr >
426
+ <td >A </td >
427
+ <td >B </td >
428
+ <td >C </td >
429
+ <td >None </td >
430
+ <td >A </td >
431
+ <td >B </td >
432
+ <td >C </td >
433
+ <td >C </td >
434
+ <td >Simple override initial settings with inputstep resume_auth .</td >
435
+ </tr >
436
+ <tr >
437
+ <td >None </td >
438
+ <td >None </td >
439
+ <td >None </td >
440
+ <td >D </td >
441
+ <td >Anyone </td >
442
+ <td >Anyone </td >
443
+ <td >Anyone </td >
444
+ <td >D </td >
445
+ <td >Anyone can start or retry or resume , but limit retries to D once inputstep is reached .</td >
446
+ </tr >
447
+ <tr >
448
+ <td >A </td >
449
+ <td >None </td >
450
+ <td >None </td >
451
+ <td >D </td >
452
+ <td >A </td >
453
+ <td >A </td >
454
+ <td >A </td >
455
+ <td >D </td >
456
+ <td >A can start or retry or resume , but limit retries to D once inputstep is reached .</td >
457
+ </tr >
458
+ <tr >
459
+ <td >None </td >
460
+ <td >B </td >
461
+ <td >None </td >
462
+ <td >D </td >
463
+ <td >Anyone </td >
464
+ <td >B </td >
465
+ <td >Anyone </td >
466
+ <td >D </td >
467
+ <td >Anyone can start or resume , but only B can retry . After inputstep , only D can retry .</td >
468
+ </tr >
469
+ <tr >
470
+ <td >A </td >
471
+ <td >B </td >
472
+ <td >None </td >
473
+ <td >D </td >
474
+ <td >A </td >
475
+ <td >B </td >
476
+ <td >A </td >
477
+ <td >D </td >
478
+ <td >A can start or resume , but only B can retry . After inputstep , only D can retry .</td >
479
+ </tr >
480
+ <tr >
481
+ <td >None </td >
482
+ <td >None </td >
483
+ <td >C </td >
484
+ <td >D </td >
485
+ <td >Anyone </td >
486
+ <td >Anyone </td >
487
+ <td >C </td >
488
+ <td >D </td >
489
+ <td >Anyone can start , but only C can resume and only D can retry after the resume .</td >
490
+ </tr >
491
+ <tr >
492
+ <td >A </td >
493
+ <td >None </td >
494
+ <td >C </td >
495
+ <td >D </td >
496
+ <td >A </td >
497
+ <td >A </td >
498
+ <td >C </td >
499
+ <td >D </td >
500
+ <td ></td >
501
+ </tr >
502
+ <tr >
503
+ <td >None </td >
504
+ <td >B </td >
505
+ <td >C </td >
506
+ <td >D </td >
507
+ <td >Anyone </td >
508
+ <td >B </td >
509
+ <td >C </td >
510
+ <td >D </td >
511
+ <td ></td >
512
+ </tr >
513
+ <tr >
514
+ <td >A </td >
515
+ <td >B </td >
516
+ <td >C </td >
517
+ <td >D </td >
518
+ <td >A </td >
519
+ <td >B </td >
520
+ <td >C </td >
521
+ <td >D </td >
522
+ <td ></td >
523
+ </tr >
524
+ </tbody >
525
+ </table >
526
+
527
+ ### Examples
528
+ Assume we have the following function that can be used to create callbacks :
529
+
530
+ ```python
531
+ def allow_roles (*roles ) -> Callable [OIDCUserModel |None , bool ]:
532
+ def f (user : OIDCUserModel ) -> bool :
533
+ if is_admin (user ): # Relative to your authorization provider
534
+ return True
535
+ for role in roles :
536
+ if has_role (user , role ): # Relative to your authorization provider
537
+ return True
538
+ return False
539
+
540
+ return f
541
+ ```
542
+
543
+ We can now construct a variety of authorization policies .
544
+
545
+ #### Rubber Stamp Model
546
+ !!!example
547
+ Suppose we have a workflow W that needs to pause on inputstep `approval ` for approval from finance . Ops (and only ops ) should be able to start the workflow and retry any failed steps . Finance (and only finance ) should be able to resume at the input step .
548
+
549
+ ```python
550
+ @workflow ("An expensive workflow ", auth =allow_roles ("ops "))
551
+ def W (...):
552
+ return begin >> A >> ... >> notify_finance >> approval >> ... >> Z
553
+
554
+ @inputstep ("Approval ", resume_auth =allow_roles ("finance "), retry_auth =allow_roles ("ops "))
555
+ def approval (...):
556
+ ...
557
+ ```
558
+
559
+
560
+ #### Hand -off Model
561
+ !!!example
562
+ Suppose we have two teams , Dev and Platform , and a long workflow W that should be handed off to Platform at step `approval `.
563
+
564
+ Dev can start the workflow and retry steps prior to S . Once step S is reached , Platform (and only Platform ) can resume the workflow and retry later failed steps .
565
+
566
+ ```python
567
+ @workflow ("An expensive workflow ", auth =allow_roles ("dev "))
568
+ def W (...):
569
+ return begin >> A >> ... >> notify_platform >> handoff >> ... >> Z
570
+
571
+ @inputstep ("Hand -off ", resume_auth =allow_roles ("platform "))
572
+ def handoff (...):
573
+ ...
574
+ ```
575
+ Notice that default behaviors let us ignore `retry_auth ` arguments in both decorators .
576
+
577
+ #### Restricted Retries Model
578
+ !!!example
579
+ Suppose we have a workflow that anyone can run , but with steps that should only be retried by users with certain backend access .
580
+
581
+ ```python
582
+ @workflow ("A workflow for any user ", retry_auth =allow_roles ("admin "))
583
+ def W (...):
584
+ return begin >> A >> ... >> S >> ... >> Z
585
+ ```
586
+
587
+ Note that we could specify `auth =allow_roles ("user ")` if helpful , or we can omit `auth ` to fail open to any logged in user .
588
+
272
589
[1]: https :// github.com/workfloworchestrator/example-orchestrator-ui
273
590
[2]: https :// github.com/workfloworchestrator/example-orchestrator
274
591
[3]: https :// next-auth.js.org/
0 commit comments