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