okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
155c8e8
main
1package daemon 2 3import ( 4"context" 5"errors" 6"io" 7"net/http" 8"regexp" 9 10"albedo/cli/internal/daemon/protocol" 11) 12 13type Webhook struct { 14ID ,Session ,Name ,URL ,Address ,Header ,Prefix ,Revision ,ETag string 15Enabled bool 16} 17type WebhookEntry struct { 18Deferred * string 19Hook Webhook 20Queued int 21} 22type WebhookResult struct { 23Hook * Webhook 24Session ,Secret ,Message ,PermissionETag ,DeletedID string 25Hooks []WebhookEntry 26AgentManagement bool 27} 28type WebhookPermission struct { 29SessionID ,ETag string 30AgentManagement bool 31} 32type WebhookCreateRequest = protocol.HookCreate 33type WebhookPatch = protocol.HookPatch 34type WebhookAction string 35 36const ( 37WebhookCreate WebhookAction = "create" 38WebhookSignature WebhookAction = "signature" 39WebhookRotate WebhookAction = "rotate" 40WebhookEnable WebhookAction = "enable" 41WebhookDisable WebhookAction = "disable" 42WebhookDelete WebhookAction = "delete" 43WebhookAgentEnable WebhookAction = "agent_enable" 44WebhookAgentDisable WebhookAction = "agent_disable" 45) 46 47// WebhookRequest is a captured terminal form operation, not a public dispatcher. 48type WebhookRequest struct { 49Action WebhookAction 50HookID ,SessionID ,Name ,Secret ,Header ,Prefix ,ETag string 51} 52 53var webhookNamePattern = regexp .MustCompile (`^[A-Za-z0-9_-]{1,64}$` ) 54 55func hookValue (w protocol.HookConfigurationResource ,deliveryURL string )Webhook { 56c := w .Value 57return Webhook {ID :c .ID ,Session :c .SessionID ,Name :c .Name ,URL :deliveryURL ,Address :deliveryURL ,Header :c .SignatureHeader ,Prefix :c .SignaturePrefix ,Revision :c .Revision ,ETag :w .ETag ,Enabled :c .Enabled } 58} 59func ListWebhooks (ctx context.Context ,conn * Connection ,sessionID string ) ([]WebhookEntry ,error ) { 60r := []WebhookEntry {} 61params := protocol.ListHooksParams {Limit :new (int64 (200 ))} 62if sessionID != "" { 63params .SessionID = & sessionID 64 } 65err := walkPages (func (next * string ) (protocol.HookPage ,* string ,error ) { 66params .Next = next 67var page protocol.HookPage 68pageErr := executeRead (ctx ,conn ,operation {Name :"list webhooks" ,BuildRequest :func (base string ,body io.Reader ) (* http.Request ,error ) { 69return protocol .NewListHooksRequest (base ,& params ) 70 },Policy :readRecovery },func (data []byte )error {return decodeRequired (data ,& page ) }) 71return page ,page .Next ,pageErr 72 },func (page protocol.HookPage )error { 73if page .Items == nil { 74return fieldError ("hooks" ) 75 } 76for _ ,row := range page .Items { 77if row .ConfigurationResource .ETag == "" { 78return fieldError ("hook validator" ) 79 } 80var deferred * string 81if row .DeferralReason != nil { 82deferred = & row .DeferralReason .Detail 83 } 84r = append (r ,WebhookEntry {Hook :hookValue (row .ConfigurationResource ,row .DeliveryURL ),Queued :int (row .PendingCount ),Deferred :deferred }) 85 } 86return nil 87 },"hook page cursor" ) 88if err != nil { 89return nil ,err 90 } 91return r ,nil 92} 93 94func hookChange (ctx context.Context ,conn * Connection ,op operation ,status int ,secret bool ) (* WebhookResult ,error ) { 95var w protocol.HookChange 96err := executeMutation (ctx ,conn ,op , []int {status },func (data []byte ,_ int )error { 97if err := decodeRequired (data ,& w );err != nil { 98return err 99 } 100if w .Resource .ETag == "" || w .Resource .Value .ID == "" { 101return fieldError ("hook resource" ) 102 } 103if secret && value (w .Secret )== "" { 104return fieldError ("hook secret" ) 105 } 106if ! secret && w .Secret != nil { 107return fieldError ("unexpected hook secret" ) 108 } 109return nil 110 }) 111if err != nil { 112return nil ,err 113 } 114h := hookValue (w .Resource ,"" ) 115r := & WebhookResult {Hook :& h ,Secret :value (w .Secret ),Message :"Saved." } 116if w .Notification .State == "failed" { 117r .Message = "Saved; notification failed: " + value (w .Notification .Detail ) 118 } 119return r ,nil 120} 121func CreateWebhook (ctx context.Context ,conn * Connection ,request WebhookCreateRequest ) (* WebhookResult ,error ) { 122if ! webhookNamePattern .MatchString (request .Name )|| request .SessionID == "" { 123return nil ,errors .New ("webhook creation requires a session and a valid name" ) 124 } 125return hookChange (ctx ,conn ,operation {Name :"create webhook" ,BuildRequest :func (base string ,body io.Reader ) (* http.Request ,error ) { 126return protocol .NewCreateHookRequest (base ,request ) 127 },Body :request ,Policy :noRecovery },201 ,true ) 128} 129func EditWebhook (ctx context.Context ,conn * Connection ,id ,etag string ,patch WebhookPatch ) (* WebhookResult ,error ) { 130headers ,err := observedHeaders (etag ) 131if err != nil { 132return nil ,err 133 } 134return hookChange (ctx ,conn ,operation {Name :"edit webhook" ,BuildRequest :func (base string ,body io.Reader ) (* http.Request ,error ) { 135return protocol .NewPatchHookRequestWithApplicationMergePatchPlusJSONBody (base ,id ,& protocol.PatchHookParams {View :"configuration" ,IfMatch :etag },patch ) 136 },Headers :headers ,Body :patch ,Policy :noRecovery },200 ,false ) 137} 138func RotateWebhookSecret (ctx context.Context ,conn * Connection ,id ,etag ,secret string ) (* WebhookResult ,error ) { 139headers ,err := observedHeaders (etag ) 140if err != nil { 141return nil ,err 142 } 143headers .Set ("Content-Type" ,"application/json" ) 144return hookChange (ctx ,conn ,operation {Name :"rotate webhook secret" ,BuildRequest :func (base string ,body io.Reader ) (* http.Request ,error ) { 145return protocol .NewRotateHookSecretRequestWithBody (base ,id ,& protocol.RotateHookSecretParams {IfMatch :etag },"application/json" ,body ) 146 },Headers :headers ,Body : protocol.HookSecretRequest {Secret :optionalText (secret )},Policy :noRecovery },200 ,true ) 147} 148func DeleteWebhook (ctx context.Context ,conn * Connection ,id ,etag string ) (* WebhookResult ,error ) { 149headers ,err := observedHeaders (etag ) 150if err != nil { 151return nil ,err 152 } 153var w protocol.ExtensionDeletion 154err = executeMutation (ctx ,conn ,operation {Name :"delete webhook" ,BuildRequest :func (base string ,body io.Reader ) (* http.Request ,error ) { 155return protocol .NewDeleteHookRequest (base ,id ,& protocol.DeleteHookParams {View :"configuration" ,IfMatch :etag }) 156 },Headers :headers ,Policy :noRecovery }, []int {200 },func (data []byte ,_ int )error { 157if err := decodeRequired (data ,& w );err != nil { 158return err 159 } 160if w .ID != id { 161return fieldError ("deleted hook" ) 162 } 163return nil 164 }) 165r := & WebhookResult {Message :"Deleted." ,DeletedID :w .ID } 166if w .Notification .State == "failed" { 167r .Message += " Notification failed: " + value (w .Notification .Detail ) 168 } 169return r ,err 170} 171func GetWebhookPermission (ctx context.Context ,conn * Connection ,id string ) (WebhookPermission ,error ) { 172r := WebhookPermission {SessionID :id } 173var w protocol.HookPermission 174err := executeRead (ctx ,conn ,operation {Name :"read webhook agent permission" ,BuildRequest :func (base string ,body io.Reader ) (* http.Request ,error ) { 175return protocol .NewGetHookPermissionRequest (base ,id ) 176 },Validator :& r .ETag ,Policy :readRecovery },func (data []byte )error { 177if err := decodeRequired (data ,& w );err != nil { 178return err 179 } 180if w .SessionID != id || r .ETag == "" { 181return fieldError ("webhook permission" ) 182 } 183r .AgentManagement = w .AgentManage 184return nil 185 }) 186return r ,err 187} 188func SetWebhookPermission (ctx context.Context ,conn * Connection ,id ,etag string ,enabled bool ) (* WebhookResult ,error ) { 189headers ,err := observedHeaders (etag ) 190if err != nil { 191return nil ,err 192 } 193var w protocol.HookPermissionChange 194err = executeMutation (ctx ,conn ,operation {Name :"edit webhook agent permission" ,BuildRequest :func (base string ,body io.Reader ) (* http.Request ,error ) { 195return protocol .NewPatchHookPermissionRequestWithBody (base ,id ,& protocol.PatchHookPermissionParams {IfMatch :etag },"application/merge-patch+json" ,body ) 196 },Headers :headers ,Body : protocol.HookPermissionPatch {AgentManage :& enabled },Policy :noRecovery }, []int {200 },func (data []byte ,_ int )error { 197if err := decodeRequired (data ,& w );err != nil { 198return err 199 } 200if w .Resource .Value .SessionID != id || w .Resource .ETag == "" || w .Resource .Value .AgentManage != enabled { 201return fieldError ("webhook permission acknowledgment" ) 202 } 203return nil 204 }) 205if err != nil { 206return nil ,err 207 } 208result := & WebhookResult {AgentManagement :w .Resource .Value .AgentManage ,PermissionETag :w .Resource .ETag ,Message :"Saved agent permission." } 209if w .Notification .State == "failed" { 210result .Message += " Notification failed: " + value (w .Notification .Detail ) 211 } 212return result ,nil 213}