Add get event federation API (#209)

* Add get event federation API

* Handle err correctly

* Add missing readers

* Ignore gocyclo lint for routing function

* Add todo

* Fix styling a bit
This commit is contained in:
Erik Johnston 2017-09-06 12:38:22 +01:00 committed by Mark Haines
parent 2dcb3a11a5
commit ea80ffb3af
6 changed files with 207 additions and 2 deletions

View File

@ -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]}
}

View File

@ -16,6 +16,7 @@ package routing
import ( import (
"net/http" "net/http"
"time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/matrix-org/dendrite/clientapi/producers" "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"],
)
},
))
} }

View File

@ -136,6 +136,20 @@ type QueryInvitesForUserResponse struct {
InviteSenderUserIDs []string `json:"invite_sender_user_ids"` 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. // RoomserverQueryAPI is used to query information from the room server.
type RoomserverQueryAPI interface { type RoomserverQueryAPI interface {
// Query the latest events and state for a room from the room server. // Query the latest events and state for a room from the room server.
@ -167,6 +181,12 @@ type RoomserverQueryAPI interface {
request *QueryInvitesForUserRequest, request *QueryInvitesForUserRequest,
response *QueryInvitesForUserResponse, response *QueryInvitesForUserResponse,
) error ) 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. // 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 // RoomserverQueryInvitesForUserPath is the HTTP path for the QueryInvitesForUser API
const RoomserverQueryInvitesForUserPath = "/api/roomserver/queryInvitesForUser" 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. // NewRoomserverQueryAPIHTTP creates a RoomserverQueryAPI implemented by talking to a HTTP POST API.
// If httpClient is nil then it uses the http.DefaultClient // If httpClient is nil then it uses the http.DefaultClient
func NewRoomserverQueryAPIHTTP(roomserverURL string, httpClient *http.Client) RoomserverQueryAPI { func NewRoomserverQueryAPIHTTP(roomserverURL string, httpClient *http.Client) RoomserverQueryAPI {
@ -243,6 +266,15 @@ func (h *httpRoomserverQueryAPI) QueryInvitesForUser(
return postJSON(h.httpClient, apiURL, request, response) 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 { func postJSON(httpClient *http.Client, apiURL string, request, response interface{}) error {
jsonBytes, err := json.Marshal(request) jsonBytes, err := json.Marshal(request)
if err != nil { if err != nil {

View File

@ -330,7 +330,66 @@ func (r *RoomserverQueryAPI) QueryInvitesForUser(
return nil 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. // SetupHTTP adds the RoomserverQueryAPI handlers to the http.ServeMux.
// nolint: gocyclo
func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) { func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
servMux.Handle( servMux.Handle(
api.RoomserverQueryLatestEventsAndStatePath, api.RoomserverQueryLatestEventsAndStatePath,
@ -402,4 +461,18 @@ func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
return util.JSONResponse{Code: 200, JSON: &response} 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}
}),
)
} }

View File

@ -18,12 +18,13 @@ package state
import ( import (
"fmt" "fmt"
"sort"
"time"
"github.com/matrix-org/dendrite/roomserver/types" "github.com/matrix-org/dendrite/roomserver/types"
"github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util" "github.com/matrix-org/util"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"sort"
"time"
) )
// A RoomStateDatabase has the storage APIs needed to load state from the database // 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. // Look up the Events for a list of numeric event IDs.
// Returns a sorted list of events. // Returns a sorted list of events.
Events(eventNIDs []types.EventNID) ([]types.Event, error) 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. // 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 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 // LoadCombinedStateAfterEvents loads a snapshot of the state after each of the events
// and combines those snapshots together into a single list. // and combines those snapshots together into a single list.
func LoadCombinedStateAfterEvents(db RoomStateDatabase, prevStates []types.StateAtEvent) ([]types.StateEntry, error) { func LoadCombinedStateAfterEvents(db RoomStateDatabase, prevStates []types.StateAtEvent) ([]types.StateEntry, error) {

View File

@ -226,6 +226,12 @@ func (d *Database) StateEntries(stateBlockNIDs []types.StateBlockNID) ([]types.S
return d.statements.bulkSelectStateBlockEntries(stateBlockNIDs) 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 // EventIDs implements input.RoomEventDatabase
func (d *Database) EventIDs(eventNIDs []types.EventNID) (map[types.EventNID]string, error) { func (d *Database) EventIDs(eventNIDs []types.EventNID) (map[types.EventNID]string, error) {
return d.statements.bulkSelectEventID(eventNIDs) return d.statements.bulkSelectEventID(eventNIDs)