aboutsummaryrefslogtreecommitdiff
path: root/roomserver/internal/query
diff options
context:
space:
mode:
authorNeil Alexander <neilalexander@users.noreply.github.com>2022-05-25 10:05:30 +0100
committerGitHub <noreply@github.com>2022-05-25 10:05:30 +0100
commit81843e8836e6f8f334c7b5fd1433c427c10a9443 (patch)
treeda86bc862d46103b955431961df95fab90c199b6 /roomserver/internal/query
parentd621dd2986fa0b8cce9d164a7249456d0be47c81 (diff)
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
Diffstat (limited to 'roomserver/internal/query')
-rw-r--r--roomserver/internal/query/query.go126
1 files changed, 126 insertions, 0 deletions
diff --git a/roomserver/internal/query/query.go b/roomserver/internal/query/query.go
index d25bdc37..70613baa 100644
--- a/roomserver/internal/query/query.go
+++ b/roomserver/internal/query/query.go
@@ -16,6 +16,7 @@ package query
import (
"context"
+ "encoding/json"
"errors"
"fmt"
@@ -757,3 +758,128 @@ func (r *Queryer) QueryAuthChain(ctx context.Context, req *api.QueryAuthChainReq
res.AuthChain = hchain
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
+}