Generate invite from 3PID known by ID server

This commit is contained in:
Brendan Abolivier 2017-08-25 12:29:18 +01:00
parent d28fa944a1
commit 4bda6dca79
No known key found for this signature in database
GPG key ID: 8EF1500759F70623

View file

@ -15,7 +15,14 @@
package writers package writers
import ( import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/http" "net/http"
"net/url"
"strings"
"time"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts" "github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
@ -31,7 +38,7 @@ import (
"github.com/matrix-org/util" "github.com/matrix-org/util"
) )
type requestBody struct { type membershipRequestBody struct {
UserID string `json:"user_id"` UserID string `json:"user_id"`
Reason string `json:"reason"` Reason string `json:"reason"`
IDServer string `json:"id_server"` IDServer string `json:"id_server"`
@ -46,11 +53,15 @@ func SendMembership(
roomID string, membership string, cfg config.Dendrite, roomID string, membership string, cfg config.Dendrite,
queryAPI api.RoomserverQueryAPI, producer *producers.RoomserverProducer, queryAPI api.RoomserverQueryAPI, producer *producers.RoomserverProducer,
) util.JSONResponse { ) util.JSONResponse {
var body requestBody var body membershipRequestBody
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil { if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
return *reqErr return *reqErr
} }
if res := checkAndProcess3PIDInvite(req, device, &body, roomID); res != nil {
return *res
}
stateKey, reason, reqErr := getMembershipStateKey(body, device, membership) stateKey, reason, reqErr := getMembershipStateKey(body, device, membership)
if reqErr != nil { if reqErr != nil {
return *reqErr return *reqErr
@ -120,7 +131,7 @@ func SendMembership(
// In the latter case, if there was an issue retrieving the user ID from the request body, // In the latter case, if there was an issue retrieving the user ID from the request body,
// returns a JSONResponse with a corresponding error code and message. // returns a JSONResponse with a corresponding error code and message.
func getMembershipStateKey( func getMembershipStateKey(
body requestBody, device *authtypes.Device, membership string, body membershipRequestBody, device *authtypes.Device, membership string,
) (stateKey string, reason string, response *util.JSONResponse) { ) (stateKey string, reason string, response *util.JSONResponse) {
if membership == "ban" || membership == "unban" || membership == "kick" || membership == "invite" { if membership == "ban" || membership == "unban" || membership == "kick" || membership == "invite" {
// If we're in this case, the state key is contained in the request body, // If we're in this case, the state key is contained in the request body,
@ -142,3 +153,146 @@ func getMembershipStateKey(
return return
} }
func checkAndProcess3PIDInvite(
req *http.Request, device *authtypes.Device, body *membershipRequestBody,
roomID string,
) *util.JSONResponse {
if body.Address == "" && body.IDServer == "" && body.Medium == "" {
// If none of the 3PID-specific fields are supplied, it's a standard invite
// so return nil for it to be processed as such
return nil
} else if body.Address == "" || body.IDServer == "" || body.Medium == "" {
// If at least one of the 3PID-specific fields is supplied but not all
// of them, return an error
return &util.JSONResponse{
Code: 400,
JSON: jsonerror.BadJSON("'address', 'id_server' and 'medium' must all be supplied"),
}
}
resp, _, err := queryIDServer(req, body)
if err != nil {
resErr := httputil.LogThenError(req, err)
return &resErr
}
if resp.MXID != "" {
// Set the Matrix user ID from the body request and let the process
// continue to create a "m.room.member" event
body.UserID = resp.MXID
}
return nil
}
type idServerLookupResponse struct {
TS int64 `json:"ts"`
NotBefore int64 `json:"not_before"`
NotAfter int64 `json:"not_after"`
Medium string `json:"medium"`
Address string `json:"address"`
MXID string `json:"mxid"`
Signatures map[string]map[string]string `json:"signatures"`
}
func queryIDServer(req *http.Request, body *membershipRequestBody) (res *idServerLookupResponse, token string, err error) {
res, err = queryIDServerLookup(body)
if err != nil {
return
}
if res.MXID == "" {
// TODO: Store the invite and send a 3PID invite event
}
// Get timestamp in milliseconds to compare it
now := time.Now().UnixNano() / 1000000
if res.NotBefore > now || now > res.NotAfter {
// If the current timestamp isn't in the time frame in which the association
// is known to be valid, re-run the query
return queryIDServer(req, body)
}
ok, err := checkIDServerSignatures(body, res)
if err != nil {
return
}
if !ok {
err = errors.New("The identity server's identity could not be verified")
return
}
return
}
func queryIDServerLookup(body *membershipRequestBody) (res *idServerLookupResponse, err error) {
address := url.QueryEscape(body.Address)
url := fmt.Sprintf("https://%s/_matrix/identity/api/v1/lookup?medium=%s&address=%s", body.IDServer, body.Medium, address)
resp, err := http.Get(url)
if err != nil {
return
}
// TODO: Check status code
res = new(idServerLookupResponse)
err = json.NewDecoder(resp.Body).Decode(res)
return
}
func queryIDServerStoreInvite(device *authtypes.Device, body *membershipRequestBody, roomID string) (*http.Response, error) {
client := http.Client{}
data := url.Values{}
data.Add("medium", body.Medium)
data.Add("address", body.Address)
data.Add("room_id", roomID)
data.Add("sender", device.UserID)
url := fmt.Sprintf("https://%s/_matrix/identity/api/v1/store-invite", body.IDServer)
req, err := http.NewRequest("POST", url, strings.NewReader(data.Encode()))
if err != nil {
return nil, err
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
return client.Do(req)
}
func queryIDServerPubKey(body *membershipRequestBody, keyID string) (publicKey []byte, err error) {
url := fmt.Sprintf("https://%s/_matrix/identity/api/v1/pubkey/%s", body.IDServer, keyID)
resp, err := http.Get(url)
if err != nil {
return
}
var pubKeyRes struct {
PublicKey string `json:"public_key"`
}
if err = json.NewDecoder(resp.Body).Decode(&pubKeyRes); err != nil {
return nil, err
}
// TODO: Store the public key in the database and, if there's one stored, retrieve
// it and verify its validity (/isvalid) instead of fetching it
return base64.RawStdEncoding.DecodeString(pubKeyRes.PublicKey)
}
func checkIDServerSignatures(body *membershipRequestBody, res *idServerLookupResponse) (ok bool, err error) {
marshalledBody, err := json.Marshal(*res)
if err != nil {
return
}
for domain, signatures := range res.Signatures {
for keyID := range signatures {
pubKey, err := queryIDServerPubKey(body, keyID)
if err != nil {
return false, err
}
if err = gomatrixserverlib.VerifyJSON(domain, gomatrixserverlib.KeyID(keyID), pubKey, marshalledBody); err != nil {
return false, nil
}
}
}
return true, nil
}