mirror of
https://github.com/1f349/dendrite.git
synced 2024-11-22 11:41:38 +00:00
Restricted join support on /make_join
, /send_join
(#2478)
* Add `QueryRestrictedJoinAllowed` * Add `Resident` flag to `QueryRestrictedJoinAllowedResponse` * Check restricted joins on federation API * Return `Restricted` to determine if the room was restricted or not * Populate `AuthorisedVia` properly * Sign the event on `/send_join`, return it in the `/send_join` response in the `"event"` key * Kick back joins with invalid authorising user IDs, use event from `"event"` key if returned in `RespSendJoin` * Use invite helper in `QueryRestrictedJoinAllowed` * Only use users with the power to invite, change error bubbling a bit * Placate the almighty linter One day I will nuke `gocyclo` from orbit and everything in the world will be much better for it. * Review comments
This commit is contained in:
parent
d621dd2986
commit
81843e8836
@ -154,6 +154,12 @@ func MissingParam(msg string) *MatrixError {
|
|||||||
return &MatrixError{"M_MISSING_PARAM", msg}
|
return &MatrixError{"M_MISSING_PARAM", msg}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnableToAuthoriseJoin is an error that is returned when a server can't
|
||||||
|
// determine whether to allow a restricted join or not.
|
||||||
|
func UnableToAuthoriseJoin(msg string) *MatrixError {
|
||||||
|
return &MatrixError{"M_UNABLE_TO_AUTHORISE_JOIN", msg}
|
||||||
|
}
|
||||||
|
|
||||||
// LeaveServerNoticeError is an error returned when trying to reject an invite
|
// LeaveServerNoticeError is an error returned when trying to reject an invite
|
||||||
// for a server notice room.
|
// for a server notice room.
|
||||||
func LeaveServerNoticeError() *MatrixError {
|
func LeaveServerNoticeError() *MatrixError {
|
||||||
|
@ -210,10 +210,18 @@ func (r *FederationInternalAPI) performJoinUsingServer(
|
|||||||
}
|
}
|
||||||
r.statistics.ForServer(serverName).Success()
|
r.statistics.ForServer(serverName).Success()
|
||||||
|
|
||||||
authEvents := respSendJoin.AuthEvents.UntrustedEvents(respMakeJoin.RoomVersion)
|
// If the remote server returned an event in the "event" key of
|
||||||
|
// the send_join request then we should use that instead. It may
|
||||||
|
// contain signatures that we don't know about.
|
||||||
|
if respSendJoin.Event != nil && isWellFormedMembershipEvent(
|
||||||
|
respSendJoin.Event, roomID, userID, r.cfg.Matrix.ServerName,
|
||||||
|
) {
|
||||||
|
event = respSendJoin.Event
|
||||||
|
}
|
||||||
|
|
||||||
// Sanity-check the join response to ensure that it has a create
|
// Sanity-check the join response to ensure that it has a create
|
||||||
// event, that the room version is known, etc.
|
// event, that the room version is known, etc.
|
||||||
|
authEvents := respSendJoin.AuthEvents.UntrustedEvents(respMakeJoin.RoomVersion)
|
||||||
if err = sanityCheckAuthChain(authEvents); err != nil {
|
if err = sanityCheckAuthChain(authEvents); err != nil {
|
||||||
return fmt.Errorf("sanityCheckAuthChain: %w", err)
|
return fmt.Errorf("sanityCheckAuthChain: %w", err)
|
||||||
}
|
}
|
||||||
@ -271,6 +279,26 @@ func (r *FederationInternalAPI) performJoinUsingServer(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isWellFormedMembershipEvent returns true if the event looks like a legitimate
|
||||||
|
// membership event.
|
||||||
|
func isWellFormedMembershipEvent(event *gomatrixserverlib.Event, roomID, userID string, origin gomatrixserverlib.ServerName) bool {
|
||||||
|
if membership, err := event.Membership(); err != nil {
|
||||||
|
return false
|
||||||
|
} else if membership != gomatrixserverlib.Join {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if event.RoomID() != roomID {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if event.Origin() != origin {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !event.StateKeyEquals(userID) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// PerformOutboundPeekRequest implements api.FederationInternalAPI
|
// PerformOutboundPeekRequest implements api.FederationInternalAPI
|
||||||
func (r *FederationInternalAPI) PerformOutboundPeek(
|
func (r *FederationInternalAPI) PerformOutboundPeek(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package routing
|
package routing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
@ -103,6 +104,16 @@ func MakeJoin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the restricted join is allowed. If the room doesn't
|
||||||
|
// support restricted joins then this is effectively a no-op.
|
||||||
|
res, authorisedVia, err := checkRestrictedJoin(httpReq, rsAPI, verRes.RoomVersion, roomID, userID)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger(httpReq.Context()).WithError(err).Error("checkRestrictedJoin failed")
|
||||||
|
return jsonerror.InternalServerError()
|
||||||
|
} else if res != nil {
|
||||||
|
return *res
|
||||||
|
}
|
||||||
|
|
||||||
// Try building an event for the server
|
// Try building an event for the server
|
||||||
builder := gomatrixserverlib.EventBuilder{
|
builder := gomatrixserverlib.EventBuilder{
|
||||||
Sender: userID,
|
Sender: userID,
|
||||||
@ -110,8 +121,11 @@ func MakeJoin(
|
|||||||
Type: "m.room.member",
|
Type: "m.room.member",
|
||||||
StateKey: &userID,
|
StateKey: &userID,
|
||||||
}
|
}
|
||||||
err = builder.SetContent(map[string]interface{}{"membership": gomatrixserverlib.Join})
|
content := gomatrixserverlib.MemberContent{
|
||||||
if err != nil {
|
Membership: gomatrixserverlib.Join,
|
||||||
|
AuthorisedVia: authorisedVia,
|
||||||
|
}
|
||||||
|
if err = builder.SetContent(content); err != nil {
|
||||||
util.GetLogger(httpReq.Context()).WithError(err).Error("builder.SetContent failed")
|
util.GetLogger(httpReq.Context()).WithError(err).Error("builder.SetContent failed")
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
@ -161,6 +175,7 @@ func MakeJoin(
|
|||||||
// SendJoin implements the /send_join API
|
// SendJoin implements the /send_join API
|
||||||
// The make-join send-join dance makes much more sense as a single
|
// The make-join send-join dance makes much more sense as a single
|
||||||
// flow so the cyclomatic complexity is high:
|
// flow so the cyclomatic complexity is high:
|
||||||
|
// nolint:gocyclo
|
||||||
func SendJoin(
|
func SendJoin(
|
||||||
httpReq *http.Request,
|
httpReq *http.Request,
|
||||||
request *gomatrixserverlib.FederationRequest,
|
request *gomatrixserverlib.FederationRequest,
|
||||||
@ -314,6 +329,40 @@ func SendJoin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the membership content contains a user ID for a server that is not
|
||||||
|
// ours then we should kick it back.
|
||||||
|
var memberContent gomatrixserverlib.MemberContent
|
||||||
|
if err := json.Unmarshal(event.Content(), &memberContent); err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.BadJSON(err.Error()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if memberContent.AuthorisedVia != "" {
|
||||||
|
_, domain, err := gomatrixserverlib.SplitID('@', memberContent.AuthorisedVia)
|
||||||
|
if err != nil {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.BadJSON(fmt.Sprintf("The authorising username %q is invalid.", memberContent.AuthorisedVia)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if domain != cfg.Matrix.ServerName {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.BadJSON(fmt.Sprintf("The authorising username %q does not belong to this server.", memberContent.AuthorisedVia)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the membership event. This is required for restricted joins to work
|
||||||
|
// in the case that the authorised via user is one of our own users. It also
|
||||||
|
// doesn't hurt to do it even if it isn't a restricted join.
|
||||||
|
signed := event.Sign(
|
||||||
|
string(cfg.Matrix.ServerName),
|
||||||
|
cfg.Matrix.KeyID,
|
||||||
|
cfg.Matrix.PrivateKey,
|
||||||
|
)
|
||||||
|
|
||||||
// Send the events to the room server.
|
// Send the events to the room server.
|
||||||
// We are responsible for notifying other servers that the user has joined
|
// We are responsible for notifying other servers that the user has joined
|
||||||
// the room, so set SendAsServer to cfg.Matrix.ServerName
|
// the room, so set SendAsServer to cfg.Matrix.ServerName
|
||||||
@ -323,7 +372,7 @@ func SendJoin(
|
|||||||
InputRoomEvents: []api.InputRoomEvent{
|
InputRoomEvents: []api.InputRoomEvent{
|
||||||
{
|
{
|
||||||
Kind: api.KindNew,
|
Kind: api.KindNew,
|
||||||
Event: event.Headered(stateAndAuthChainResponse.RoomVersion),
|
Event: signed.Headered(stateAndAuthChainResponse.RoomVersion),
|
||||||
SendAsServer: string(cfg.Matrix.ServerName),
|
SendAsServer: string(cfg.Matrix.ServerName),
|
||||||
TransactionID: nil,
|
TransactionID: nil,
|
||||||
},
|
},
|
||||||
@ -354,10 +403,77 @@ func SendJoin(
|
|||||||
StateEvents: gomatrixserverlib.NewEventJSONsFromHeaderedEvents(stateAndAuthChainResponse.StateEvents),
|
StateEvents: gomatrixserverlib.NewEventJSONsFromHeaderedEvents(stateAndAuthChainResponse.StateEvents),
|
||||||
AuthEvents: gomatrixserverlib.NewEventJSONsFromHeaderedEvents(stateAndAuthChainResponse.AuthChainEvents),
|
AuthEvents: gomatrixserverlib.NewEventJSONsFromHeaderedEvents(stateAndAuthChainResponse.AuthChainEvents),
|
||||||
Origin: cfg.Matrix.ServerName,
|
Origin: cfg.Matrix.ServerName,
|
||||||
|
Event: &signed,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkRestrictedJoin finds out whether or not we can assist in processing
|
||||||
|
// a restricted room join. If the room version does not support restricted
|
||||||
|
// joins then this function returns with no side effects. This returns three
|
||||||
|
// values:
|
||||||
|
// * an optional JSON response body (i.e. M_UNABLE_TO_AUTHORISE_JOIN) which
|
||||||
|
// should always be sent back to the client if one is specified
|
||||||
|
// * a user ID of an authorising user, typically a user that has power to
|
||||||
|
// issue invites in the room, if one has been found
|
||||||
|
// * an error if there was a problem finding out if this was allowable,
|
||||||
|
// like if the room version isn't known or a problem happened talking to
|
||||||
|
// the roomserver
|
||||||
|
func checkRestrictedJoin(
|
||||||
|
httpReq *http.Request,
|
||||||
|
rsAPI api.FederationRoomserverAPI,
|
||||||
|
roomVersion gomatrixserverlib.RoomVersion,
|
||||||
|
roomID, userID string,
|
||||||
|
) (*util.JSONResponse, string, error) {
|
||||||
|
if allowRestricted, err := roomVersion.AllowRestrictedJoinsInEventAuth(); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
} else if !allowRestricted {
|
||||||
|
return nil, "", nil
|
||||||
|
}
|
||||||
|
req := &api.QueryRestrictedJoinAllowedRequest{
|
||||||
|
RoomID: roomID,
|
||||||
|
UserID: userID,
|
||||||
|
}
|
||||||
|
res := &api.QueryRestrictedJoinAllowedResponse{}
|
||||||
|
if err := rsAPI.QueryRestrictedJoinAllowed(httpReq.Context(), req, res); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !res.Restricted:
|
||||||
|
// The join rules for the room don't restrict membership.
|
||||||
|
return nil, "", nil
|
||||||
|
|
||||||
|
case !res.Resident:
|
||||||
|
// The join rules restrict membership but our server isn't currently
|
||||||
|
// joined to all of the allowed rooms, so we can't actually decide
|
||||||
|
// whether or not to allow the user to join. This error code should
|
||||||
|
// tell the joining server to try joining via another resident server
|
||||||
|
// instead.
|
||||||
|
return &util.JSONResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
JSON: jsonerror.UnableToAuthoriseJoin("This server cannot authorise the join."),
|
||||||
|
}, "", nil
|
||||||
|
|
||||||
|
case !res.Allowed:
|
||||||
|
// The join rules restrict membership, our server is in the relevant
|
||||||
|
// rooms and the user wasn't joined to join any of the allowed rooms
|
||||||
|
// and therefore can't join this room.
|
||||||
|
return &util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: jsonerror.Forbidden("You are not joined to any matching rooms."),
|
||||||
|
}, "", nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
// The join rules restrict membership, our server is in the relevant
|
||||||
|
// rooms and the user was allowed to join because they belong to one
|
||||||
|
// of the allowed rooms. We now need to pick one of our own local users
|
||||||
|
// from within the room to use as the authorising user ID, so that it
|
||||||
|
// can be referred to from within the membership content.
|
||||||
|
return nil, res.AuthorisedVia, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type eventsByDepth []*gomatrixserverlib.HeaderedEvent
|
type eventsByDepth []*gomatrixserverlib.HeaderedEvent
|
||||||
|
|
||||||
func (e eventsByDepth) Len() int {
|
func (e eventsByDepth) Len() int {
|
||||||
|
@ -184,6 +184,7 @@ type FederationRoomserverAPI interface {
|
|||||||
// Query whether a server is allowed to see an event
|
// Query whether a server is allowed to see an event
|
||||||
QueryServerAllowedToSeeEvent(ctx context.Context, req *QueryServerAllowedToSeeEventRequest, res *QueryServerAllowedToSeeEventResponse) error
|
QueryServerAllowedToSeeEvent(ctx context.Context, req *QueryServerAllowedToSeeEventRequest, res *QueryServerAllowedToSeeEventResponse) error
|
||||||
QueryRoomsForUser(ctx context.Context, req *QueryRoomsForUserRequest, res *QueryRoomsForUserResponse) error
|
QueryRoomsForUser(ctx context.Context, req *QueryRoomsForUserRequest, res *QueryRoomsForUserResponse) error
|
||||||
|
QueryRestrictedJoinAllowed(ctx context.Context, req *QueryRestrictedJoinAllowedRequest, res *QueryRestrictedJoinAllowedResponse) error
|
||||||
PerformInboundPeek(ctx context.Context, req *PerformInboundPeekRequest, res *PerformInboundPeekResponse) error
|
PerformInboundPeek(ctx context.Context, req *PerformInboundPeekRequest, res *PerformInboundPeekResponse) error
|
||||||
PerformInvite(ctx context.Context, req *PerformInviteRequest, res *PerformInviteResponse) error
|
PerformInvite(ctx context.Context, req *PerformInviteRequest, res *PerformInviteResponse) error
|
||||||
// Query a given amount (or less) of events prior to a given set of events.
|
// Query a given amount (or less) of events prior to a given set of events.
|
||||||
|
@ -354,6 +354,16 @@ func (t *RoomserverInternalAPITrace) QueryAuthChain(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *RoomserverInternalAPITrace) QueryRestrictedJoinAllowed(
|
||||||
|
ctx context.Context,
|
||||||
|
request *QueryRestrictedJoinAllowedRequest,
|
||||||
|
response *QueryRestrictedJoinAllowedResponse,
|
||||||
|
) error {
|
||||||
|
err := t.Impl.QueryRestrictedJoinAllowed(ctx, request, response)
|
||||||
|
util.GetLogger(ctx).WithError(err).Infof("QueryRestrictedJoinAllowed req=%+v res=%+v", js(request), js(response))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func js(thing interface{}) string {
|
func js(thing interface{}) string {
|
||||||
b, err := json.Marshal(thing)
|
b, err := json.Marshal(thing)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -348,6 +348,26 @@ type QueryServerBannedFromRoomResponse struct {
|
|||||||
Banned bool `json:"banned"`
|
Banned bool `json:"banned"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QueryRestrictedJoinAllowedRequest struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
RoomID string `json:"room_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryRestrictedJoinAllowedResponse struct {
|
||||||
|
// True if the room membership is restricted by the join rule being set to "restricted"
|
||||||
|
Restricted bool `json:"restricted"`
|
||||||
|
// True if our local server is joined to all of the allowed rooms specified in the "allow"
|
||||||
|
// key of the join rule, false if we are missing from some of them and therefore can't
|
||||||
|
// reliably decide whether or not we can satisfy the join
|
||||||
|
Resident bool `json:"resident"`
|
||||||
|
// True if the restricted join is allowed because we found the membership in one of the
|
||||||
|
// allowed rooms from the join rule, false if not
|
||||||
|
Allowed bool `json:"allowed"`
|
||||||
|
// Contains the user ID of the selected user ID that has power to issue invites, this will
|
||||||
|
// get populated into the "join_authorised_via_users_server" content in the membership
|
||||||
|
AuthorisedVia string `json:"authorised_via,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalJSON stringifies the room ID and StateKeyTuple keys so they can be sent over the wire in HTTP API mode.
|
// MarshalJSON stringifies the room ID and StateKeyTuple keys so they can be sent over the wire in HTTP API mode.
|
||||||
func (r *QueryBulkStateContentResponse) MarshalJSON() ([]byte, error) {
|
func (r *QueryBulkStateContentResponse) MarshalJSON() ([]byte, error) {
|
||||||
se := make(map[string]string)
|
se := make(map[string]string)
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
fsAPI "github.com/matrix-org/dendrite/federationapi/api"
|
fsAPI "github.com/matrix-org/dendrite/federationapi/api"
|
||||||
"github.com/matrix-org/dendrite/internal/eventutil"
|
"github.com/matrix-org/dendrite/internal/eventutil"
|
||||||
|
"github.com/matrix-org/dendrite/roomserver/api"
|
||||||
rsAPI "github.com/matrix-org/dendrite/roomserver/api"
|
rsAPI "github.com/matrix-org/dendrite/roomserver/api"
|
||||||
"github.com/matrix-org/dendrite/roomserver/internal/helpers"
|
"github.com/matrix-org/dendrite/roomserver/internal/helpers"
|
||||||
"github.com/matrix-org/dendrite/roomserver/internal/input"
|
"github.com/matrix-org/dendrite/roomserver/internal/input"
|
||||||
@ -160,6 +161,7 @@ func (r *Joiner) performJoinRoomByAlias(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Break this function up a bit
|
// TODO: Break this function up a bit
|
||||||
|
// nolint:gocyclo
|
||||||
func (r *Joiner) performJoinRoomByID(
|
func (r *Joiner) performJoinRoomByID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *rsAPI.PerformJoinRequest,
|
req *rsAPI.PerformJoinRequest,
|
||||||
@ -210,6 +212,11 @@ func (r *Joiner) performJoinRoomByID(
|
|||||||
req.Content = map[string]interface{}{}
|
req.Content = map[string]interface{}{}
|
||||||
}
|
}
|
||||||
req.Content["membership"] = gomatrixserverlib.Join
|
req.Content["membership"] = gomatrixserverlib.Join
|
||||||
|
if authorisedVia, aerr := r.populateAuthorisedViaUserForRestrictedJoin(ctx, req); aerr != nil {
|
||||||
|
return "", "", aerr
|
||||||
|
} else if authorisedVia != "" {
|
||||||
|
req.Content["join_authorised_via_users_server"] = authorisedVia
|
||||||
|
}
|
||||||
if err = eb.SetContent(req.Content); err != nil {
|
if err = eb.SetContent(req.Content); err != nil {
|
||||||
return "", "", fmt.Errorf("eb.SetContent: %w", err)
|
return "", "", fmt.Errorf("eb.SetContent: %w", err)
|
||||||
}
|
}
|
||||||
@ -350,6 +357,33 @@ func (r *Joiner) performFederatedJoinRoomByID(
|
|||||||
return fedRes.JoinedVia, nil
|
return fedRes.JoinedVia, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Joiner) populateAuthorisedViaUserForRestrictedJoin(
|
||||||
|
ctx context.Context,
|
||||||
|
joinReq *rsAPI.PerformJoinRequest,
|
||||||
|
) (string, error) {
|
||||||
|
req := &api.QueryRestrictedJoinAllowedRequest{
|
||||||
|
UserID: joinReq.UserID,
|
||||||
|
RoomID: joinReq.RoomIDOrAlias,
|
||||||
|
}
|
||||||
|
res := &api.QueryRestrictedJoinAllowedResponse{}
|
||||||
|
if err := r.Queryer.QueryRestrictedJoinAllowed(ctx, req, res); err != nil {
|
||||||
|
return "", fmt.Errorf("r.Queryer.QueryRestrictedJoinAllowed: %w", err)
|
||||||
|
}
|
||||||
|
if !res.Restricted {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if !res.Resident {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if !res.Allowed {
|
||||||
|
return "", &rsAPI.PerformError{
|
||||||
|
Code: rsAPI.PerformErrorNotAllowed,
|
||||||
|
Msg: fmt.Sprintf("The join to room %s was not allowed.", joinReq.RoomIDOrAlias),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res.AuthorisedVia, nil
|
||||||
|
}
|
||||||
|
|
||||||
func buildEvent(
|
func buildEvent(
|
||||||
ctx context.Context, db storage.Database, cfg *config.Global, builder *gomatrixserverlib.EventBuilder,
|
ctx context.Context, db storage.Database, cfg *config.Global, builder *gomatrixserverlib.EventBuilder,
|
||||||
) (*gomatrixserverlib.HeaderedEvent, *rsAPI.QueryLatestEventsAndStateResponse, error) {
|
) (*gomatrixserverlib.HeaderedEvent, *rsAPI.QueryLatestEventsAndStateResponse, error) {
|
||||||
|
@ -16,6 +16,7 @@ package query
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
@ -757,3 +758,128 @@ func (r *Queryer) QueryAuthChain(ctx context.Context, req *api.QueryAuthChainReq
|
|||||||
res.AuthChain = hchain
|
res.AuthChain = hchain
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nolint:gocyclo
|
||||||
|
func (r *Queryer) QueryRestrictedJoinAllowed(ctx context.Context, req *api.QueryRestrictedJoinAllowedRequest, res *api.QueryRestrictedJoinAllowedResponse) error {
|
||||||
|
// Look up if we know anything about the room. If it doesn't exist
|
||||||
|
// or is a stub entry then we can't do anything.
|
||||||
|
roomInfo, err := r.DB.RoomInfo(ctx, req.RoomID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("r.DB.RoomInfo: %w", err)
|
||||||
|
}
|
||||||
|
if roomInfo == nil || roomInfo.IsStub {
|
||||||
|
return nil // fmt.Errorf("room %q doesn't exist or is stub room", req.RoomID)
|
||||||
|
}
|
||||||
|
// If the room version doesn't allow restricted joins then don't
|
||||||
|
// try to process any further.
|
||||||
|
allowRestrictedJoins, err := roomInfo.RoomVersion.AllowRestrictedJoinsInEventAuth()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("roomInfo.RoomVersion.AllowRestrictedJoinsInEventAuth: %w", err)
|
||||||
|
} else if !allowRestrictedJoins {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Get the join rules to work out if the join rule is "restricted".
|
||||||
|
joinRulesEvent, err := r.DB.GetStateEvent(ctx, req.RoomID, gomatrixserverlib.MRoomJoinRules, "")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("r.DB.GetStateEvent: %w", err)
|
||||||
|
}
|
||||||
|
var joinRules gomatrixserverlib.JoinRuleContent
|
||||||
|
if err = json.Unmarshal(joinRulesEvent.Content(), &joinRules); err != nil {
|
||||||
|
return fmt.Errorf("json.Unmarshal: %w", err)
|
||||||
|
}
|
||||||
|
// If the join rule isn't "restricted" then there's nothing more to do.
|
||||||
|
res.Restricted = joinRules.JoinRule == gomatrixserverlib.Restricted
|
||||||
|
if !res.Restricted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Start off by populating the "resident" flag in the response. If we
|
||||||
|
// come across any rooms in the request that are missing, we will unset
|
||||||
|
// the flag.
|
||||||
|
res.Resident = true
|
||||||
|
// If the user is already invited to the room then the join is allowed
|
||||||
|
// but we don't specify an authorised via user, since the event auth
|
||||||
|
// will allow the join anyway.
|
||||||
|
var pending bool
|
||||||
|
if pending, _, _, err = helpers.IsInvitePending(ctx, r.DB, req.RoomID, req.UserID); err != nil {
|
||||||
|
return fmt.Errorf("helpers.IsInvitePending: %w", err)
|
||||||
|
} else if pending {
|
||||||
|
res.Allowed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// We need to get the power levels content so that we can determine which
|
||||||
|
// users in the room are entitled to issue invites. We need to use one of
|
||||||
|
// these users as the authorising user.
|
||||||
|
powerLevelsEvent, err := r.DB.GetStateEvent(ctx, req.RoomID, gomatrixserverlib.MRoomPowerLevels, "")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("r.DB.GetStateEvent: %w", err)
|
||||||
|
}
|
||||||
|
var powerLevels gomatrixserverlib.PowerLevelContent
|
||||||
|
if err = json.Unmarshal(powerLevelsEvent.Content(), &powerLevels); err != nil {
|
||||||
|
return fmt.Errorf("json.Unmarshal: %w", err)
|
||||||
|
}
|
||||||
|
// Step through the join rules and see if the user matches any of them.
|
||||||
|
for _, rule := range joinRules.Allow {
|
||||||
|
// We only understand "m.room_membership" rules at this point in
|
||||||
|
// time, so skip any rule that doesn't match those.
|
||||||
|
if rule.Type != gomatrixserverlib.MRoomMembership {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// See if the room exists. If it doesn't exist or if it's a stub
|
||||||
|
// room entry then we can't check memberships.
|
||||||
|
targetRoomInfo, err := r.DB.RoomInfo(ctx, rule.RoomID)
|
||||||
|
if err != nil || targetRoomInfo == nil || targetRoomInfo.IsStub {
|
||||||
|
res.Resident = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// First of all work out if *we* are still in the room, otherwise
|
||||||
|
// it's possible that the memberships will be out of date.
|
||||||
|
isIn, err := r.DB.GetLocalServerInRoom(ctx, targetRoomInfo.RoomNID)
|
||||||
|
if err != nil || !isIn {
|
||||||
|
// If we aren't in the room, we can no longer tell if the room
|
||||||
|
// memberships are up-to-date.
|
||||||
|
res.Resident = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// At this point we're happy that we are in the room, so now let's
|
||||||
|
// see if the target user is in the room.
|
||||||
|
_, isIn, _, err = r.DB.GetMembership(ctx, targetRoomInfo.RoomNID, req.UserID)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If the user is not in the room then we will skip them.
|
||||||
|
if !isIn {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The user is in the room, so now we will need to authorise the
|
||||||
|
// join using the user ID of one of our own users in the room. Pick
|
||||||
|
// one.
|
||||||
|
joinNIDs, err := r.DB.GetMembershipEventNIDsForRoom(ctx, targetRoomInfo.RoomNID, true, true)
|
||||||
|
if err != nil || len(joinNIDs) == 0 {
|
||||||
|
// There should always be more than one join NID at this point
|
||||||
|
// because we are gated behind GetLocalServerInRoom, but y'know,
|
||||||
|
// sometimes strange things happen.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// For each of the joined users, let's see if we can get a valid
|
||||||
|
// membership event.
|
||||||
|
for _, joinNID := range joinNIDs {
|
||||||
|
events, err := r.DB.Events(ctx, []types.EventNID{joinNID})
|
||||||
|
if err != nil || len(events) != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := events[0]
|
||||||
|
if event.Type() != gomatrixserverlib.MRoomMember || event.StateKey() == nil {
|
||||||
|
continue // shouldn't happen
|
||||||
|
}
|
||||||
|
// Only users that have the power to invite should be chosen.
|
||||||
|
if powerLevels.UserLevel(*event.StateKey()) < powerLevels.Invite {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res.Resident = true
|
||||||
|
res.Allowed = true
|
||||||
|
res.AuthorisedVia = *event.StateKey()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -61,6 +61,7 @@ const (
|
|||||||
RoomserverQueryKnownUsersPath = "/roomserver/queryKnownUsers"
|
RoomserverQueryKnownUsersPath = "/roomserver/queryKnownUsers"
|
||||||
RoomserverQueryServerBannedFromRoomPath = "/roomserver/queryServerBannedFromRoom"
|
RoomserverQueryServerBannedFromRoomPath = "/roomserver/queryServerBannedFromRoom"
|
||||||
RoomserverQueryAuthChainPath = "/roomserver/queryAuthChain"
|
RoomserverQueryAuthChainPath = "/roomserver/queryAuthChain"
|
||||||
|
RoomserverQueryRestrictedJoinAllowed = "/roomserver/queryRestrictedJoinAllowed"
|
||||||
)
|
)
|
||||||
|
|
||||||
type httpRoomserverInternalAPI struct {
|
type httpRoomserverInternalAPI struct {
|
||||||
@ -557,6 +558,16 @@ func (h *httpRoomserverInternalAPI) QueryServerBannedFromRoom(
|
|||||||
return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res)
|
return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *httpRoomserverInternalAPI) QueryRestrictedJoinAllowed(
|
||||||
|
ctx context.Context, req *api.QueryRestrictedJoinAllowedRequest, res *api.QueryRestrictedJoinAllowedResponse,
|
||||||
|
) error {
|
||||||
|
span, ctx := opentracing.StartSpanFromContext(ctx, "QueryRestrictedJoinAllowed")
|
||||||
|
defer span.Finish()
|
||||||
|
|
||||||
|
apiURL := h.roomserverURL + RoomserverQueryRestrictedJoinAllowed
|
||||||
|
return httputil.PostJSON(ctx, span, h.httpClient, apiURL, req, res)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *httpRoomserverInternalAPI) PerformForget(ctx context.Context, req *api.PerformForgetRequest, res *api.PerformForgetResponse) error {
|
func (h *httpRoomserverInternalAPI) PerformForget(ctx context.Context, req *api.PerformForgetRequest, res *api.PerformForgetResponse) error {
|
||||||
span, ctx := opentracing.StartSpanFromContext(ctx, "PerformForget")
|
span, ctx := opentracing.StartSpanFromContext(ctx, "PerformForget")
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
|
@ -472,4 +472,17 @@ func AddRoutes(r api.RoomserverInternalAPI, internalAPIMux *mux.Router) {
|
|||||||
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
internalAPIMux.Handle(RoomserverQueryRestrictedJoinAllowed,
|
||||||
|
httputil.MakeInternalAPI("queryRestrictedJoinAllowed", func(req *http.Request) util.JSONResponse {
|
||||||
|
request := api.QueryRestrictedJoinAllowedRequest{}
|
||||||
|
response := api.QueryRestrictedJoinAllowedResponse{}
|
||||||
|
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
|
||||||
|
return util.MessageResponse(http.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
if err := r.QueryRestrictedJoinAllowed(req.Context(), &request, &response); err != nil {
|
||||||
|
return util.ErrorResponse(err)
|
||||||
|
}
|
||||||
|
return util.JSONResponse{Code: http.StatusOK, JSON: &response}
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user