2017-04-20 17:40:52 -05: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-03-07 10:11:08 -06:00
package writers
import (
"encoding/json"
2017-03-09 05:47:06 -06:00
"fmt"
2017-03-07 10:11:08 -06:00
"net/http"
2017-03-09 05:47:06 -06:00
"strings"
2017-03-10 05:32:53 -06:00
"time"
2017-03-07 10:11:08 -06:00
2017-09-27 10:44:40 -05:00
"github.com/matrix-org/dendrite/roomserver/api"
2017-03-07 10:11:08 -06:00
log "github.com/Sirupsen/logrus"
2017-05-23 11:43:05 -05:00
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
2017-07-25 10:10:59 -05:00
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
2017-03-10 05:32:53 -06:00
"github.com/matrix-org/dendrite/clientapi/httputil"
2017-03-09 05:47:06 -06:00
"github.com/matrix-org/dendrite/clientapi/jsonerror"
2017-03-15 08:36:26 -05:00
"github.com/matrix-org/dendrite/clientapi/producers"
2017-08-22 05:12:51 -05:00
"github.com/matrix-org/dendrite/common"
2017-06-19 09:21:04 -05:00
"github.com/matrix-org/dendrite/common/config"
2017-03-10 05:32:53 -06:00
"github.com/matrix-org/gomatrixserverlib"
2017-03-07 10:11:08 -06:00
"github.com/matrix-org/util"
)
// https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
type createRoomRequest struct {
Invite [ ] string ` json:"invite" `
Name string ` json:"name" `
Visibility string ` json:"visibility" `
Topic string ` json:"topic" `
Preset string ` json:"preset" `
CreationContent map [ string ] interface { } ` json:"creation_content" `
InitialState json . RawMessage ` json:"initial_state" ` // TODO
RoomAliasName string ` json:"room_alias_name" `
}
2017-03-09 05:47:06 -06: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 {
Code : 400 ,
JSON : jsonerror . BadJSON ( "room_alias_name cannot contain whitespace" ) ,
}
}
for _ , userID := range r . Invite {
// TODO: We should put user ID parsing code into gomatrixserverlib and use that instead
// (see https://github.com/matrix-org/gomatrixserverlib/blob/3394e7c7003312043208aa73727d2256eea3d1f6/eventcontent.go#L347 )
// It should be a struct (with pointers into a single string to avoid copying) and
// we should update all refs to use UserID types rather than strings.
// https://github.com/matrix-org/synapse/blob/v0.19.2/synapse/types.py#L92
2017-07-07 08:11:32 -05:00
if _ , _ , err := gomatrixserverlib . SplitID ( '@' , userID ) ; err != nil {
2017-03-09 05:47:06 -06:00
return & util . JSONResponse {
Code : 400 ,
JSON : jsonerror . BadJSON ( "user id must be in the form @localpart:domain" ) ,
}
}
}
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-10 05:32:53 -06:00
// fledglingEvent is a helper representation of an event used when creating many events in succession.
type fledglingEvent struct {
Type string
StateKey string
Content interface { }
}
2017-03-07 10:11:08 -06:00
// CreateRoom implements /createRoom
2017-07-25 10:10:59 -05:00
func CreateRoom ( req * http . Request , device * authtypes . Device ,
cfg config . Dendrite , producer * producers . RoomserverProducer ,
2017-09-27 10:44:40 -05:00
accountDB * accounts . Database , aliasAPI api . RoomserverAliasAPI ,
2017-07-25 10:10:59 -05:00
) util . JSONResponse {
2017-09-27 10:44:40 -05:00
// TODO (#267): Check room ID doesn't clash with an existing one, and we
// probably shouldn't be using pseudo-random strings, maybe GUIDs?
2017-06-19 09:21:04 -05:00
roomID := fmt . Sprintf ( "!%s:%s" , util . RandomString ( 16 ) , cfg . Matrix . ServerName )
2017-09-27 10:44:40 -05:00
return createRoom ( req , device , cfg , roomID , producer , accountDB , aliasAPI )
2017-03-09 05:47:06 -06:00
}
// createRoom implements /createRoom
2017-09-27 10:44:40 -05:00
// nolint: gocyclo
2017-07-25 10:10:59 -05:00
func createRoom ( req * http . Request , device * authtypes . Device ,
cfg config . Dendrite , roomID string , producer * producers . RoomserverProducer ,
2017-09-27 10:44:40 -05:00
accountDB * accounts . Database , aliasAPI api . RoomserverAliasAPI ,
2017-07-25 10:10:59 -05:00
) util . JSONResponse {
2017-03-07 10:11:08 -06:00
logger := util . GetLogger ( req . Context ( ) )
2017-05-23 11:43:05 -05:00
userID := device . UserID
2017-03-07 10:11:08 -06:00
var r createRoomRequest
2017-05-23 11:43:05 -05:00
resErr := httputil . UnmarshalJSONRequest ( req , & r )
2017-03-07 10:11:08 -06:00
if resErr != nil {
return * resErr
}
2017-03-09 05:47:06 -06:00
// TODO: apply rate-limit
if resErr = r . Validate ( ) ; resErr != nil {
return * resErr
}
// TODO: visibility/presets/raw initial state/creation content
// TODO: Create room alias association
2017-03-07 10:11:08 -06:00
logger . WithFields ( log . Fields {
"userID" : userID ,
2017-03-09 05:47:06 -06:00
"roomID" : roomID ,
2017-03-10 05:32:53 -06:00
} ) . Info ( "Creating new room" )
2017-07-25 10:10:59 -05:00
localpart , _ , err := gomatrixserverlib . SplitID ( '@' , userID )
if err != nil {
return httputil . LogThenError ( req , err )
}
2017-09-18 08:15:27 -05:00
profile , err := accountDB . GetProfileByLocalpart ( req . Context ( ) , localpart )
2017-07-25 10:10:59 -05:00
if err != nil {
return httputil . LogThenError ( req , err )
}
2017-08-22 05:12:51 -05:00
membershipContent := common . MemberContent {
2017-07-25 10:10:59 -05:00
Membership : "join" ,
DisplayName : profile . DisplayName ,
AvatarURL : profile . AvatarURL ,
}
2017-03-15 08:36:26 -05:00
var builtEvents [ ] gomatrixserverlib . Event
2017-03-09 05:47:06 -06:00
// send events into the room in order of:
// 1- m.room.create
// 2- room creator join member
// 3- m.room.power_levels
// 4- m.room.canonical_alias (opt) TODO
// 5- m.room.join_rules
// 6- m.room.history_visibility
2017-07-20 07:06:14 -05:00
// 7- m.room.guest_access (opt)
2017-03-09 05:47:06 -06:00
// 8- other initial state items TODO
// 9- m.room.name (opt)
// 10- m.room.topic (opt)
// 11- invite events (opt) - with is_direct flag if applicable TODO
// 12- 3pid invite events (opt) TODO
// 13- m.room.aliases event for HS (if alias specified) TODO
// This differs from Synapse slightly. Synapse would vary the ordering of 3-7
// depending on if those events were in "initial_state" or not. This made it
// harder to reason about, hence sticking to a strict static ordering.
2017-03-10 05:32:53 -06:00
// TODO: Synapse has txn/token ID on each event. Do we need to do this here?
eventsToMake := [ ] fledglingEvent {
2017-08-22 05:12:51 -05:00
{ "m.room.create" , "" , common . CreateContent { Creator : userID } } ,
2017-07-25 10:10:59 -05:00
{ "m.room.member" , userID , membershipContent } ,
2017-08-22 05:12:51 -05:00
{ "m.room.power_levels" , "" , common . InitialPowerLevelsContent ( userID ) } ,
2017-03-10 05:32:53 -06:00
// TODO: m.room.canonical_alias
2017-09-20 07:40:22 -05:00
{ "m.room.join_rules" , "" , common . JoinRulesContent { JoinRule : "public" } } , // FIXME: Allow this to be changed
{ "m.room.history_visibility" , "" , common . HistoryVisibilityContent { HistoryVisibility : "joined" } } , // FIXME: Allow this to be changed
{ "m.room.guest_access" , "" , common . GuestAccessContent { GuestAccess : "can_join" } } , // FIXME: Allow this to be changed
2017-03-10 05:32:53 -06:00
// TODO: Other initial state items
2017-09-20 07:40:22 -05:00
{ "m.room.name" , "" , common . NameContent { Name : r . Name } } , // FIXME: Only send the name event if a name is supplied, to avoid sending a false room name removal event
{ "m.room.topic" , "" , common . TopicContent { Topic : r . Topic } } ,
2017-03-10 05:32:53 -06:00
// TODO: invite events
// TODO: 3pid invite events
2017-07-20 07:06:14 -05:00
// TODO: m.room.aliases
2017-03-10 05:32:53 -06:00
}
2017-03-10 11:54:17 -06:00
authEvents := gomatrixserverlib . NewAuthEvents ( nil )
2017-03-10 05:32:53 -06:00
for i , e := range eventsToMake {
depth := i + 1 // depth starts at 1
builder := gomatrixserverlib . EventBuilder {
Sender : userID ,
RoomID : roomID ,
Type : e . Type ,
StateKey : & e . StateKey ,
Depth : int64 ( depth ) ,
}
2017-09-20 04:59:19 -05:00
err = builder . SetContent ( e . Content )
if err != nil {
return httputil . LogThenError ( req , err )
}
2017-03-10 05:32:53 -06:00
if i > 0 {
builder . PrevEvents = [ ] gomatrixserverlib . EventReference { builtEvents [ i - 1 ] . EventReference ( ) }
}
2017-09-27 10:44:40 -05:00
var ev * gomatrixserverlib . Event
ev , err = buildEvent ( & builder , & authEvents , cfg )
2017-03-10 05:32:53 -06:00
if err != nil {
2017-03-10 10:50:41 -06:00
return httputil . LogThenError ( req , err )
2017-03-10 05:32:53 -06:00
}
2017-09-20 04:59:19 -05:00
if err = gomatrixserverlib . Allowed ( * ev , & authEvents ) ; err != nil {
2017-03-10 10:50:41 -06:00
return httputil . LogThenError ( req , err )
2017-03-10 05:32:53 -06:00
}
// Add the event to the list of auth events
2017-03-15 08:36:26 -05:00
builtEvents = append ( builtEvents , * ev )
2017-09-20 04:59:19 -05:00
err = authEvents . AddEvent ( ev )
if err != nil {
return httputil . LogThenError ( req , err )
}
2017-03-10 10:19:23 -06:00
}
2017-03-10 05:32:53 -06:00
2017-03-10 10:19:23 -06:00
// send events to the room server
2017-09-27 10:44:40 -05:00
err = producer . SendEvents ( req . Context ( ) , builtEvents , cfg . Matrix . ServerName )
if err != nil {
2017-03-10 10:50:41 -06:00
return httputil . LogThenError ( req , err )
2017-03-10 05:32:53 -06:00
}
2017-09-27 10:44:40 -05:00
// TODO(#269): Reserve room alias while we create the room. This stops us
// from creating the room but still failing due to the alias having already
// been taken.
var roomAlias string
if r . RoomAliasName != "" {
roomAlias = fmt . Sprintf ( "#%s:%s" , r . RoomAliasName , cfg . Matrix . ServerName )
aliasReq := api . SetRoomAliasRequest {
Alias : roomAlias ,
RoomID : roomID ,
UserID : userID ,
}
var aliasResp api . SetRoomAliasResponse
err = aliasAPI . SetRoomAlias ( req . Context ( ) , & aliasReq , & aliasResp )
if err != nil {
return httputil . LogThenError ( req , err )
}
if aliasResp . AliasExists {
return util . MessageResponse ( 400 , "Alias already exists" )
}
}
2017-07-06 05:44:15 -05:00
response := createRoomResponse {
2017-09-27 10:44:40 -05:00
RoomID : roomID ,
RoomAlias : roomAlias ,
2017-07-06 05:44:15 -05:00
}
2017-03-10 05:32:53 -06:00
return util . JSONResponse {
Code : 200 ,
2017-07-06 05:44:15 -05:00
JSON : response ,
2017-03-10 05:32:53 -06:00
}
}
// buildEvent fills out auth_events for the builder then builds the event
func buildEvent ( builder * gomatrixserverlib . EventBuilder ,
2017-03-17 11:28:15 -05:00
provider gomatrixserverlib . AuthEventProvider ,
2017-06-19 09:21:04 -05:00
cfg config . Dendrite ) ( * gomatrixserverlib . Event , error ) {
2017-03-10 05:32:53 -06:00
eventsNeeded , err := gomatrixserverlib . StateNeededForEventBuilder ( builder )
if err != nil {
return nil , err
}
2017-03-17 11:28:15 -05:00
refs , err := eventsNeeded . AuthEventReferences ( provider )
if err != nil {
return nil , err
}
builder . AuthEvents = refs
2017-06-19 09:21:04 -05:00
eventID := fmt . Sprintf ( "$%s:%s" , util . RandomString ( 16 ) , cfg . Matrix . ServerName )
2017-03-10 05:32:53 -06:00
now := time . Now ( )
2017-06-19 09:21:04 -05:00
event , err := builder . Build ( eventID , now , cfg . Matrix . ServerName , cfg . Matrix . KeyID , cfg . Matrix . PrivateKey )
2017-03-10 05:32:53 -06:00
if err != nil {
return nil , fmt . Errorf ( "cannot build event %s : Builder failed to build. %s" , builder . Type , err )
}
return & event , nil
}