44package action
55
66import (
7+ "context"
78 "embed"
89 "encoding/json"
910 "fmt"
@@ -36,8 +37,10 @@ var embedHtml embed.FS
3637var embedFS = hashfs .NewFS (embedHtml )
3738
3839type ActionLink struct {
39- Name string
40- Path string
40+ Name string
41+ Path string
42+ Permits []string
43+ Authorized bool
4144}
4245
4346// Action represents a single action that is exposed by the App. Actions
@@ -69,13 +72,16 @@ type Action struct {
6972 esmLibs []types.JSLibrary
7073 appPathDomain types.AppPathDomain
7174 serverConfig * types.ServerConfig
75+ permit []string
76+ authorizer types.AuthorizerFunc // can be null
7277}
7378
7479// NewAction creates a new action
7580func NewAction (logger * types.Logger , sourceFS * appfs.SourceFs , isDev bool , name , description , apath string , run , suggest starlark.Callable ,
7681 params []apptype.AppParam , paramValuesStr map [string ]string , paramDict starlark.StringDict ,
7782 appPath string , styleType types.StyleType , containerProxyUrl string , hidden []string , showValidate bool ,
78- auditInsert func (* types.AuditEvent ) error , containerManager any , jsLibs []types.JSLibrary , appPathDomain types.AppPathDomain , serverConfig * types.ServerConfig ) (* Action , error ) {
83+ auditInsert func (* types.AuditEvent ) error , containerManager any , jsLibs []types.JSLibrary , appPathDomain types.AppPathDomain ,
84+ serverConfig * types.ServerConfig , permit []string , authorizer types.AuthorizerFunc ) (* Action , error ) {
7985
8086 funcMap := system .GetFuncMap ()
8187
@@ -151,13 +157,16 @@ func NewAction(logger *types.Logger, sourceFS *appfs.SourceFs, isDev bool, name,
151157 // Links, AppTemplate and Theme names are initialized later
152158 appPathDomain : appPathDomain ,
153159 serverConfig : serverConfig ,
160+ permit : permit ,
161+ authorizer : authorizer ,
154162 }, nil
155163}
156164
157165func (a * Action ) GetLink () ActionLink {
158166 return ActionLink {
159- Name : a .name ,
160- Path : a .pagePath ,
167+ Name : a .name ,
168+ Path : a .pagePath ,
169+ Permits : a .permit ,
161170 }
162171}
163172
@@ -203,7 +212,27 @@ func (a *Action) validateAction(w http.ResponseWriter, r *http.Request) {
203212 a .execAction (w , r , false , true , "validate" )
204213}
205214
215+ func (a * Action ) authorizeAction (w http.ResponseWriter , r * http.Request ) bool {
216+ if a .authorizer != nil && len (a .permit ) > 0 {
217+ authorized , err := a .authorizer (r .Context (), a .permit )
218+ if err != nil {
219+ http .Error (w , err .Error (), http .StatusInternalServerError )
220+ return false
221+ }
222+ if ! authorized {
223+ userId := system .GetContextUserId (r .Context ())
224+ http .Error (w , fmt .Sprintf ("Unauthorized : %s does not have access to action %s" , userId , a .name ), http .StatusUnauthorized )
225+ return false
226+ }
227+ }
228+ return true
229+ }
230+
206231func (a * Action ) execAction (w http.ResponseWriter , r * http.Request , isSuggest , isValidate bool , op string ) {
232+ if ! a .authorizeAction (w , r ) {
233+ return
234+ }
235+
207236 if isSuggest && a .suggest == nil {
208237 http .Error (w , "suggest not supported for this action" , http .StatusNotImplemented )
209238 return
@@ -406,7 +435,7 @@ func (a *Action) execAction(w http.ResponseWriter, r *http.Request, isSuggest, i
406435 }
407436
408437 if isSuggest {
409- a .handleSuggestResponse (w , qsParams .Encode (), ret )
438+ a .handleSuggestResponse (r . Context (), w , qsParams .Encode (), ret )
410439 return
411440 }
412441
@@ -487,7 +516,7 @@ func (a *Action) execAction(w http.ResponseWriter, r *http.Request, isSuggest, i
487516 }
488517
489518 if len (a .Links ) > 1 {
490- linksWithQS := a .getLinksWithQS (qsParams .Encode ())
519+ linksWithQS := a .getLinksWithQS (r . Context (), qsParams .Encode ())
491520 input := map [string ]any {"links" : linksWithQS }
492521 err = a .actionTemplate .ExecuteTemplate (w , "dropdown" , input )
493522 if err != nil {
@@ -730,6 +759,10 @@ const (
730759)
731760
732761func (a * Action ) getForm (w http.ResponseWriter , r * http.Request ) {
762+ if ! a .authorizeAction (w , r ) {
763+ return
764+ }
765+
733766 queryParams := r .URL .Query ()
734767 params := make ([]ParamDef , 0 , len (a .params ))
735768
@@ -810,7 +843,7 @@ func (a *Action) getForm(w http.ResponseWriter, r *http.Request) {
810843 params = append (params , param )
811844 }
812845
813- linksWithQS := a .getLinksWithQS (r .URL .RawQuery )
846+ linksWithQS := a .getLinksWithQS (r .Context (), r . URL .RawQuery )
814847 input := map [string ]any {
815848 "dev" : a .isDev ,
816849 "name" : a .name ,
@@ -833,20 +866,29 @@ func (a *Action) getForm(w http.ResponseWriter, r *http.Request) {
833866 }
834867}
835868
836- func (a * Action ) getLinksWithQS (qs string ) []ActionLink {
869+ func (a * Action ) getLinksWithQS (ctx context. Context , qs string ) []ActionLink {
837870 linksWithQS := make ([]ActionLink , 0 , len (a .Links ))
838871 for _ , link := range a .Links {
872+ authorized := true
873+ if a .authorizer != nil && len (link .Permits ) > 0 {
874+ var err error
875+ authorized , err = a .authorizer (ctx , link .Permits )
876+ if err != nil {
877+ a .Error ().Msgf ("error authorizing link %s: %s" , link .Name , err )
878+ }
879+ }
839880 if link .Path != a .pagePath { // Don't add self link
840881 if qs != "" {
841882 link .Path = link .Path + "?" + qs
842883 }
884+ link .Authorized = authorized // whether this user has access to this action
843885 linksWithQS = append (linksWithQS , link )
844886 }
845887 }
846888 return linksWithQS
847889}
848890
849- func (a * Action ) handleSuggestResponse (w http.ResponseWriter , paramQS string , retVal starlark.Value ) {
891+ func (a * Action ) handleSuggestResponse (ctx context. Context , w http.ResponseWriter , paramQS string , retVal starlark.Value ) {
850892 ret , err := starlark_type .UnmarshalStarlark (retVal )
851893 if err != nil {
852894 http .Error (w , fmt .Sprintf ("error unmarshalling suggest response: %s" , err ), http .StatusInternalServerError )
@@ -865,7 +907,7 @@ func (a *Action) handleSuggestResponse(w http.ResponseWriter, paramQS string, re
865907 }
866908
867909 if len (a .Links ) > 1 {
868- linksWithQS := a .getLinksWithQS (paramQS )
910+ linksWithQS := a .getLinksWithQS (ctx , paramQS )
869911 input := map [string ]any {"links" : linksWithQS }
870912 err = a .actionTemplate .ExecuteTemplate (w , "dropdown" , input )
871913 if err != nil {
0 commit comments