c7193e24d0
There are cases where a dendrite instance is unaware of a pseudo ID for a user, the user is not a member of that room. To represent this case, we currently use the 'zero' value, which is often not checked and so causes errors later down the line. To make this case more explict, and to be consistent with `QueryUserIDForSender`, this PR changes this to use a pointer (and `nil` to mean no sender ID). Signed-off-by: `Sam Wedgwood <sam@wedgwood.dev>`
202 lines
6.1 KiB
Go
202 lines
6.1 KiB
Go
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package routing
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/matrix-org/gomatrixserverlib"
|
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
|
"github.com/matrix-org/util"
|
|
|
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
|
"github.com/matrix-org/dendrite/internal/transactions"
|
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
|
"github.com/matrix-org/dendrite/roomserver/types"
|
|
"github.com/matrix-org/dendrite/setup/config"
|
|
userapi "github.com/matrix-org/dendrite/userapi/api"
|
|
)
|
|
|
|
type redactionContent struct {
|
|
Reason string `json:"reason"`
|
|
}
|
|
|
|
type redactionResponse struct {
|
|
EventID string `json:"event_id"`
|
|
}
|
|
|
|
func SendRedaction(
|
|
req *http.Request, device *userapi.Device, roomID, eventID string, cfg *config.ClientAPI,
|
|
rsAPI roomserverAPI.ClientRoomserverAPI,
|
|
txnID *string,
|
|
txnCache *transactions.Cache,
|
|
) util.JSONResponse {
|
|
deviceUserID, userIDErr := spec.NewUserID(device.UserID, true)
|
|
if userIDErr != nil {
|
|
return util.JSONResponse{
|
|
Code: http.StatusForbidden,
|
|
JSON: spec.Forbidden("userID doesn't have power level to redact"),
|
|
}
|
|
}
|
|
validRoomID, err := spec.NewRoomID(roomID)
|
|
if err != nil {
|
|
return util.JSONResponse{
|
|
Code: http.StatusBadRequest,
|
|
JSON: spec.BadJSON("RoomID is invalid"),
|
|
}
|
|
}
|
|
senderID, queryErr := rsAPI.QuerySenderIDForUser(req.Context(), *validRoomID, *deviceUserID)
|
|
if queryErr != nil {
|
|
return util.JSONResponse{
|
|
Code: http.StatusForbidden,
|
|
JSON: spec.Forbidden("userID doesn't have power level to redact"),
|
|
}
|
|
}
|
|
|
|
resErr := checkMemberInRoom(req.Context(), rsAPI, *deviceUserID, roomID)
|
|
if resErr != nil {
|
|
return *resErr
|
|
}
|
|
|
|
// if user is member of room, and sender ID is nil, then this user doesn't have a pseudo ID for some reason,
|
|
// which is unexpected.
|
|
if senderID == nil {
|
|
util.GetLogger(req.Context()).WithField("userID", *deviceUserID).WithField("roomID", roomID).Error("missing sender ID for user, despite having membership")
|
|
return util.JSONResponse{
|
|
Code: http.StatusInternalServerError,
|
|
JSON: spec.Unknown("internal server error"),
|
|
}
|
|
}
|
|
|
|
if txnID != nil {
|
|
// Try to fetch response from transactionsCache
|
|
if res, ok := txnCache.FetchTransaction(device.AccessToken, *txnID, req.URL); ok {
|
|
return *res
|
|
}
|
|
}
|
|
|
|
ev := roomserverAPI.GetEvent(req.Context(), rsAPI, roomID, eventID)
|
|
if ev == nil {
|
|
return util.JSONResponse{
|
|
Code: 400,
|
|
JSON: spec.NotFound("unknown event ID"), // TODO: is it ok to leak existence?
|
|
}
|
|
}
|
|
if ev.RoomID() != roomID {
|
|
return util.JSONResponse{
|
|
Code: 400,
|
|
JSON: spec.NotFound("cannot redact event in another room"),
|
|
}
|
|
}
|
|
|
|
// "Users may redact their own events, and any user with a power level greater than or equal
|
|
// to the redact power level of the room may redact events there"
|
|
// https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
|
|
allowedToRedact := ev.SenderID() == *senderID
|
|
if !allowedToRedact {
|
|
plEvent := roomserverAPI.GetStateEvent(req.Context(), rsAPI, roomID, gomatrixserverlib.StateKeyTuple{
|
|
EventType: spec.MRoomPowerLevels,
|
|
StateKey: "",
|
|
})
|
|
if plEvent == nil {
|
|
return util.JSONResponse{
|
|
Code: 403,
|
|
JSON: spec.Forbidden("You don't have permission to redact this event, no power_levels event in this room."),
|
|
}
|
|
}
|
|
pl, plErr := plEvent.PowerLevels()
|
|
if plErr != nil {
|
|
return util.JSONResponse{
|
|
Code: 403,
|
|
JSON: spec.Forbidden(
|
|
"You don't have permission to redact this event, the power_levels event for this room is malformed so auth checks cannot be performed.",
|
|
),
|
|
}
|
|
}
|
|
allowedToRedact = pl.UserLevel(*senderID) >= pl.Redact
|
|
}
|
|
if !allowedToRedact {
|
|
return util.JSONResponse{
|
|
Code: 403,
|
|
JSON: spec.Forbidden("You don't have permission to redact this event, power level too low."),
|
|
}
|
|
}
|
|
|
|
var r redactionContent
|
|
resErr = httputil.UnmarshalJSONRequest(req, &r)
|
|
if resErr != nil {
|
|
return *resErr
|
|
}
|
|
|
|
// create the new event and set all the fields we can
|
|
proto := gomatrixserverlib.ProtoEvent{
|
|
SenderID: string(*senderID),
|
|
RoomID: roomID,
|
|
Type: spec.MRoomRedaction,
|
|
Redacts: eventID,
|
|
}
|
|
err = proto.SetContent(r)
|
|
if err != nil {
|
|
util.GetLogger(req.Context()).WithError(err).Error("proto.SetContent failed")
|
|
return util.JSONResponse{
|
|
Code: http.StatusInternalServerError,
|
|
JSON: spec.InternalServerError{},
|
|
}
|
|
}
|
|
|
|
identity, err := rsAPI.SigningIdentityFor(req.Context(), *validRoomID, *deviceUserID)
|
|
if err != nil {
|
|
return util.JSONResponse{
|
|
Code: http.StatusInternalServerError,
|
|
JSON: spec.InternalServerError{},
|
|
}
|
|
}
|
|
|
|
var queryRes roomserverAPI.QueryLatestEventsAndStateResponse
|
|
e, err := eventutil.QueryAndBuildEvent(req.Context(), &proto, &identity, time.Now(), rsAPI, &queryRes)
|
|
if errors.Is(err, eventutil.ErrRoomNoExists{}) {
|
|
return util.JSONResponse{
|
|
Code: http.StatusNotFound,
|
|
JSON: spec.NotFound("Room does not exist"),
|
|
}
|
|
}
|
|
domain := device.UserDomain()
|
|
if err = roomserverAPI.SendEvents(context.Background(), rsAPI, roomserverAPI.KindNew, []*types.HeaderedEvent{e}, device.UserDomain(), domain, domain, nil, false); err != nil {
|
|
util.GetLogger(req.Context()).WithError(err).Errorf("failed to SendEvents")
|
|
return util.JSONResponse{
|
|
Code: http.StatusInternalServerError,
|
|
JSON: spec.InternalServerError{},
|
|
}
|
|
}
|
|
|
|
res := util.JSONResponse{
|
|
Code: 200,
|
|
JSON: redactionResponse{
|
|
EventID: e.EventID(),
|
|
},
|
|
}
|
|
|
|
// Add response to transactionsCache
|
|
if txnID != nil {
|
|
txnCache.AddTransaction(device.AccessToken, *txnID, req.URL, &res)
|
|
}
|
|
|
|
return res
|
|
}
|