2017-04-20 23:40:52 +01: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 16:11:08 +00:00
package writers
import (
"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"
2017-03-10 11:32:53 +00:00
"time"
2017-03-07 16:11:08 +00:00
log "github.com/Sirupsen/logrus"
2017-05-23 17:43:05 +01:00
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
2017-07-25 16:10:59 +01:00
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
2017-03-10 11:32:53 +00:00
"github.com/matrix-org/dendrite/clientapi/events"
"github.com/matrix-org/dendrite/clientapi/httputil"
2017-03-09 11:47:06 +00:00
"github.com/matrix-org/dendrite/clientapi/jsonerror"
2017-03-15 13:36:26 +00:00
"github.com/matrix-org/dendrite/clientapi/producers"
2017-06-19 15:21:04 +01:00
"github.com/matrix-org/dendrite/common/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"
)
// 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 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 {
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 14:11:32 +01:00
if _ , _ , err := gomatrixserverlib . SplitID ( '@' , userID ) ; err != nil {
2017-03-09 11:47:06 +00: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 11:32:53 +00: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 16:11:08 +00:00
// CreateRoom implements /createRoom
2017-07-25 16:10:59 +01:00
func CreateRoom ( req * http . Request , device * authtypes . Device ,
cfg config . Dendrite , producer * producers . RoomserverProducer ,
accountDB * accounts . Database ,
) util . JSONResponse {
2017-03-09 11:47:06 +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?
2017-06-19 15:21:04 +01:00
roomID := fmt . Sprintf ( "!%s:%s" , util . RandomString ( 16 ) , cfg . Matrix . ServerName )
2017-07-25 16:10:59 +01:00
return createRoom ( req , device , cfg , roomID , producer , accountDB )
2017-03-09 11:47:06 +00:00
}
// createRoom implements /createRoom
2017-07-25 16:10:59 +01:00
func createRoom ( req * http . Request , device * authtypes . Device ,
cfg config . Dendrite , roomID string , producer * producers . RoomserverProducer ,
accountDB * accounts . Database ,
) util . JSONResponse {
2017-03-07 16:11:08 +00:00
logger := util . GetLogger ( req . Context ( ) )
2017-05-23 17:43:05 +01:00
userID := device . UserID
2017-03-07 16:11:08 +00:00
var r createRoomRequest
2017-05-23 17:43:05 +01:00
resErr := httputil . UnmarshalJSONRequest ( req , & r )
2017-03-07 16:11:08 +00:00
if resErr != nil {
return * resErr
}
2017-03-09 11:47:06 +00: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 16:11:08 +00:00
logger . WithFields ( log . Fields {
"userID" : userID ,
2017-03-09 11:47:06 +00:00
"roomID" : roomID ,
2017-03-10 11:32:53 +00:00
} ) . Info ( "Creating new room" )
2017-07-25 16:10:59 +01:00
localpart , _ , err := gomatrixserverlib . SplitID ( '@' , userID )
if err != nil {
return httputil . LogThenError ( req , err )
}
profile , err := accountDB . GetProfileByLocalpart ( localpart )
if err != nil {
return httputil . LogThenError ( req , err )
}
membershipContent := events . MemberContent {
Membership : "join" ,
DisplayName : profile . DisplayName ,
AvatarURL : profile . AvatarURL ,
}
2017-03-15 13:36:26 +00:00
var builtEvents [ ] gomatrixserverlib . Event
2017-03-09 11:47:06 +00: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 13:06:14 +01:00
// 7- m.room.guest_access (opt)
2017-03-09 11:47:06 +00: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 11:32:53 +00:00
// TODO: Synapse has txn/token ID on each event. Do we need to do this here?
eventsToMake := [ ] fledglingEvent {
{ "m.room.create" , "" , events . CreateContent { Creator : userID } } ,
2017-07-25 16:10:59 +01:00
{ "m.room.member" , userID , membershipContent } ,
2017-03-10 11:32:53 +00:00
{ "m.room.power_levels" , "" , events . InitialPowerLevelsContent ( userID ) } ,
// TODO: m.room.canonical_alias
{ "m.room.join_rules" , "" , events . JoinRulesContent { "public" } } , // FIXME: Allow this to be changed
{ "m.room.history_visibility" , "" , events . HistoryVisibilityContent { "joined" } } , // FIXME: Allow this to be changed
2017-07-20 13:06:14 +01:00
{ "m.room.guest_access" , "" , events . GuestAccessContent { "can_join" } } , // FIXME: Allow this to be changed
2017-03-10 11:32:53 +00:00
// TODO: Other initial state items
2017-07-20 13:06:14 +01:00
{ "m.room.name" , "" , events . NameContent { 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" , "" , events . TopicContent { r . Topic } } ,
2017-03-10 11:32:53 +00:00
// TODO: invite events
// TODO: 3pid invite events
2017-07-20 13:06:14 +01:00
// TODO: m.room.aliases
2017-03-10 11:32:53 +00:00
}
2017-03-10 17:54:17 +00:00
authEvents := gomatrixserverlib . NewAuthEvents ( nil )
2017-03-10 11:32:53 +00: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 ) ,
}
builder . SetContent ( e . Content )
if i > 0 {
builder . PrevEvents = [ ] gomatrixserverlib . EventReference { builtEvents [ i - 1 ] . EventReference ( ) }
}
2017-03-17 16:28:15 +00:00
ev , err := buildEvent ( & builder , & authEvents , cfg )
2017-03-10 11:32:53 +00:00
if err != nil {
2017-03-10 16:50:41 +00:00
return httputil . LogThenError ( req , err )
2017-03-10 11:32:53 +00:00
}
if err := gomatrixserverlib . Allowed ( * ev , & authEvents ) ; err != nil {
2017-03-10 16:50:41 +00:00
return httputil . LogThenError ( req , err )
2017-03-10 11:32:53 +00:00
}
// Add the event to the list of auth events
2017-03-15 13:36:26 +00:00
builtEvents = append ( builtEvents , * ev )
2017-03-10 17:54:17 +00:00
authEvents . AddEvent ( ev )
2017-03-10 16:19:23 +00:00
}
2017-03-10 11:32:53 +00:00
2017-03-10 16:19:23 +00:00
// send events to the room server
2017-06-27 15:28:44 +01:00
if err := producer . SendEvents ( builtEvents , cfg . Matrix . ServerName ) ; err != nil {
2017-03-10 16:50:41 +00:00
return httputil . LogThenError ( req , err )
2017-03-10 11:32:53 +00:00
}
2017-07-06 11:44:15 +01:00
response := createRoomResponse {
RoomID : roomID ,
}
2017-03-10 11:32:53 +00:00
return util . JSONResponse {
Code : 200 ,
2017-07-06 11:44:15 +01:00
JSON : response ,
2017-03-10 11:32:53 +00:00
}
}
// buildEvent fills out auth_events for the builder then builds the event
func buildEvent ( builder * gomatrixserverlib . EventBuilder ,
2017-03-17 16:28:15 +00:00
provider gomatrixserverlib . AuthEventProvider ,
2017-06-19 15:21:04 +01:00
cfg config . Dendrite ) ( * gomatrixserverlib . Event , error ) {
2017-03-10 11:32:53 +00:00
eventsNeeded , err := gomatrixserverlib . StateNeededForEventBuilder ( builder )
if err != nil {
return nil , err
}
2017-03-17 16:28:15 +00:00
refs , err := eventsNeeded . AuthEventReferences ( provider )
if err != nil {
return nil , err
}
builder . AuthEvents = refs
2017-06-19 15:21:04 +01:00
eventID := fmt . Sprintf ( "$%s:%s" , util . RandomString ( 16 ) , cfg . Matrix . ServerName )
2017-03-10 11:32:53 +00:00
now := time . Now ( )
2017-06-19 15:21:04 +01:00
event , err := builder . Build ( eventID , now , cfg . Matrix . ServerName , cfg . Matrix . KeyID , cfg . Matrix . PrivateKey )
2017-03-10 11:32:53 +00:00
if err != nil {
return nil , fmt . Errorf ( "cannot build event %s : Builder failed to build. %s" , builder . Type , err )
}
return & event , nil
}