aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrendan Abolivier <contact@brendanabolivier.com>2017-09-11 19:18:19 +0100
committerGitHub <noreply@github.com>2017-09-11 19:18:19 +0100
commitf1fce55697df421af3840edce4e60071adf0e818 (patch)
tree024376ed2b4e1b836cd2bd32fb698cb18b20a594
parent28346b39e8e6d666442b00fed79a07e695046855 (diff)
Add config and checks for trusted ID servers (#206)
* Add config for trusted ID servers * Add new error * Implement check for trusted ID server * Complete unfinished comment * Make comment more explicit in the config file * Use go standard errors in membership.go * Use standard errors instead of JSON responses in threepid * Doc errors * Remove unused parameter
-rw-r--r--dendrite-config.yaml6
-rw-r--r--src/github.com/matrix-org/dendrite/clientapi/jsonerror/jsonerror.go9
-rw-r--r--src/github.com/matrix-org/dendrite/clientapi/readers/threepid.go30
-rw-r--r--src/github.com/matrix-org/dendrite/clientapi/routing/routing.go4
-rw-r--r--src/github.com/matrix-org/dendrite/clientapi/threepid/invites.go59
-rw-r--r--src/github.com/matrix-org/dendrite/clientapi/threepid/threepid.go34
-rw-r--r--src/github.com/matrix-org/dendrite/clientapi/writers/membership.go105
-rw-r--r--src/github.com/matrix-org/dendrite/common/config/config.go8
8 files changed, 175 insertions, 80 deletions
diff --git a/dendrite-config.yaml b/dendrite-config.yaml
index a91429c2..c9cab73e 100644
--- a/dendrite-config.yaml
+++ b/dendrite-config.yaml
@@ -12,6 +12,12 @@ matrix:
private_key: "/etc/dendrite/matrix_key.pem"
# The x509 certificates used by the federation listeners for this server
federation_certificates: ["/etc/dendrite/server.pem"]
+ # The list of identity servers trusted to verify third party identifiers by this server.
+ # Defaults to no trusted servers.
+ trusted_third_party_id_servers:
+ - vector.im
+ - matrix.org
+ - riot.im
# The media repository config
media:
diff --git a/src/github.com/matrix-org/dendrite/clientapi/jsonerror/jsonerror.go b/src/github.com/matrix-org/dendrite/clientapi/jsonerror/jsonerror.go
index 3a424dde..d267355e 100644
--- a/src/github.com/matrix-org/dendrite/clientapi/jsonerror/jsonerror.go
+++ b/src/github.com/matrix-org/dendrite/clientapi/jsonerror/jsonerror.go
@@ -104,3 +104,12 @@ func LimitExceeded(msg string, retryAfterMS int64) *LimitExceededError {
RetryAfterMS: retryAfterMS,
}
}
+
+// NotTrusted is an error which is returned when the client asks the server to
+// proxy a request (e.g. 3PID association) to a server that isn't trusted
+func NotTrusted(serverName string) *MatrixError {
+ return &MatrixError{
+ ErrCode: "M_SERVER_NOT_TRUSTED",
+ Err: fmt.Sprintf("Untrusted server '%s'", serverName),
+ }
+}
diff --git a/src/github.com/matrix-org/dendrite/clientapi/readers/threepid.go b/src/github.com/matrix-org/dendrite/clientapi/readers/threepid.go
index 4b86108e..b0d79287 100644
--- a/src/github.com/matrix-org/dendrite/clientapi/readers/threepid.go
+++ b/src/github.com/matrix-org/dendrite/clientapi/readers/threepid.go
@@ -22,6 +22,7 @@ import (
"github.com/matrix-org/dendrite/clientapi/httputil"
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/clientapi/threepid"
+ "github.com/matrix-org/dendrite/common/config"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util"
@@ -38,7 +39,7 @@ type threePIDsResponse struct {
// RequestEmailToken implements:
// POST /account/3pid/email/requestToken
// POST /register/email/requestToken
-func RequestEmailToken(req *http.Request, accountDB *accounts.Database) util.JSONResponse {
+func RequestEmailToken(req *http.Request, accountDB *accounts.Database, cfg config.Dendrite) util.JSONResponse {
var body threepid.EmailAssociationRequest
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
return *reqErr
@@ -63,8 +64,13 @@ func RequestEmailToken(req *http.Request, accountDB *accounts.Database) util.JSO
}
}
- resp.SID, err = threepid.CreateSession(body)
- if err != nil {
+ resp.SID, err = threepid.CreateSession(body, cfg)
+ if err == threepid.ErrNotTrusted {
+ return util.JSONResponse{
+ Code: 400,
+ JSON: jsonerror.NotTrusted(body.IDServer),
+ }
+ } else if err != nil {
return httputil.LogThenError(req, err)
}
@@ -77,6 +83,7 @@ func RequestEmailToken(req *http.Request, accountDB *accounts.Database) util.JSO
// CheckAndSave3PIDAssociation implements POST /account/3pid
func CheckAndSave3PIDAssociation(
req *http.Request, accountDB *accounts.Database, device *authtypes.Device,
+ cfg config.Dendrite,
) util.JSONResponse {
var body threepid.EmailAssociationCheckRequest
if reqErr := httputil.UnmarshalJSONRequest(req, &body); reqErr != nil {
@@ -84,8 +91,13 @@ func CheckAndSave3PIDAssociation(
}
// Check if the association has been validated
- verified, address, medium, err := threepid.CheckAssociation(body.Creds)
- if err != nil {
+ verified, address, medium, err := threepid.CheckAssociation(body.Creds, cfg)
+ if err == threepid.ErrNotTrusted {
+ return util.JSONResponse{
+ Code: 400,
+ JSON: jsonerror.NotTrusted(body.Creds.IDServer),
+ }
+ } else if err != nil {
return httputil.LogThenError(req, err)
}
@@ -101,7 +113,13 @@ func CheckAndSave3PIDAssociation(
if body.Bind {
// Publish the association on the identity server if requested
- if err = threepid.PublishAssociation(body.Creds, device.UserID); err != nil {
+ err = threepid.PublishAssociation(body.Creds, device.UserID, cfg)
+ if err == threepid.ErrNotTrusted {
+ return util.JSONResponse{
+ Code: 400,
+ JSON: jsonerror.NotTrusted(body.Creds.IDServer),
+ }
+ } else if err != nil {
return httputil.LogThenError(req, err)
}
}
diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go
index 58e852fe..1620d6df 100644
--- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go
+++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go
@@ -241,7 +241,7 @@ func Setup(
r0mux.Handle("/account/3pid",
common.MakeAuthAPI("account_3pid", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse {
- return readers.CheckAndSave3PIDAssociation(req, accountDB, device)
+ return readers.CheckAndSave3PIDAssociation(req, accountDB, device, cfg)
}),
).Methods("POST", "OPTIONS")
@@ -253,7 +253,7 @@ func Setup(
r0mux.Handle("/{path:(?:account/3pid|register)}/email/requestToken",
common.MakeAPI("account_3pid_request_token", func(req *http.Request) util.JSONResponse {
- return readers.RequestEmailToken(req, accountDB)
+ return readers.RequestEmailToken(req, accountDB, cfg)
}),
).Methods("POST", "OPTIONS")
diff --git a/src/github.com/matrix-org/dendrite/clientapi/threepid/invites.go b/src/github.com/matrix-org/dendrite/clientapi/threepid/invites.go
index 85f0b5dc..27cdf343 100644
--- a/src/github.com/matrix-org/dendrite/clientapi/threepid/invites.go
+++ b/src/github.com/matrix-org/dendrite/clientapi/threepid/invites.go
@@ -26,15 +26,11 @@ import (
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
"github.com/matrix-org/dendrite/clientapi/events"
- "github.com/matrix-org/dendrite/clientapi/httputil"
- "github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/clientapi/producers"
"github.com/matrix-org/dendrite/common"
"github.com/matrix-org/dendrite/common/config"
"github.com/matrix-org/dendrite/roomserver/api"
"github.com/matrix-org/gomatrixserverlib"
-
- "github.com/matrix-org/util"
)
// MembershipRequest represents the body of an incoming POST request
@@ -66,6 +62,15 @@ type idServerStoreInviteResponse struct {
PublicKeys []common.PublicKey `json:"public_keys"`
}
+var (
+ // ErrMissingParameter is the error raised if a request for 3PID invite has
+ // an incomplete body
+ ErrMissingParameter = errors.New("'address', 'id_server' and 'medium' must all be supplied")
+ // ErrNotTrusted is the error raised if an identity server isn't in the list
+ // of trusted servers in the configuration file.
+ ErrNotTrusted = errors.New("untrusted server")
+)
+
// CheckAndProcessInvite analyses the body of an incoming membership request.
// If the fields relative to a third-party-invite are all supplied, lookups the
// matching Matrix ID from the given identity server. If no Matrix ID is
@@ -80,27 +85,24 @@ type idServerStoreInviteResponse struct {
// fills the Matrix ID in the request body so a normal invite membership event
// can be emitted.
func CheckAndProcessInvite(
- req *http.Request, device *authtypes.Device, body *MembershipRequest,
- cfg config.Dendrite, queryAPI api.RoomserverQueryAPI, db *accounts.Database,
+ device *authtypes.Device, body *MembershipRequest, cfg config.Dendrite,
+ queryAPI api.RoomserverQueryAPI, db *accounts.Database,
producer *producers.RoomserverProducer, membership string, roomID string,
-) *util.JSONResponse {
+) (inviteStoredOnIDServer bool, err error) {
if membership != "invite" || (body.Address == "" && body.IDServer == "" && body.Medium == "") {
// If none of the 3PID-specific fields are supplied, it's a standard invite
// so return nil for it to be processed as such
- return nil
+ return
} else if body.Address == "" || body.IDServer == "" || body.Medium == "" {
// If at least one of the 3PID-specific fields is supplied but not all
// of them, return an error
- return &util.JSONResponse{
- Code: 400,
- JSON: jsonerror.BadJSON("'address', 'id_server' and 'medium' must all be supplied"),
- }
+ err = ErrMissingParameter
+ return
}
lookupRes, storeInviteRes, err := queryIDServer(db, cfg, device, body, roomID)
if err != nil {
- resErr := httputil.LogThenError(req, err)
- return &resErr
+ return
}
if lookupRes.MXID == "" {
@@ -108,28 +110,16 @@ func CheckAndProcessInvite(
// "m.room.third_party_invite" have to be emitted from the data in
// storeInviteRes.
err = emit3PIDInviteEvent(body, storeInviteRes, device, roomID, cfg, queryAPI, producer)
- if err == events.ErrRoomNoExists {
- return &util.JSONResponse{
- Code: 404,
- JSON: jsonerror.NotFound(err.Error()),
- }
- } else if err != nil {
- resErr := httputil.LogThenError(req, err)
- return &resErr
- }
+ inviteStoredOnIDServer = err == nil
- // If everything went well, returns with an empty response.
- return &util.JSONResponse{
- Code: 200,
- JSON: struct{}{},
- }
+ return
}
// A Matrix ID have been found: set it in the body request and let the process
// continue to create a "m.room.member" event with an "invite" membership
body.UserID = lookupRes.MXID
- return nil
+ return
}
// queryIDServer handles all the requests to the identity server, starting by
@@ -142,9 +132,13 @@ func CheckAndProcessInvite(
// Returns a representation of the response for both cases.
// Returns an error if a check or a request failed.
func queryIDServer(
- db *accounts.Database, cfg config.Dendrite,
- device *authtypes.Device, body *MembershipRequest, roomID string,
+ db *accounts.Database, cfg config.Dendrite, device *authtypes.Device,
+ body *MembershipRequest, roomID string,
) (lookupRes *idServerLookupResponse, storeInviteRes *idServerStoreInviteResponse, err error) {
+ if err = isTrusted(body.IDServer, cfg); err != nil {
+ return
+ }
+
// Lookup the 3PID
lookupRes, err = queryIDServerLookup(body)
if err != nil {
@@ -249,7 +243,6 @@ func queryIDServerStoreInvite(
}
if resp.StatusCode != http.StatusOK {
- // TODO: Log the error supplied with the identity server?
errMsg := fmt.Sprintf("Identity server %s responded with a %d error code", body.IDServer, resp.StatusCode)
return nil, errors.New(errMsg)
}
@@ -275,7 +268,6 @@ func queryIDServerPubKey(idServerName string, keyID string) ([]byte, error) {
}
if resp.StatusCode != http.StatusOK {
- // TODO: Log the error supplied with the identity server?
errMsg := fmt.Sprintf("Couldn't retrieve key %s from server %s", keyID, idServerName)
return nil, errors.New(errMsg)
}
@@ -297,7 +289,6 @@ func checkIDServerSignatures(body *MembershipRequest, res *idServerLookupRespons
return err
}
- // TODO: Check if the domain is part of a list of trusted ID servers
signatures, ok := res.Signatures[body.IDServer]
if !ok {
return errors.New("No signature for domain " + body.IDServer)
diff --git a/src/github.com/matrix-org/dendrite/clientapi/threepid/threepid.go b/src/github.com/matrix-org/dendrite/clientapi/threepid/threepid.go
index 2ec4599a..f07a3a14 100644
--- a/src/github.com/matrix-org/dendrite/clientapi/threepid/threepid.go
+++ b/src/github.com/matrix-org/dendrite/clientapi/threepid/threepid.go
@@ -22,6 +22,8 @@ import (
"net/url"
"strconv"
"strings"
+
+ "github.com/matrix-org/dendrite/common/config"
)
// EmailAssociationRequest represents the request defined at https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register-email-requesttoken
@@ -49,8 +51,10 @@ type Credentials struct {
// Returns the session's ID.
// Returns an error if there was a problem sending the request or decoding the
// response, or if the identity server responded with a non-OK status.
-func CreateSession(req EmailAssociationRequest) (string, error) {
- // TODO: Check if the ID server is trusted
+func CreateSession(req EmailAssociationRequest, cfg config.Dendrite) (string, error) {
+ if err := isTrusted(req.IDServer, cfg); err != nil {
+ return "", err
+ }
// Create a session on the ID server
postURL := fmt.Sprintf("https://%s/_matrix/identity/api/v1/validate/email/requestToken", req.IDServer)
@@ -93,8 +97,11 @@ func CreateSession(req EmailAssociationRequest) (string, error) {
// identifier and its medium.
// Returns an error if there was a problem sending the request or decoding the
// response, or if the identity server responded with a non-OK status.
-func CheckAssociation(creds Credentials) (bool, string, string, error) {
- // TODO: Check if the ID server is trusted
+func CheckAssociation(creds Credentials, cfg config.Dendrite) (bool, string, string, error) {
+ if err := isTrusted(creds.IDServer, cfg); err != nil {
+ return false, "", "", err
+ }
+
url := fmt.Sprintf("https://%s/_matrix/identity/api/v1/3pid/getValidated3pid?sid=%s&client_secret=%s", creds.IDServer, creds.SID, creds.Secret)
resp, err := http.Get(url)
if err != nil {
@@ -126,8 +133,11 @@ func CheckAssociation(creds Credentials) (bool, string, string, error) {
// identifier and a Matrix ID.
// Returns an error if there was a problem sending the request or decoding the
// response, or if the identity server responded with a non-OK status.
-func PublishAssociation(creds Credentials, userID string) error {
- // TODO: Check if the ID server is trusted
+func PublishAssociation(creds Credentials, userID string, cfg config.Dendrite) error {
+ if err := isTrusted(creds.IDServer, cfg); err != nil {
+ return err
+ }
+
postURL := fmt.Sprintf("https://%s/_matrix/identity/api/v1/3pid/bind", creds.IDServer)
data := url.Values{}
@@ -154,3 +164,15 @@ func PublishAssociation(creds Credentials, userID string) error {
return nil
}
+
+// isTrusted checks if a given identity server is part of the list of trusted
+// identity servers in the configuration file.
+// Returns an error if the server isn't trusted.
+func isTrusted(idServer string, cfg config.Dendrite) error {
+ for _, server := range cfg.Matrix.TrustedIDServers {
+ if idServer == server {
+ return nil
+ }
+ }
+ return ErrNotTrusted
+}
diff --git a/src/github.com/matrix-org/dendrite/clientapi/writers/membership.go b/src/github.com/matrix-org/dendrite/clientapi/writers/membership.go
index 6dd52a00..4173e51a 100644
--- a/src/github.com/matrix-org/dendrite/clientapi/writers/membership.go
+++ b/src/github.com/matrix-org/dendrite/clientapi/writers/membership.go
@@ -15,6 +15,7 @@
package writers
import (
+ "errors"
"net/http"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
@@ -32,6 +33,8 @@ import (
"github.com/matrix-org/util"
)
+var errMissingUserID = errors.New("'user_id' must be supplied")
+
// SendMembership implements PUT /rooms/{roomID}/(join|kick|ban|unban|leave|invite)
// by building a m.room.member event then sending it to the room server
func SendMembership(
@@ -44,20 +47,78 @@ func SendMembership(
return *reqErr
}
- if res := threepid.CheckAndProcessInvite(
- req, device, &body, cfg, queryAPI, accountDB, producer, membership, roomID,
- ); res != nil {
- return *res
+ inviteStored, err := threepid.CheckAndProcessInvite(
+ device, &body, cfg, queryAPI, accountDB, producer, membership, roomID,
+ )
+ if err == threepid.ErrMissingParameter {
+ return util.JSONResponse{
+ Code: 400,
+ JSON: jsonerror.BadJSON(err.Error()),
+ }
+ } else if err == threepid.ErrNotTrusted {
+ return util.JSONResponse{
+ Code: 400,
+ JSON: jsonerror.NotTrusted(body.IDServer),
+ }
+ } else if err == events.ErrRoomNoExists {
+ return util.JSONResponse{
+ Code: 404,
+ JSON: jsonerror.NotFound(err.Error()),
+ }
+ } else if err != nil {
+ return httputil.LogThenError(req, err)
}
- stateKey, reason, reqErr := getMembershipStateKey(body, device, membership)
- if reqErr != nil {
- return *reqErr
+ // If an invite has been stored on an identity server, it means that a
+ // m.room.third_party_invite event has been emitted and that we shouldn't
+ // emit a m.room.member one.
+ if inviteStored {
+ return util.JSONResponse{
+ Code: 200,
+ JSON: struct{}{},
+ }
+ }
+
+ event, err := buildMembershipEvent(
+ body, accountDB, device, membership, roomID, cfg, queryAPI,
+ )
+ if err == errMissingUserID {
+ return util.JSONResponse{
+ Code: 400,
+ JSON: jsonerror.BadJSON(err.Error()),
+ }
+ } else if err == events.ErrRoomNoExists {
+ return util.JSONResponse{
+ Code: 404,
+ JSON: jsonerror.NotFound(err.Error()),
+ }
+ } else if err != nil {
+ return httputil.LogThenError(req, err)
+ }
+
+ if err := producer.SendEvents([]gomatrixserverlib.Event{*event}, cfg.Matrix.ServerName); err != nil {
+ return httputil.LogThenError(req, err)
+ }
+
+ return util.JSONResponse{
+ Code: 200,
+ JSON: struct{}{},
+ }
+}
+
+func buildMembershipEvent(
+ body threepid.MembershipRequest, accountDB *accounts.Database,
+ device *authtypes.Device, membership string, roomID string, cfg config.Dendrite,
+ queryAPI api.RoomserverQueryAPI,
+) (*gomatrixserverlib.Event, error) {
+ stateKey, reason, err := getMembershipStateKey(body, device, membership)
+ if err != nil {
+ return nil, err
}
profile, err := loadProfile(stateKey, cfg, accountDB)
if err != nil {
- return httputil.LogThenError(req, err)
+ return nil, err
}
builder := gomatrixserverlib.EventBuilder{
@@ -80,27 +141,10 @@ func SendMembership(
}
if err = builder.SetContent(content); err != nil {
- return httputil.LogThenError(req, err)
- }
-
- event, err := events.BuildEvent(&builder, cfg, queryAPI, nil)
- if err == events.ErrRoomNoExists {
- return util.JSONResponse{
- Code: 404,
- JSON: jsonerror.NotFound(err.Error()),
- }
- } else if err != nil {
- return httputil.LogThenError(req, err)
- }
-
- if err := producer.SendEvents([]gomatrixserverlib.Event{*event}, cfg.Matrix.ServerName); err != nil {
- return httputil.LogThenError(req, err)
+ return nil, err
}
- return util.JSONResponse{
- Code: 200,
- JSON: struct{}{},
- }
+ return events.BuildEvent(&builder, cfg, queryAPI, nil)
}
// loadProfile lookups the profile of a given user from the database and returns
@@ -130,16 +174,13 @@ func loadProfile(userID string, cfg config.Dendrite, accountDB *accounts.Databas
// returns a JSONResponse with a corresponding error code and message.
func getMembershipStateKey(
body threepid.MembershipRequest, device *authtypes.Device, membership string,
-) (stateKey string, reason string, response *util.JSONResponse) {
+) (stateKey string, reason string, err error) {
if membership == "ban" || membership == "unban" || membership == "kick" || membership == "invite" {
// If we're in this case, the state key is contained in the request body,
// possibly along with a reason (for "kick" and "ban") so we need to parse
// it
if body.UserID == "" {
- response = &util.JSONResponse{
- Code: 400,
- JSON: jsonerror.BadJSON("'user_id' must be supplied."),
- }
+ err = errMissingUserID
return
}
diff --git a/src/github.com/matrix-org/dendrite/common/config/config.go b/src/github.com/matrix-org/dendrite/common/config/config.go
index 8d76a03d..86234b8d 100644
--- a/src/github.com/matrix-org/dendrite/common/config/config.go
+++ b/src/github.com/matrix-org/dendrite/common/config/config.go
@@ -70,6 +70,10 @@ type Dendrite struct {
// by remote servers.
// Defaults to 24 hours.
KeyValidityPeriod time.Duration `yaml:"key_validity_period"`
+ // List of domains that the server will trust as identity servers to
+ // verify third-party identifiers.
+ // Defaults to an empty array.
+ TrustedIDServers []string `yaml:"trusted_third_party_id_servers"`
} `yaml:"matrix"`
// The configuration specific to the media repostitory.
@@ -273,6 +277,10 @@ func (config *Dendrite) setDefaults() {
config.Matrix.KeyValidityPeriod = 24 * time.Hour
}
+ if config.Matrix.TrustedIDServers == nil {
+ config.Matrix.TrustedIDServers = []string{}
+ }
+
if config.Media.MaxThumbnailGenerators == 0 {
config.Media.MaxThumbnailGenerators = 10
}