2017-04-20 22:40:52 +00:00
|
|
|
// Copyright 2017 Vector Creations 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.
|
|
|
|
|
2017-10-11 17:16:53 +00:00
|
|
|
package routing
|
2017-03-07 16:11:08 +00:00
|
|
|
|
|
|
|
import (
|
2022-02-18 15:05:03 +00:00
|
|
|
"context"
|
2019-07-24 16:15:36 +00:00
|
|
|
"encoding/json"
|
2017-03-09 11:47:06 +00:00
|
|
|
"fmt"
|
2017-03-07 16:11:08 +00:00
|
|
|
"net/http"
|
2017-03-09 11:47:06 +00:00
|
|
|
"strings"
|
2018-08-06 13:09:25 +00:00
|
|
|
"time"
|
2017-03-07 16:11:08 +00:00
|
|
|
|
2018-08-20 09:45:17 +00:00
|
|
|
appserviceAPI "github.com/matrix-org/dendrite/appservice/api"
|
|
|
|
roomserverAPI "github.com/matrix-org/dendrite/roomserver/api"
|
2020-03-17 15:12:01 +00:00
|
|
|
roomserverVersion "github.com/matrix-org/dendrite/roomserver/version"
|
2020-06-16 13:10:55 +00:00
|
|
|
"github.com/matrix-org/dendrite/userapi/api"
|
2023-04-19 14:50:33 +00:00
|
|
|
"github.com/matrix-org/gomatrixserverlib/spec"
|
2017-09-27 15:44:40 +00:00
|
|
|
|
2017-03-10 11:32:53 +00:00
|
|
|
"github.com/matrix-org/dendrite/clientapi/httputil"
|
2020-12-02 17:41:00 +00:00
|
|
|
"github.com/matrix-org/dendrite/setup/config"
|
2017-03-10 11:32:53 +00:00
|
|
|
"github.com/matrix-org/gomatrixserverlib"
|
2017-03-07 16:11:08 +00:00
|
|
|
"github.com/matrix-org/util"
|
2017-11-16 10:12:02 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2017-03-07 16:11:08 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
|
|
|
type createRoomRequest struct {
|
2023-05-31 15:27:08 +00:00
|
|
|
Invite []string `json:"invite"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Visibility string `json:"visibility"`
|
|
|
|
Topic string `json:"topic"`
|
|
|
|
Preset string `json:"preset"`
|
|
|
|
CreationContent json.RawMessage `json:"creation_content"`
|
|
|
|
InitialState []gomatrixserverlib.FledglingEvent `json:"initial_state"`
|
|
|
|
RoomAliasName string `json:"room_alias_name"`
|
|
|
|
RoomVersion gomatrixserverlib.RoomVersion `json:"room_version"`
|
|
|
|
PowerLevelContentOverride json.RawMessage `json:"power_level_content_override"`
|
|
|
|
IsDirect bool `json:"is_direct"`
|
2017-03-07 16:11:08 +00:00
|
|
|
}
|
|
|
|
|
2017-03-09 11:47:06 +00:00
|
|
|
func (r createRoomRequest) Validate() *util.JSONResponse {
|
|
|
|
whitespace := "\t\n\x0b\x0c\r " // https://docs.python.org/2/library/string.html#string.whitespace
|
|
|
|
// https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/handlers/room.py#L81
|
|
|
|
// Synapse doesn't check for ':' but we will else it will break parsers badly which split things into 2 segments.
|
|
|
|
if strings.ContainsAny(r.RoomAliasName, whitespace+":") {
|
|
|
|
return &util.JSONResponse{
|
2018-03-13 15:55:45 +00:00
|
|
|
Code: http.StatusBadRequest,
|
2023-05-09 22:46:49 +00:00
|
|
|
JSON: spec.BadJSON("room_alias_name cannot contain whitespace or ':'"),
|
2017-03-09 11:47:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, userID := range r.Invite {
|
2023-05-31 15:27:08 +00:00
|
|
|
if _, err := spec.NewUserID(userID, true); err != nil {
|
2017-03-09 11:47:06 +00:00
|
|
|
return &util.JSONResponse{
|
2018-03-13 15:55:45 +00:00
|
|
|
Code: http.StatusBadRequest,
|
2023-05-09 22:46:49 +00:00
|
|
|
JSON: spec.BadJSON("user id must be in the form @localpart:domain"),
|
2017-03-09 11:47:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-01-02 10:32:53 +00:00
|
|
|
switch r.Preset {
|
2023-05-31 15:27:08 +00:00
|
|
|
case spec.PresetPrivateChat, spec.PresetTrustedPrivateChat, spec.PresetPublicChat, "":
|
2018-01-02 10:32:53 +00:00
|
|
|
default:
|
|
|
|
return &util.JSONResponse{
|
2018-03-13 15:55:45 +00:00
|
|
|
Code: http.StatusBadRequest,
|
2023-05-09 22:46:49 +00:00
|
|
|
JSON: spec.BadJSON("preset must be any of 'private_chat', 'trusted_private_chat', 'public_chat'"),
|
2018-01-02 10:32:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-24 16:15:36 +00:00
|
|
|
// Validate creation_content fields defined in the spec by marshalling the
|
|
|
|
// creation_content map into bytes and then unmarshalling the bytes into
|
2020-06-12 13:55:57 +00:00
|
|
|
// eventutil.CreateContent.
|
2019-07-24 16:15:36 +00:00
|
|
|
|
|
|
|
creationContentBytes, err := json.Marshal(r.CreationContent)
|
|
|
|
if err != nil {
|
|
|
|
return &util.JSONResponse{
|
|
|
|
Code: http.StatusBadRequest,
|
2023-05-09 22:46:49 +00:00
|
|
|
JSON: spec.BadJSON("malformed creation_content"),
|
2019-07-24 16:15:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-15 17:45:11 +00:00
|
|
|
var CreationContent gomatrixserverlib.CreateContent
|
2019-07-24 16:15:36 +00:00
|
|
|
err = json.Unmarshal(creationContentBytes, &CreationContent)
|
|
|
|
if err != nil {
|
|
|
|
return &util.JSONResponse{
|
|
|
|
Code: http.StatusBadRequest,
|
2023-05-09 22:46:49 +00:00
|
|
|
JSON: spec.BadJSON("malformed creation_content"),
|
2019-07-24 16:15:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-09 11:47:06 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
|
|
|
type createRoomResponse struct {
|
|
|
|
RoomID string `json:"room_id"`
|
|
|
|
RoomAlias string `json:"room_alias,omitempty"` // in synapse not spec
|
|
|
|
}
|
|
|
|
|
2017-03-07 16:11:08 +00:00
|
|
|
// CreateRoom implements /createRoom
|
2018-07-17 15:45:30 +00:00
|
|
|
func CreateRoom(
|
2020-06-16 13:10:55 +00:00
|
|
|
req *http.Request, device *api.Device,
|
2020-08-10 13:18:04 +00:00
|
|
|
cfg *config.ClientAPI,
|
2022-05-05 12:17:38 +00:00
|
|
|
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
2022-05-06 11:39:26 +00:00
|
|
|
asAPI appserviceAPI.AppServiceInternalAPI,
|
2017-07-25 15:10:59 +00:00
|
|
|
) util.JSONResponse {
|
2023-05-31 15:27:08 +00:00
|
|
|
var createRequest createRoomRequest
|
|
|
|
resErr := httputil.UnmarshalJSONRequest(req, &createRequest)
|
2017-03-07 16:11:08 +00:00
|
|
|
if resErr != nil {
|
|
|
|
return *resErr
|
|
|
|
}
|
2023-05-31 15:27:08 +00:00
|
|
|
if resErr = createRequest.Validate(); resErr != nil {
|
2017-03-09 11:47:06 +00:00
|
|
|
return *resErr
|
|
|
|
}
|
2018-08-22 12:40:25 +00:00
|
|
|
evTime, err := httputil.ParseTSParam(req)
|
|
|
|
if err != nil {
|
|
|
|
return util.JSONResponse{
|
|
|
|
Code: http.StatusBadRequest,
|
2023-05-09 22:46:49 +00:00
|
|
|
JSON: spec.InvalidParam(err.Error()),
|
2018-08-22 12:40:25 +00:00
|
|
|
}
|
|
|
|
}
|
2023-05-31 15:27:08 +00:00
|
|
|
return createRoom(req.Context(), createRequest, device, cfg, profileAPI, rsAPI, asAPI, evTime)
|
2022-02-18 15:05:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// createRoom implements /createRoom
|
|
|
|
func createRoom(
|
|
|
|
ctx context.Context,
|
2023-05-31 15:27:08 +00:00
|
|
|
createRequest createRoomRequest, device *api.Device,
|
2022-02-18 15:05:03 +00:00
|
|
|
cfg *config.ClientAPI,
|
2022-05-05 12:17:38 +00:00
|
|
|
profileAPI api.ClientUserAPI, rsAPI roomserverAPI.ClientRoomserverAPI,
|
2022-05-06 11:39:26 +00:00
|
|
|
asAPI appserviceAPI.AppServiceInternalAPI,
|
2022-02-18 15:05:03 +00:00
|
|
|
evTime time.Time,
|
|
|
|
) util.JSONResponse {
|
2023-05-31 15:27:08 +00:00
|
|
|
userID, err := spec.NewUserID(device.UserID, true)
|
2022-10-26 11:59:19 +00:00
|
|
|
if err != nil {
|
2023-05-31 15:27:08 +00:00
|
|
|
util.GetLogger(ctx).WithError(err).Error("invalid userID")
|
2023-05-17 00:33:27 +00:00
|
|
|
return util.JSONResponse{
|
|
|
|
Code: http.StatusInternalServerError,
|
|
|
|
JSON: spec.InternalServerError{},
|
|
|
|
}
|
2022-10-26 11:59:19 +00:00
|
|
|
}
|
2023-05-31 15:27:08 +00:00
|
|
|
if !cfg.Matrix.IsLocalServerName(userID.Domain()) {
|
2022-10-26 11:59:19 +00:00
|
|
|
return util.JSONResponse{
|
|
|
|
Code: http.StatusForbidden,
|
2023-05-31 15:27:08 +00:00
|
|
|
JSON: spec.Forbidden(fmt.Sprintf("User domain %q not configured locally", userID.Domain())),
|
2022-10-26 11:59:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-18 15:05:03 +00:00
|
|
|
logger := util.GetLogger(ctx)
|
2023-05-31 15:27:08 +00:00
|
|
|
|
|
|
|
// TODO: Check room ID doesn't clash with an existing one, and we
|
|
|
|
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
|
|
|
|
roomID, err := spec.NewRoomID(fmt.Sprintf("!%s:%s", util.RandomString(16), userID.Domain()))
|
|
|
|
if err != nil {
|
|
|
|
util.GetLogger(ctx).WithError(err).Error("invalid roomID")
|
|
|
|
return util.JSONResponse{
|
|
|
|
Code: http.StatusInternalServerError,
|
|
|
|
JSON: spec.InternalServerError{},
|
|
|
|
}
|
|
|
|
}
|
2019-07-24 16:15:36 +00:00
|
|
|
|
|
|
|
// Clobber keys: creator, room_version
|
|
|
|
|
2023-08-08 13:20:05 +00:00
|
|
|
roomVersion := rsAPI.DefaultRoomVersion()
|
2023-05-31 15:27:08 +00:00
|
|
|
if createRequest.RoomVersion != "" {
|
|
|
|
candidateVersion := gomatrixserverlib.RoomVersion(createRequest.RoomVersion)
|
2020-03-17 15:12:01 +00:00
|
|
|
_, roomVersionError := roomserverVersion.SupportedRoomVersion(candidateVersion)
|
|
|
|
if roomVersionError != nil {
|
|
|
|
return util.JSONResponse{
|
|
|
|
Code: http.StatusBadRequest,
|
2023-05-09 22:46:49 +00:00
|
|
|
JSON: spec.UnsupportedRoomVersion(roomVersionError.Error()),
|
2020-03-17 15:12:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
roomVersion = candidateVersion
|
|
|
|
}
|
2019-07-24 16:15:36 +00:00
|
|
|
|
2017-03-07 16:11:08 +00:00
|
|
|
logger.WithFields(log.Fields{
|
2023-05-31 15:27:08 +00:00
|
|
|
"userID": userID.String(),
|
|
|
|
"roomID": roomID.String(),
|
2021-07-21 11:31:46 +00:00
|
|
|
"roomVersion": roomVersion,
|
2017-03-10 11:32:53 +00:00
|
|
|
}).Info("Creating new room")
|
|
|
|
|
2023-05-31 15:27:08 +00:00
|
|
|
profile, err := appserviceAPI.RetrieveUserProfile(ctx, userID.String(), asAPI, profileAPI)
|
2017-07-25 15:10:59 +00:00
|
|
|
if err != nil {
|
2022-02-18 15:05:03 +00:00
|
|
|
util.GetLogger(ctx).WithError(err).Error("appserviceAPI.RetrieveUserProfile failed")
|
2023-05-17 00:33:27 +00:00
|
|
|
return util.JSONResponse{
|
|
|
|
Code: http.StatusInternalServerError,
|
|
|
|
JSON: spec.InternalServerError{},
|
|
|
|
}
|
2017-07-25 15:10:59 +00:00
|
|
|
}
|
|
|
|
|
2023-05-31 15:27:08 +00:00
|
|
|
userDisplayName := profile.DisplayName
|
|
|
|
userAvatarURL := profile.AvatarURL
|
2017-09-27 15:44:40 +00:00
|
|
|
|
2023-05-31 15:27:08 +00:00
|
|
|
keyID := cfg.Matrix.KeyID
|
|
|
|
privateKey := cfg.Matrix.PrivateKey
|
2017-09-27 15:44:40 +00:00
|
|
|
|
2023-05-31 15:27:08 +00:00
|
|
|
req := roomserverAPI.PerformCreateRoomRequest{
|
|
|
|
InvitedUsers: createRequest.Invite,
|
|
|
|
RoomName: createRequest.Name,
|
|
|
|
Visibility: createRequest.Visibility,
|
|
|
|
Topic: createRequest.Topic,
|
|
|
|
StatePreset: createRequest.Preset,
|
|
|
|
CreationContent: createRequest.CreationContent,
|
|
|
|
InitialState: createRequest.InitialState,
|
|
|
|
RoomAliasName: createRequest.RoomAliasName,
|
|
|
|
RoomVersion: roomVersion,
|
|
|
|
PowerLevelContentOverride: createRequest.PowerLevelContentOverride,
|
|
|
|
IsDirect: createRequest.IsDirect,
|
2020-08-17 16:33:19 +00:00
|
|
|
|
2023-05-31 15:27:08 +00:00
|
|
|
UserDisplayName: userDisplayName,
|
|
|
|
UserAvatarURL: userAvatarURL,
|
|
|
|
KeyID: keyID,
|
|
|
|
PrivateKey: privateKey,
|
|
|
|
EventTime: evTime,
|
2020-05-07 15:46:11 +00:00
|
|
|
}
|
2023-06-12 11:19:25 +00:00
|
|
|
|
2023-05-31 15:27:08 +00:00
|
|
|
roomAlias, createRes := rsAPI.PerformCreateRoom(ctx, *userID, *roomID, &req)
|
|
|
|
if createRes != nil {
|
|
|
|
return *createRes
|
2020-07-02 14:41:18 +00:00
|
|
|
}
|
|
|
|
|
2017-07-06 10:44:15 +00:00
|
|
|
response := createRoomResponse{
|
2023-05-31 15:27:08 +00:00
|
|
|
RoomID: roomID.String(),
|
2017-09-27 15:44:40 +00:00
|
|
|
RoomAlias: roomAlias,
|
2017-07-06 10:44:15 +00:00
|
|
|
}
|
|
|
|
|
2017-03-10 11:32:53 +00:00
|
|
|
return util.JSONResponse{
|
|
|
|
Code: 200,
|
2017-07-06 10:44:15 +00:00
|
|
|
JSON: response,
|
2017-03-10 11:32:53 +00:00
|
|
|
}
|
|
|
|
}
|