github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/move/sharing.go (about) 1 package move 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "net/http" 8 "net/url" 9 "strings" 10 "time" 11 12 "github.com/cozy/cozy-stack/client/request" 13 "github.com/cozy/cozy-stack/model/instance" 14 "github.com/cozy/cozy-stack/model/oauth" 15 "github.com/cozy/cozy-stack/model/permission" 16 "github.com/cozy/cozy-stack/model/sharing" 17 "github.com/cozy/cozy-stack/pkg/consts" 18 "github.com/cozy/cozy-stack/pkg/couchdb" 19 "github.com/cozy/cozy-stack/pkg/jsonapi" 20 multierror "github.com/hashicorp/go-multierror" 21 "github.com/labstack/echo/v4" 22 ) 23 24 // NotifySharings will notify other instances with a common sharing that this 25 // instance has moved, and will tell them to the new URL to use for the 26 // sharing. 27 func NotifySharings(inst *instance.Instance) error { 28 // Let the dust settle a bit before starting the notifications 29 time.Sleep(3 * time.Second) 30 31 var sharings []*sharing.Sharing 32 req := couchdb.AllDocsRequest{Limit: 1000} 33 if err := couchdb.GetAllDocs(inst, consts.Sharings, &req, &sharings); err != nil { 34 return err 35 } 36 37 var errm error 38 for _, s := range sharings { 39 if strings.HasPrefix(s.ID(), "_design") { 40 continue 41 } 42 time.Sleep(100 * time.Millisecond) 43 if err := notifySharing(inst, s); err != nil { 44 errm = multierror.Append(errm, err) 45 } 46 } 47 return errm 48 } 49 50 func notifySharing(inst *instance.Instance, s *sharing.Sharing) error { 51 if !s.Owner { 52 return notifyMember(inst, s, 0) 53 } 54 55 var errm error 56 for i := range s.Members { 57 if i == 0 { 58 continue // skip the owner 59 } 60 if err := notifyMember(inst, s, i); err != nil { 61 errm = multierror.Append(errm, err) 62 } 63 } 64 return errm 65 } 66 67 func notifyMember(inst *instance.Instance, s *sharing.Sharing, index int) error { 68 u, err := url.Parse(s.Members[index].Instance) 69 if s.Members[index].Instance == "" || err != nil { 70 return err 71 } 72 73 clientID := s.Credentials[0].InboundClientID 74 if index > 0 { 75 clientID = s.Credentials[index-1].InboundClientID 76 } 77 cli := &oauth.Client{ClientID: clientID} 78 newToken, err := sharing.CreateAccessToken(inst, cli, s.ID(), permission.ALL) 79 if err != nil { 80 return err 81 } 82 moved := sharing.APIMoved{ 83 SharingID: s.ID(), 84 NewInstance: inst.PageURL("", nil), 85 AccessToken: newToken.AccessToken, 86 RefreshToken: newToken.RefreshToken, 87 } 88 data, err := jsonapi.MarshalObject(&moved) 89 if err != nil { 90 return err 91 } 92 body, err := json.Marshal(jsonapi.Document{Data: &data}) 93 if err != nil { 94 return err 95 } 96 97 credIndex := 0 98 if s.Owner { 99 credIndex = index - 1 100 } 101 if len(s.Credentials) <= credIndex || s.Credentials[credIndex].AccessToken == nil { 102 return errors.New("sharing in invalid state") 103 } 104 token := s.Credentials[credIndex].AccessToken.AccessToken 105 106 opts := &request.Options{ 107 Method: http.MethodPost, 108 Scheme: u.Scheme, 109 Domain: u.Host, 110 Path: "/sharings/" + s.SID + "/recipients/self/moved", 111 Headers: request.Headers{ 112 echo.HeaderAccept: jsonapi.ContentType, 113 echo.HeaderContentType: jsonapi.ContentType, 114 echo.HeaderAuthorization: "Bearer " + token, 115 }, 116 Body: bytes.NewReader(body), 117 ParseError: sharing.ParseRequestError, 118 } 119 res, err := request.Req(opts) 120 if res != nil && res.StatusCode/100 == 4 { 121 res, err = sharing.RefreshToken(inst, err, s, &s.Members[index], &s.Credentials[credIndex], opts, body) 122 } 123 if err != nil { 124 return err 125 } 126 defer res.Body.Close() 127 128 return nil 129 }