1// Copyright 2020 The Matrix.org Foundation C.I.C. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package api 16 17import ( 18 "context" 19 "fmt" 20 21 "github.com/matrix-org/gomatrixserverlib" 22 "github.com/matrix-org/util" 23) 24 25// SendEvents to the roomserver The events are written with KindNew. 26func SendEvents( 27 ctx context.Context, rsAPI RoomserverInternalAPI, 28 kind Kind, events []*gomatrixserverlib.HeaderedEvent, 29 sendAsServer gomatrixserverlib.ServerName, txnID *TransactionID, 30) error { 31 ires := make([]InputRoomEvent, len(events)) 32 for i, event := range events { 33 ires[i] = InputRoomEvent{ 34 Kind: kind, 35 Event: event, 36 AuthEventIDs: event.AuthEventIDs(), 37 SendAsServer: string(sendAsServer), 38 TransactionID: txnID, 39 } 40 } 41 return SendInputRoomEvents(ctx, rsAPI, ires) 42} 43 44// SendEventWithState writes an event with the specified kind to the roomserver 45// with the state at the event as KindOutlier before it. Will not send any event that is 46// marked as `true` in haveEventIDs. 47func SendEventWithState( 48 ctx context.Context, rsAPI RoomserverInternalAPI, kind Kind, 49 state *gomatrixserverlib.RespState, event *gomatrixserverlib.HeaderedEvent, 50 haveEventIDs map[string]bool, 51) error { 52 outliers, err := state.Events() 53 if err != nil { 54 return err 55 } 56 57 var ires []InputRoomEvent 58 for _, outlier := range outliers { 59 if haveEventIDs[outlier.EventID()] { 60 continue 61 } 62 ires = append(ires, InputRoomEvent{ 63 Kind: KindOutlier, 64 Event: outlier.Headered(event.RoomVersion), 65 AuthEventIDs: outlier.AuthEventIDs(), 66 }) 67 } 68 69 stateEventIDs := make([]string, len(state.StateEvents)) 70 for i := range state.StateEvents { 71 stateEventIDs[i] = state.StateEvents[i].EventID() 72 } 73 74 ires = append(ires, InputRoomEvent{ 75 Kind: kind, 76 Event: event, 77 AuthEventIDs: event.AuthEventIDs(), 78 HasState: true, 79 StateEventIDs: stateEventIDs, 80 }) 81 82 return SendInputRoomEvents(ctx, rsAPI, ires) 83} 84 85// SendInputRoomEvents to the roomserver. 86func SendInputRoomEvents( 87 ctx context.Context, rsAPI RoomserverInternalAPI, ires []InputRoomEvent, 88) error { 89 request := InputRoomEventsRequest{InputRoomEvents: ires} 90 var response InputRoomEventsResponse 91 rsAPI.InputRoomEvents(ctx, &request, &response) 92 return response.Err() 93} 94 95// SendInvite event to the roomserver. 96// This should only be needed for invite events that occur outside of a known room. 97// If we are in the room then the event should be sent using the SendEvents method. 98func SendInvite( 99 ctx context.Context, 100 rsAPI RoomserverInternalAPI, inviteEvent *gomatrixserverlib.HeaderedEvent, 101 inviteRoomState []gomatrixserverlib.InviteV2StrippedState, 102 sendAsServer gomatrixserverlib.ServerName, txnID *TransactionID, 103) error { 104 // Start by sending the invite request into the roomserver. This will 105 // trigger the federation request amongst other things if needed. 106 request := &PerformInviteRequest{ 107 Event: inviteEvent, 108 InviteRoomState: inviteRoomState, 109 RoomVersion: inviteEvent.RoomVersion, 110 SendAsServer: string(sendAsServer), 111 TransactionID: txnID, 112 } 113 response := &PerformInviteResponse{} 114 if err := rsAPI.PerformInvite(ctx, request, response); err != nil { 115 return fmt.Errorf("rsAPI.PerformInvite: %w", err) 116 } 117 if response.Error != nil { 118 return response.Error 119 } 120 121 return nil 122} 123 124// GetEvent returns the event or nil, even on errors. 125func GetEvent(ctx context.Context, rsAPI RoomserverInternalAPI, eventID string) *gomatrixserverlib.HeaderedEvent { 126 var res QueryEventsByIDResponse 127 err := rsAPI.QueryEventsByID(ctx, &QueryEventsByIDRequest{ 128 EventIDs: []string{eventID}, 129 }, &res) 130 if err != nil { 131 util.GetLogger(ctx).WithError(err).Error("Failed to QueryEventsByID") 132 return nil 133 } 134 if len(res.Events) != 1 { 135 return nil 136 } 137 return res.Events[0] 138} 139 140// GetStateEvent returns the current state event in the room or nil. 141func GetStateEvent(ctx context.Context, rsAPI RoomserverInternalAPI, roomID string, tuple gomatrixserverlib.StateKeyTuple) *gomatrixserverlib.HeaderedEvent { 142 var res QueryCurrentStateResponse 143 err := rsAPI.QueryCurrentState(ctx, &QueryCurrentStateRequest{ 144 RoomID: roomID, 145 StateTuples: []gomatrixserverlib.StateKeyTuple{tuple}, 146 }, &res) 147 if err != nil { 148 util.GetLogger(ctx).WithError(err).Error("Failed to QueryCurrentState") 149 return nil 150 } 151 ev, ok := res.StateEvents[tuple] 152 if ok { 153 return ev 154 } 155 return nil 156} 157 158// IsServerBannedFromRoom returns whether the server is banned from a room by server ACLs. 159func IsServerBannedFromRoom(ctx context.Context, rsAPI RoomserverInternalAPI, roomID string, serverName gomatrixserverlib.ServerName) bool { 160 req := &QueryServerBannedFromRoomRequest{ 161 ServerName: serverName, 162 RoomID: roomID, 163 } 164 res := &QueryServerBannedFromRoomResponse{} 165 if err := rsAPI.QueryServerBannedFromRoom(ctx, req, res); err != nil { 166 util.GetLogger(ctx).WithError(err).Error("Failed to QueryServerBannedFromRoom") 167 return true 168 } 169 return res.Banned 170} 171 172// PopulatePublicRooms extracts PublicRoom information for all the provided room IDs. The IDs are not checked to see if they are visible in the 173// published room directory. 174// due to lots of switches 175func PopulatePublicRooms(ctx context.Context, roomIDs []string, rsAPI RoomserverInternalAPI) ([]gomatrixserverlib.PublicRoom, error) { 176 avatarTuple := gomatrixserverlib.StateKeyTuple{EventType: "m.room.avatar", StateKey: ""} 177 nameTuple := gomatrixserverlib.StateKeyTuple{EventType: "m.room.name", StateKey: ""} 178 canonicalTuple := gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomCanonicalAlias, StateKey: ""} 179 topicTuple := gomatrixserverlib.StateKeyTuple{EventType: "m.room.topic", StateKey: ""} 180 guestTuple := gomatrixserverlib.StateKeyTuple{EventType: "m.room.guest_access", StateKey: ""} 181 visibilityTuple := gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomHistoryVisibility, StateKey: ""} 182 joinRuleTuple := gomatrixserverlib.StateKeyTuple{EventType: gomatrixserverlib.MRoomJoinRules, StateKey: ""} 183 184 var stateRes QueryBulkStateContentResponse 185 err := rsAPI.QueryBulkStateContent(ctx, &QueryBulkStateContentRequest{ 186 RoomIDs: roomIDs, 187 AllowWildcards: true, 188 StateTuples: []gomatrixserverlib.StateKeyTuple{ 189 nameTuple, canonicalTuple, topicTuple, guestTuple, visibilityTuple, joinRuleTuple, avatarTuple, 190 {EventType: gomatrixserverlib.MRoomMember, StateKey: "*"}, 191 }, 192 }, &stateRes) 193 if err != nil { 194 util.GetLogger(ctx).WithError(err).Error("QueryBulkStateContent failed") 195 return nil, err 196 } 197 chunk := make([]gomatrixserverlib.PublicRoom, len(roomIDs)) 198 i := 0 199 for roomID, data := range stateRes.Rooms { 200 pub := gomatrixserverlib.PublicRoom{ 201 RoomID: roomID, 202 } 203 joinCount := 0 204 var joinRule, guestAccess string 205 for tuple, contentVal := range data { 206 if tuple.EventType == gomatrixserverlib.MRoomMember && contentVal == "join" { 207 joinCount++ 208 continue 209 } 210 switch tuple { 211 case avatarTuple: 212 pub.AvatarURL = contentVal 213 case nameTuple: 214 pub.Name = contentVal 215 case topicTuple: 216 pub.Topic = contentVal 217 case canonicalTuple: 218 if _, _, err := gomatrixserverlib.SplitID('#', contentVal); err == nil { 219 pub.CanonicalAlias = contentVal 220 } 221 case visibilityTuple: 222 pub.WorldReadable = contentVal == "world_readable" 223 // need both of these to determine whether guests can join 224 case joinRuleTuple: 225 joinRule = contentVal 226 case guestTuple: 227 guestAccess = contentVal 228 } 229 } 230 if joinRule == gomatrixserverlib.Public && guestAccess == "can_join" { 231 pub.GuestCanJoin = true 232 } 233 pub.JoinedMembersCount = joinCount 234 chunk[i] = pub 235 i++ 236 } 237 return chunk, nil 238} 239