mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-11 17:03:10 -06:00
Generate invite from 3PID known by ID server
This commit is contained in:
parent
d28fa944a1
commit
4bda6dca79
|
|
@ -15,7 +15,14 @@
|
|||
package writers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
||||
|
|
@ -31,7 +38,7 @@ import (
|
|||
"github.com/matrix-org/util"
|
||||
)
|
||||
|
||||
type requestBody struct {
|
||||
type membershipRequestBody struct {
|
||||
UserID string `json:"user_id"`
|
||||
Reason string `json:"reason"`
|
||||
IDServer string `json:"id_server"`
|
||||
|
|
@ -46,11 +53,15 @@ func SendMembership(
|
|||
roomID string, membership string, cfg config.Dendrite,
|
||||
queryAPI api.RoomserverQueryAPI, producer *producers.RoomserverProducer,
|
||||
) util.JSONResponse {
|
||||
var body requestBody
|
||||
var body membershipRequestBody
|
||||
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
|
||||
return *reqErr
|
||||
}
|
||||
|
||||
if res := checkAndProcess3PIDInvite(req, device, &body, roomID); res != nil {
|
||||
return *res
|
||||
}
|
||||
|
||||
stateKey, reason, reqErr := getMembershipStateKey(body, device, membership)
|
||||
if reqErr != nil {
|
||||
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,
|
||||
// returns a JSONResponse with a corresponding error code and message.
|
||||
func getMembershipStateKey(
|
||||
body requestBody, device *authtypes.Device, membership string,
|
||||
body membershipRequestBody, device *authtypes.Device, membership string,
|
||||
) (stateKey string, reason string, response *util.JSONResponse) {
|
||||
if membership == "ban" || membership == "unban" || membership == "kick" || membership == "invite" {
|
||||
// If we're in this case, the state key is contained in the request body,
|
||||
|
|
@ -142,3 +153,146 @@ func getMembershipStateKey(
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue