diff --git a/src/github.com/matrix-org/dendrite/federationapi/readers/events.go b/src/github.com/matrix-org/dendrite/federationapi/readers/events.go new file mode 100644 index 00000000..e161f54c --- /dev/null +++ b/src/github.com/matrix-org/dendrite/federationapi/readers/events.go @@ -0,0 +1,65 @@ +// Copyright 2017 New Vector Ltd +// +// 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 readers + +import ( + "net/http" + "time" + + "github.com/matrix-org/dendrite/common/config" + "github.com/matrix-org/dendrite/roomserver/api" + "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/util" +) + +// GetEvent returns the requested event +func GetEvent( + httpReq *http.Request, + request *gomatrixserverlib.FederationRequest, + cfg config.Dendrite, + query api.RoomserverQueryAPI, + now time.Time, + keys gomatrixserverlib.KeyRing, + eventID string, +) util.JSONResponse { + var authResponse api.QueryServerAllowedToSeeEventResponse + err := query.QueryServerAllowedToSeeEvent(&api.QueryServerAllowedToSeeEventRequest{ + EventID: eventID, + ServerName: request.Origin(), + }, &authResponse) + if err != nil { + return util.ErrorResponse(err) + } + + if !authResponse.AllowedToSeeEvent { + return util.MessageResponse(403, "server not allowed to see event") + } + + var eventsResponse api.QueryEventsByIDResponse + err = query.QueryEventsByID( + &api.QueryEventsByIDRequest{EventIDs: []string{eventID}}, + &eventsResponse, + ) + + if err != nil { + return util.ErrorResponse(err) + } + + if len(eventsResponse.Events) == 0 { + return util.JSONResponse{Code: 404, JSON: nil} + } + + return util.JSONResponse{Code: 200, JSON: &eventsResponse.Events[0]} +} diff --git a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go index 02508907..2f753055 100644 --- a/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/federationapi/routing/routing.go @@ -16,6 +16,7 @@ package routing import ( "net/http" + "time" "github.com/gorilla/mux" "github.com/matrix-org/dendrite/clientapi/producers" @@ -77,4 +78,14 @@ func Setup( ) }, )) + + v1fedmux.Handle("/event/{eventID}", common.MakeFedAPI( + "federation_get_event", cfg.Matrix.ServerName, keys, + func(httpReq *http.Request, request *gomatrixserverlib.FederationRequest) util.JSONResponse { + vars := mux.Vars(httpReq) + return readers.GetEvent( + httpReq, request, cfg, query, time.Now(), keys, vars["eventID"], + ) + }, + )) } diff --git a/src/github.com/matrix-org/dendrite/roomserver/api/query.go b/src/github.com/matrix-org/dendrite/roomserver/api/query.go index c26955ab..41c7821d 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/api/query.go +++ b/src/github.com/matrix-org/dendrite/roomserver/api/query.go @@ -136,6 +136,20 @@ type QueryInvitesForUserResponse struct { InviteSenderUserIDs []string `json:"invite_sender_user_ids"` } +// QueryServerAllowedToSeeEventRequest is a request to QueryServerAllowedToSeeEvent +type QueryServerAllowedToSeeEventRequest struct { + // The event ID to look up invites in. + EventID string `json:"event_id"` + // The server interested in the event + ServerName gomatrixserverlib.ServerName `json:"server_name"` +} + +// QueryServerAllowedToSeeEventResponse is a response to QueryServerAllowedToSeeEvent +type QueryServerAllowedToSeeEventResponse struct { + // Wether the server in question is allowed to see the event + AllowedToSeeEvent bool `json:"can_see_event"` +} + // RoomserverQueryAPI is used to query information from the room server. type RoomserverQueryAPI interface { // Query the latest events and state for a room from the room server. @@ -167,6 +181,12 @@ type RoomserverQueryAPI interface { request *QueryInvitesForUserRequest, response *QueryInvitesForUserResponse, ) error + + // Query whether a server is allowed to see an event + QueryServerAllowedToSeeEvent( + request *QueryServerAllowedToSeeEventRequest, + response *QueryServerAllowedToSeeEventResponse, + ) error } // RoomserverQueryLatestEventsAndStatePath is the HTTP path for the QueryLatestEventsAndState API. @@ -184,6 +204,9 @@ const RoomserverQueryMembershipsForRoomPath = "/api/roomserver/queryMembershipsF // RoomserverQueryInvitesForUserPath is the HTTP path for the QueryInvitesForUser API const RoomserverQueryInvitesForUserPath = "/api/roomserver/queryInvitesForUser" +// RoomserverQueryServerAllowedToSeeEventPath is the HTTP path for the QueryServerAllowedToSeeEvent API +const RoomserverQueryServerAllowedToSeeEventPath = "/api/roomserver/queryServerAllowedToSeeEvent" + // NewRoomserverQueryAPIHTTP creates a RoomserverQueryAPI implemented by talking to a HTTP POST API. // If httpClient is nil then it uses the http.DefaultClient func NewRoomserverQueryAPIHTTP(roomserverURL string, httpClient *http.Client) RoomserverQueryAPI { @@ -243,6 +266,15 @@ func (h *httpRoomserverQueryAPI) QueryInvitesForUser( return postJSON(h.httpClient, apiURL, request, response) } +// QueryServerAllowedToSeeEvent implements RoomserverQueryAPI +func (h *httpRoomserverQueryAPI) QueryServerAllowedToSeeEvent( + request *QueryServerAllowedToSeeEventRequest, + response *QueryServerAllowedToSeeEventResponse, +) error { + apiURL := h.roomserverURL + RoomserverQueryServerAllowedToSeeEventPath + return postJSON(h.httpClient, apiURL, request, response) +} + func postJSON(httpClient *http.Client, apiURL string, request, response interface{}) error { jsonBytes, err := json.Marshal(request) if err != nil { diff --git a/src/github.com/matrix-org/dendrite/roomserver/query/query.go b/src/github.com/matrix-org/dendrite/roomserver/query/query.go index cbbfa63a..be355eb2 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/query/query.go +++ b/src/github.com/matrix-org/dendrite/roomserver/query/query.go @@ -330,7 +330,66 @@ func (r *RoomserverQueryAPI) QueryInvitesForUser( return nil } +// QueryServerAllowedToSeeEvent implements api.RoomserverQueryAPI +func (r *RoomserverQueryAPI) QueryServerAllowedToSeeEvent( + request *api.QueryServerAllowedToSeeEventRequest, + response *api.QueryServerAllowedToSeeEventResponse, +) error { + stateEntries, err := state.LoadStateAtEvent(r.DB, request.EventID) + if err != nil { + return err + } + + // TODO: We probably want to make it so that we don't have to pull + // out all the state if possible. + stateAtEvent, err := r.loadStateEvents(stateEntries) + if err != nil { + return err + } + + // TODO: Should this be lifted out of here to a more general set of + // auth functions? + + isInRoom := false + for _, ev := range stateAtEvent { + membership, err := ev.Membership() + if err != nil { + continue + } + + if membership != "join" { + continue + } + + stateKey := ev.StateKey() + if stateKey == nil { + continue + } + + _, domain, err := gomatrixserverlib.SplitID('@', *stateKey) + if err != nil { + continue + } + + if domain == request.ServerName { + isInRoom = true + break + } + } + + if isInRoom { + response.AllowedToSeeEvent = true + return nil + } + + // TODO: Check if history visibility is shared and if the server is currently in the room + + response.AllowedToSeeEvent = false + return nil +} + // SetupHTTP adds the RoomserverQueryAPI handlers to the http.ServeMux. +// nolint: gocyclo func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) { servMux.Handle( api.RoomserverQueryLatestEventsAndStatePath, @@ -402,4 +461,18 @@ func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) { return util.JSONResponse{Code: 200, JSON: &response} }), ) + servMux.Handle( + api.RoomserverQueryServerAllowedToSeeEventPath, + common.MakeAPI("queryServerAllowedToSeeEvent", func(req *http.Request) util.JSONResponse { + var request api.QueryServerAllowedToSeeEventRequest + var response api.QueryServerAllowedToSeeEventResponse + if err := json.NewDecoder(req.Body).Decode(&request); err != nil { + return util.ErrorResponse(err) + } + if err := r.QueryServerAllowedToSeeEvent(&request, &response); err != nil { + return util.ErrorResponse(err) + } + return util.JSONResponse{Code: 200, JSON: &response} + }), + ) } diff --git a/src/github.com/matrix-org/dendrite/roomserver/state/state.go b/src/github.com/matrix-org/dendrite/roomserver/state/state.go index 3c2780ae..0323be37 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/state/state.go +++ b/src/github.com/matrix-org/dendrite/roomserver/state/state.go @@ -18,12 +18,13 @@ package state import ( "fmt" + "sort" + "time" + "github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/util" "github.com/prometheus/client_golang/prometheus" - "sort" - "time" ) // A RoomStateDatabase has the storage APIs needed to load state from the database @@ -56,6 +57,8 @@ type RoomStateDatabase interface { // Look up the Events for a list of numeric event IDs. // Returns a sorted list of events. Events(eventNIDs []types.EventNID) ([]types.Event, error) + // Look up snapshot NID for an event ID string + SnapshotNIDFromEventID(eventID string) (types.StateSnapshotNID, error) } // LoadStateAtSnapshot loads the full state of a room at a particular snapshot. @@ -96,6 +99,21 @@ func LoadStateAtSnapshot(db RoomStateDatabase, stateNID types.StateSnapshotNID) return fullState, nil } +// LoadStateAtEvent loads the full state of a room at a particular event. +func LoadStateAtEvent(db RoomStateDatabase, eventID string) ([]types.StateEntry, error) { + snapshotNID, err := db.SnapshotNIDFromEventID(eventID) + if err != nil { + return nil, err + } + + stateEntries, err := LoadStateAtSnapshot(db, snapshotNID) + if err != nil { + return nil, err + } + + return stateEntries, nil +} + // LoadCombinedStateAfterEvents loads a snapshot of the state after each of the events // and combines those snapshots together into a single list. func LoadCombinedStateAfterEvents(db RoomStateDatabase, prevStates []types.StateAtEvent) ([]types.StateEntry, error) { diff --git a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go index 8a0523e5..85c5160a 100644 --- a/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go +++ b/src/github.com/matrix-org/dendrite/roomserver/storage/storage.go @@ -226,6 +226,12 @@ func (d *Database) StateEntries(stateBlockNIDs []types.StateBlockNID) ([]types.S return d.statements.bulkSelectStateBlockEntries(stateBlockNIDs) } +// SnapshotNIDFromEventID implements state.RoomStateDatabase +func (d *Database) SnapshotNIDFromEventID(eventID string) (types.StateSnapshotNID, error) { + _, stateNID, err := d.statements.selectEvent(eventID) + return stateNID, err +} + // EventIDs implements input.RoomEventDatabase func (d *Database) EventIDs(eventNIDs []types.EventNID) (map[types.EventNID]string, error) { return d.statements.bulkSelectEventID(eventNIDs)