Generate m.room.third_party_invite if the ID server doesn't know the 3PID

This commit is contained in:
Brendan Abolivier 2017-08-25 15:54:14 +01:00
parent 5d8d2d948f
commit c207c5859e
No known key found for this signature in database
GPG key ID: 8EF1500759F70623
2 changed files with 111 additions and 17 deletions

View file

@ -58,7 +58,7 @@ func SendMembership(
return *reqErr return *reqErr
} }
if res := checkAndProcess3PIDInvite(req, device, &body, roomID); res != nil { if res := checkAndProcess3PIDInvite(req, device, &body, cfg, queryAPI, producer, roomID); res != nil {
return *res return *res
} }
@ -162,7 +162,8 @@ func getMembershipStateKey(
func checkAndProcess3PIDInvite( func checkAndProcess3PIDInvite(
req *http.Request, device *authtypes.Device, body *membershipRequestBody, req *http.Request, device *authtypes.Device, body *membershipRequestBody,
roomID string, cfg config.Dendrite, queryAPI api.RoomserverQueryAPI,
producer *producers.RoomserverProducer, roomID string,
) *util.JSONResponse { ) *util.JSONResponse {
if body.Address == "" && body.IDServer == "" && body.Medium == "" { if body.Address == "" && body.IDServer == "" && body.Medium == "" {
// If none of the 3PID-specific fields are supplied, it's a standard invite // If none of the 3PID-specific fields are supplied, it's a standard invite
@ -177,20 +178,47 @@ func checkAndProcess3PIDInvite(
} }
} }
resp, _, err := queryIDServer(req, body) resp, err := queryIDServer(req, device, body, roomID)
if err != nil { if err != nil {
resErr := httputil.LogThenError(req, err) resErr := httputil.LogThenError(req, err)
return &resErr return &resErr
} }
if resp.MXID != "" { if resp.Lookup.MXID == "" {
event, err := make3PIDInviteEvent(body, resp.StoreInvite, device, roomID, cfg, queryAPI)
if err == events.ErrRoomNoExists {
return &util.JSONResponse{
Code: 404,
JSON: jsonerror.NotFound(err.Error()),
}
} else if err != nil {
resErr := httputil.LogThenError(req, err)
return &resErr
}
if err := producer.SendEvents([]gomatrixserverlib.Event{*event}, cfg.Matrix.ServerName); err != nil {
resErr := httputil.LogThenError(req, err)
return &resErr
}
return &util.JSONResponse{
Code: 200,
JSON: struct{}{},
}
}
// Set the Matrix user ID from the body request and let the process // Set the Matrix user ID from the body request and let the process
// continue to create a "m.room.member" event // continue to create a "m.room.member" event
body.UserID = resp.MXID body.UserID = resp.Lookup.MXID
}
return nil return nil
} }
type idServerResponses struct {
Lookup *idServerLookupResponse
StoreInvite *idServerStoreInviteResponse
}
type idServerLookupResponse struct { type idServerLookupResponse struct {
TS int64 `json:"ts"` TS int64 `json:"ts"`
NotBefore int64 `json:"not_before"` NotBefore int64 `json:"not_before"`
@ -201,25 +229,36 @@ type idServerLookupResponse struct {
Signatures map[string]map[string]string `json:"signatures"` Signatures map[string]map[string]string `json:"signatures"`
} }
func queryIDServer(req *http.Request, body *membershipRequestBody) (res *idServerLookupResponse, token string, err error) { type idServerStoreInviteResponse struct {
res, err = queryIDServerLookup(body) PublicKey string `json:"public_key"`
Token string `json:"token"`
DisplayName string `json:"display_name"`
PublicKeys []common.PublicKey `json:"public_keys"`
}
func queryIDServer(
req *http.Request, device *authtypes.Device, body *membershipRequestBody,
roomID string,
) (res *idServerResponses, err error) {
res = new(idServerResponses)
res.Lookup, err = queryIDServerLookup(body)
if err != nil { if err != nil {
return return
} }
if res.Lookup.MXID == "" {
if res.MXID == "" { res.StoreInvite, err = queryIDServerStoreInvite(device, body, roomID)
// TODO: Store the invite and send a 3PID invite event return
} }
// Get timestamp in milliseconds to compare it // Get timestamp in milliseconds to compare it
now := time.Now().UnixNano() / 1000000 now := time.Now().UnixNano() / 1000000
if res.NotBefore > now || now > res.NotAfter { if res.Lookup.NotBefore > now || now > res.Lookup.NotAfter {
// If the current timestamp isn't in the time frame in which the association // If the current timestamp isn't in the time frame in which the association
// is known to be valid, re-run the query // is known to be valid, re-run the query
return queryIDServer(req, body) return queryIDServer(req, device, body, roomID)
} }
ok, err := checkIDServerSignatures(body, res) ok, err := checkIDServerSignatures(body, res.Lookup)
if err != nil { if err != nil {
return return
} }
@ -244,7 +283,7 @@ func queryIDServerLookup(body *membershipRequestBody) (res *idServerLookupRespon
return return
} }
func queryIDServerStoreInvite(device *authtypes.Device, body *membershipRequestBody, roomID string) (*http.Response, error) { func queryIDServerStoreInvite(device *authtypes.Device, body *membershipRequestBody, roomID string) (*idServerStoreInviteResponse, error) {
client := http.Client{} client := http.Client{}
data := url.Values{} data := url.Values{}
@ -260,8 +299,19 @@ func queryIDServerStoreInvite(device *authtypes.Device, body *membershipRequestB
} }
req.Header.Add("Content-Type", "application/x-www-form-urlencoded") req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
return client.Do(req) if resp.StatusCode != http.StatusOK {
errMsg := fmt.Sprintf("Identity server %s responded with a %d error code", body.IDServer, resp.StatusCode)
return nil, errors.New(errMsg)
}
idResp := new(idServerStoreInviteResponse)
err = json.NewDecoder(resp.Body).Decode(idResp)
return idResp, err
} }
func queryIDServerPubKey(body *membershipRequestBody, keyID string) (publicKey []byte, err error) { func queryIDServerPubKey(body *membershipRequestBody, keyID string) (publicKey []byte, err error) {
@ -302,3 +352,33 @@ func checkIDServerSignatures(body *membershipRequestBody, res *idServerLookupRes
return true, nil return true, nil
} }
func make3PIDInviteEvent(
body *membershipRequestBody, res *idServerStoreInviteResponse,
device *authtypes.Device, roomID string, cfg config.Dendrite,
queryAPI api.RoomserverQueryAPI,
) (*gomatrixserverlib.Event, error) {
builder := &gomatrixserverlib.EventBuilder{
Sender: device.UserID,
RoomID: roomID,
Type: "m.room.third_party_invite",
StateKey: &res.Token,
}
validityURL := fmt.Sprintf("https://%s/_matrix/identity/api/v1/pubkey/isvalid", body.IDServer)
content := common.ThirdPartyInviteContent{
DisplayName: res.DisplayName,
KeyValidityURL: validityURL,
PublicKey: res.PublicKey,
}
content.PublicKeys = make([]common.PublicKey, len(res.PublicKeys))
copy(content.PublicKeys, res.PublicKeys)
if err := builder.SetContent(content); err != nil {
return nil, err
}
var queryRes *api.QueryLatestEventsAndStateResponse
return events.BuildEvent(builder, cfg, queryAPI, queryRes)
}

View file

@ -29,6 +29,20 @@ type MemberContent struct {
// TODO: ThirdPartyInvite string `json:"third_party_invite,omitempty"` // TODO: ThirdPartyInvite string `json:"third_party_invite,omitempty"`
} }
// ThirdPartyInviteContent is the content event for https://matrix.org/speculator/spec/HEAD/client_server/unstable.html#m-room-third-party-invite
type ThirdPartyInviteContent struct {
DisplayName string `json:"display_name"`
KeyValidityURL string `json:"key_validity_url"`
PublicKey string `json:"public_key"`
PublicKeys []PublicKey `json:"public_keys"`
}
// PublicKey is the PublicKeys structure in https://matrix.org/speculator/spec/HEAD/client_server/unstable.html#m-room-third-party-invite
type PublicKey struct {
KeyValidityURL string `json:"key_validity_url"`
PublicKey string `json:"public_key"`
}
// NameContent is the event content for https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-name // NameContent is the event content for https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-name
type NameContent struct { type NameContent struct {
Name string `json:"name"` Name string `json:"name"`