From f286a4fcd46ff6c2c42a8732d004d8188aaa65f8 Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Fri, 5 Sep 2014 15:50:44 +0200 Subject: [PATCH 001/317] Fixed empty display name (content.displayname in a room member can be null) --- webclient/components/matrix/event-handler-service.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js index ee478d2eb..79d352a7c 100644 --- a/webclient/components/matrix/event-handler-service.js +++ b/webclient/components/matrix/event-handler-service.js @@ -26,6 +26,9 @@ Typically, this service will store events or broadcast them to any listeners (e.g. controllers) via $broadcast. Alternatively, it may update the $rootScope if typically all the $on method would do is update its own $scope. */ + +var toto; + angular.module('eventHandlerService', []) .factory('eventHandlerService', ['matrixService', '$rootScope', '$q', function(matrixService, $rootScope, $q) { var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT"; @@ -38,6 +41,9 @@ angular.module('eventHandlerService', []) var InitialSyncDeferred = $q.defer(); + + toto = $rootScope; + $rootScope.events = { rooms: {} // will contain roomId: { messages:[], members:{userid1: event} } }; From ec1cc29ecbc03e968789d799fbc2054468a36147 Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Fri, 5 Sep 2014 15:51:34 +0200 Subject: [PATCH 002/317] Revert "Fixed empty display name (content.displayname in a room member can be null)" This reverts commit f286a4fcd46ff6c2c42a8732d004d8188aaa65f8. --- webclient/components/matrix/event-handler-service.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/webclient/components/matrix/event-handler-service.js b/webclient/components/matrix/event-handler-service.js index 79d352a7c..ee478d2eb 100644 --- a/webclient/components/matrix/event-handler-service.js +++ b/webclient/components/matrix/event-handler-service.js @@ -26,9 +26,6 @@ Typically, this service will store events or broadcast them to any listeners (e.g. controllers) via $broadcast. Alternatively, it may update the $rootScope if typically all the $on method would do is update its own $scope. */ - -var toto; - angular.module('eventHandlerService', []) .factory('eventHandlerService', ['matrixService', '$rootScope', '$q', function(matrixService, $rootScope, $q) { var ROOM_CREATE_EVENT = "ROOM_CREATE_EVENT"; @@ -41,9 +38,6 @@ angular.module('eventHandlerService', []) var InitialSyncDeferred = $q.defer(); - - toto = $rootScope; - $rootScope.events = { rooms: {} // will contain roomId: { messages:[], members:{userid1: event} } }; From 4b7a5b7bfa792d91775821208aaef3be77e3163b Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Fri, 5 Sep 2014 15:54:34 +0200 Subject: [PATCH 003/317] Fixed empty display name (content.displayname in a room member can be null) --- webclient/components/matrix/matrix-filter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webclient/components/matrix/matrix-filter.js b/webclient/components/matrix/matrix-filter.js index 0922684e3..260e0827d 100644 --- a/webclient/components/matrix/matrix-filter.js +++ b/webclient/components/matrix/matrix-filter.js @@ -120,7 +120,9 @@ angular.module('matrixFilter', []) var room = $rootScope.events.rooms[room_id]; if (room && (user_id in room.members)) { var member = room.members[user_id]; - displayName = member.content.displayname; + if (member.content.displayname) { + displayName = member.content.displayname; + } } } From dcf0a6fbfd76257fbf3193ff128505dc0965f67d Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Fri, 5 Sep 2014 16:45:59 +0200 Subject: [PATCH 004/317] Display ban & kick reason --- webclient/recents/recents.html | 6 ++++++ webclient/room/room.html | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/webclient/recents/recents.html b/webclient/recents/recents.html index 1c059249b..280d0632a 100644 --- a/webclient/recents/recents.html +++ b/webclient/recents/recents.html @@ -34,11 +34,17 @@ {{ {"join": "kicked", "ban": "unbanned"}[room.lastMsg.content.prev] }} {{ room.lastMsg.state_key | mUserDisplayName: room.room_id }} + + : {{ room.lastMsg.content.reason }} + {{ room.lastMsg.user_id | mUserDisplayName: room.room_id }} {{ {"invite": "invited", "ban": "banned"}[room.lastMsg.content.membership] }} {{ room.lastMsg.state_key | mUserDisplayName: room.room_id }} + + : {{ room.lastMsg.content.reason }} + diff --git a/webclient/room/room.html b/webclient/room/room.html index 147113987..5bd2cc92d 100644 --- a/webclient/room/room.html +++ b/webclient/room/room.html @@ -62,13 +62,20 @@ {{ members[msg.user_id].displayname || msg.user_id }} {{ {"join": "kicked", "ban": "unbanned"}[msg.content.prev] }} {{ members[msg.state_key].displayname || msg.state_key }} + + : {{ msg.content.reason }} + {{ members[msg.user_id].displayname || msg.user_id }} {{ {"invite": "invited", "ban": "banned"}[msg.content.membership] }} {{ members[msg.state_key].displayname || msg.state_key }} - + + : {{ msg.content.reason }} + + + Date: Fri, 5 Sep 2014 16:56:50 +0200 Subject: [PATCH 005/317] BF: Make /unban work again --- webclient/components/matrix/matrix-service.js | 2 +- webclient/room/room-controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index 436ff5462..18a484129 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -169,7 +169,7 @@ angular.module('matrixService', []) // Change the membership of an another user setMembership: function(room_id, user_id, membershipValue) { - return this.setMemberShipObject(room_id, user_id, { + return this.setMembershipObject(room_id, user_id, { membership : membershipValue }); }, diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index b9ba23dc4..5a2f06d8e 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -351,7 +351,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) var matches = args.match(/^(\S+)$/); if (matches) { // Reset the user membership to "leave" to unban him - promise = matrixService.setMembership($scope.room_id, args, "leave"); + promise = matrixService.setMembership($scope.room_id, matches[1], "leave"); } else { $scope.feedback = "Usage: /unban "; From cf4c17deaf7110f52a22456fa3cb63a80825de14 Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Fri, 5 Sep 2014 17:23:41 +0200 Subject: [PATCH 006/317] Added sanity checks in commands --- webclient/room/room-controller.js | 89 +++++++++++++++++++------------ 1 file changed, 55 insertions(+), 34 deletions(-) diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 5a2f06d8e..39f8635d7 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -313,32 +313,44 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) case "/nick": // Change user display name - promise = matrixService.setDisplayName(args); + if (args) { + promise = matrixService.setDisplayName(args); + } + else { + $scope.feedback = "Usage: /nick "; + } break; case "/kick": - var matches = args.match(/^(\S+?)( +(.*))?$/); - if (matches.length === 2) { - promise = matrixService.setMembership($scope.room_id, matches[1], "leave"); + // Kick a user from the room with an optional reason + if (args) { + var matches = args.match(/^(\S+?)( +(.*))?$/); + if (matches.length === 2) { + promise = matrixService.setMembership($scope.room_id, matches[1], "leave"); + } + else if (matches.length === 4) { + promise = matrixService.setMembershipObject($scope.room_id, matches[1], { + membership: "leave", + reason: matches[3] // TODO: we need to specify resaon in the spec + }); + } } - else if (matches.length === 4) { - promise = matrixService.setMembershipObject($scope.room_id, matches[1], { - membership: "leave", - reason: matches[3] // TODO: we need to specify resaon in the spec - }); - } - else { + + if (!promise) { $scope.feedback = "Usage: /kick []"; } break; case "/ban": - // Ban a user from the room with optional reason - var matches = args.match(/^(\S+?)( +(.*))?$/); - if (matches) { - promise = matrixService.ban($scope.room_id, matches[1], matches[3]); + // Ban a user from the room with an optional reason + if (args) { + var matches = args.match(/^(\S+?)( +(.*))?$/); + if (matches) { + promise = matrixService.ban($scope.room_id, matches[1], matches[3]); + } } - else { + + if (!promise) { $scope.feedback = "Usage: /ban []"; } break; @@ -348,29 +360,35 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) // FIXME: this feels horribly asymmetrical - why are we banning via RPC // and unbanning by editing the membership list? // Why can't we specify a reason? - var matches = args.match(/^(\S+)$/); - if (matches) { - // Reset the user membership to "leave" to unban him - promise = matrixService.setMembership($scope.room_id, matches[1], "leave"); + if (args) { + var matches = args.match(/^(\S+)$/); + if (matches) { + // Reset the user membership to "leave" to unban him + promise = matrixService.setMembership($scope.room_id, matches[1], "leave"); + } } - else { + + if (!promise) { $scope.feedback = "Usage: /unban "; } break; case "/op": // Define the power level of a user - var matches = args.match(/^(\S+?)( +(\d+))?$/); - var powerLevel = 50; // default power level for op - if (matches) { - var user_id = matches[1]; - if (matches.length === 4) { - powerLevel = parseInt(matches[3]); - } - if (powerLevel !== NaN) { - promise = matrixService.setUserPowerLevel($scope.room_id, user_id, powerLevel); + if (args) { + var matches = args.match(/^(\S+?)( +(\d+))?$/); + var powerLevel = 50; // default power level for op + if (matches) { + var user_id = matches[1]; + if (matches.length === 4) { + powerLevel = parseInt(matches[3]); + } + if (powerLevel !== NaN) { + promise = matrixService.setUserPowerLevel($scope.room_id, user_id, powerLevel); + } } } + if (!promise) { $scope.feedback = "Usage: /op []"; } @@ -378,11 +396,14 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) case "/deop": // Reset the power level of a user - var matches = args.match(/^(\S+)$/); - if (matches) { - promise = matrixService.setUserPowerLevel($scope.room_id, args, undefined); + if (args) { + var matches = args.match(/^(\S+)$/); + if (matches) { + promise = matrixService.setUserPowerLevel($scope.room_id, args, undefined); + } } - else { + + if (!promise) { $scope.feedback = "Usage: /deop "; } break; From 3be615677407a4224202a3ae107762b1f3bc97ac Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Fri, 5 Sep 2014 17:30:50 +0200 Subject: [PATCH 007/317] Created kick & unban methods in matrixService. Made some factorisation. --- webclient/components/matrix/matrix-service.js | 32 ++++++++++++------- webclient/room/room-controller.js | 15 ++------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index 18a484129..8a0223979 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -168,23 +168,20 @@ angular.module('matrixService', []) }, // Change the membership of an another user - setMembership: function(room_id, user_id, membershipValue) { - return this.setMembershipObject(room_id, user_id, { - membership : membershipValue - }); - }, - - // Change the membership of an another user - setMembershipObject: function(room_id, user_id, membershipObject) { + setMembership: function(room_id, user_id, membershipValue, reason) { + // The REST path spec var path = "/rooms/$room_id/state/m.room.member/$user_id"; path = path.replace("$room_id", encodeURIComponent(room_id)); path = path.replace("$user_id", user_id); - return doRequest("PUT", path, undefined, membershipObject); + return doRequest("PUT", path, undefined, { + membership : membershipValue, + reason: reason + }); }, - // Bans a user from from a room + // Bans a user from a room ban: function(room_id, user_id, reason) { var path = "/rooms/$room_id/ban"; path = path.replace("$room_id", encodeURIComponent(room_id)); @@ -194,7 +191,20 @@ angular.module('matrixService', []) reason: reason }); }, - + + // Unbans a user in a room + unban: function(room_id, user_id) { + // FIXME: To update when there will be homeserver API for unban + // For now, do an unban by resetting the user membership to "leave" + return this.setMembership(room_id, user_id, "leave"); + }, + + // Kicks a user from a room + kick: function(room_id, user_id, reason) { + // Set the user membership to "leave" to kick him + return this.setMembership(room_id, user_id, "leave", reason); + }, + // Retrieves the room ID corresponding to a room alias resolveRoomAlias:function(room_alias) { var path = "/_matrix/client/api/v1/directory/room/$room_alias"; diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 39f8635d7..905a0723d 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -325,15 +325,9 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) // Kick a user from the room with an optional reason if (args) { var matches = args.match(/^(\S+?)( +(.*))?$/); - if (matches.length === 2) { - promise = matrixService.setMembership($scope.room_id, matches[1], "leave"); + if (matches) { + promise = matrixService.kick($scope.room_id, matches[1], matches[3]); } - else if (matches.length === 4) { - promise = matrixService.setMembershipObject($scope.room_id, matches[1], { - membership: "leave", - reason: matches[3] // TODO: we need to specify resaon in the spec - }); - } } if (!promise) { @@ -357,14 +351,11 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) case "/unban": // Unban a user from the room - // FIXME: this feels horribly asymmetrical - why are we banning via RPC - // and unbanning by editing the membership list? - // Why can't we specify a reason? if (args) { var matches = args.match(/^(\S+)$/); if (matches) { // Reset the user membership to "leave" to unban him - promise = matrixService.setMembership($scope.room_id, matches[1], "leave"); + promise = matrixService.unban($scope.room_id, matches[1]); } } From 3a888089832c18306d99d429a1f87d3c685e0a60 Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Fri, 5 Sep 2014 17:32:35 +0200 Subject: [PATCH 008/317] doc: kick can take a reason arg --- webclient/settings/settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webclient/settings/settings.html b/webclient/settings/settings.html index 0af137d0a..924812e7a 100644 --- a/webclient/settings/settings.html +++ b/webclient/settings/settings.html @@ -81,7 +81,7 @@
  • /nick <display_name>: change your display name
  • /me <action>: send the action you are doing. /me will be replaced by your display name
  • -
  • /kick <user_id>: kick the user
  • +
  • /kick <user_id> [<reason>]: kick the user
  • /ban <user_id> [<reason>]: ban the user
  • /unban <user_id>: unban the user
  • /op <user_id> <power_level>: set user power level
  • From 12a23f01b4d2b4a9a10b3db5373d092136e9a772 Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Fri, 5 Sep 2014 17:52:11 +0200 Subject: [PATCH 009/317] autoscroll down(if the scroller was already at the bottom) when receiving member events --- webclient/room/room-controller.js | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 905a0723d..2267283fb 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -42,23 +42,24 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) $scope.imageURLToSend = ""; $scope.userIDToInvite = ""; - var scrollToBottom = function() { + var scrollToBottom = function(force) { console.log("Scrolling to bottom"); - $timeout(function() { - var objDiv = document.getElementById("messageTableWrapper"); - objDiv.scrollTop = objDiv.scrollHeight; - }, 0); + + // Do not autoscroll to the bottom to display the new event if the user is not at the bottom. + // Exception: in case where the event is from the user, we want to force scroll to the bottom + var objDiv = document.getElementById("messageTableWrapper"); + if ((objDiv.offsetHeight + objDiv.scrollTop >= objDiv.scrollHeight) || force) { + + $timeout(function() { + objDiv.scrollTop = objDiv.scrollHeight; + }, 0); + } }; $scope.$on(eventHandlerService.MSG_EVENT, function(ngEvent, event, isLive) { if (isLive && event.room_id === $scope.room_id) { - - // Do not autoscroll to the bottom to display this new event if the user is not at the bottom. - // Exception: if the event is from the user, scroll to the bottom - var objDiv = document.getElementById("messageTableWrapper"); - if ( (objDiv.offsetHeight + objDiv.scrollTop >= objDiv.scrollHeight) || event.user_id === $scope.state.user_id) { - scrollToBottom(); - } + + scrollToBottom(); if (window.Notification) { // Show notification when the user is idle @@ -80,6 +81,7 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) $scope.$on(eventHandlerService.MEMBER_EVENT, function(ngEvent, event, isLive) { if (isLive) { + scrollToBottom(); updateMemberList(event); } }); @@ -288,6 +290,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) return; } + scrollToBottom(true); + var promise; var isCmd = false; @@ -614,7 +618,8 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) }; $scope.sendImage = function(url, body) { - + scrollToBottom(true); + matrixService.sendImageMessage($scope.room_id, url, body).then( function() { console.log("Image sent"); From 8a7f7f50044bb277e8d2e9c08dc3cee1d88ab1ab Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Fri, 5 Sep 2014 18:05:23 +0200 Subject: [PATCH 010/317] BF: Update the members list on banned & kicked "events" --- webclient/room/room-controller.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/webclient/room/room-controller.js b/webclient/room/room-controller.js index 2267283fb..8203b6ed3 100644 --- a/webclient/room/room-controller.js +++ b/webclient/room/room-controller.js @@ -175,16 +175,18 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) var updateMemberList = function(chunk) { if (chunk.room_id != $scope.room_id) return; - // Ignore banned and kicked (leave) people - if ("ban" === chunk.membership || "leave" === chunk.membership) { - return; - } // set target_user_id to keep things clear var target_user_id = chunk.state_key; var isNewMember = !(target_user_id in $scope.members); if (isNewMember) { + + // Ignore banned and kicked (leave) people + if ("ban" === chunk.membership || "leave" === chunk.membership) { + return; + } + // FIXME: why are we copying these fields around inside chunk? if ("presence" in chunk.content) { chunk.presence = chunk.content.presence; @@ -208,6 +210,13 @@ angular.module('RoomController', ['ngSanitize', 'matrixFilter', 'mFileInput']) } else { // selectively update membership and presence else it will nuke the picture and displayname too :/ + + // Remove banned and kicked (leave) people + if ("ban" === chunk.membership || "leave" === chunk.membership) { + delete $scope.members[target_user_id]; + return; + } + var member = $scope.members[target_user_id]; member.membership = chunk.content.membership; if ("presence" in chunk.content) { From 95037d8d9df113fef85953a6f277b095bda997ad Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 4 Sep 2014 16:16:26 +0100 Subject: [PATCH 011/317] Change the default power levels to be 0, 50 and 100 --- synapse/api/auth.py | 4 ++-- synapse/handlers/room.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/synapse/api/auth.py b/synapse/api/auth.py index b4eda3df0..0681a1f71 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -172,7 +172,7 @@ class Auth(object): if kick_level: kick_level = int(kick_level) else: - kick_level = 5 + kick_level = 50 if user_level < kick_level: raise AuthError( @@ -189,7 +189,7 @@ class Auth(object): if ban_level: ban_level = int(ban_level) else: - ban_level = 5 # FIXME (erikj): What should we do here? + ban_level = 50 # FIXME (erikj): What should we do here? if user_level < ban_level: raise AuthError(403, "You don't have permission to ban") diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 8171e9eb4..171ca3d79 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -132,7 +132,7 @@ class RoomCreationHandler(BaseRoomHandler): etype=RoomNameEvent.TYPE, room_id=room_id, user_id=user_id, - required_power_level=5, + required_power_level=50, content={"name": name}, ) @@ -143,7 +143,7 @@ class RoomCreationHandler(BaseRoomHandler): etype=RoomNameEvent.TYPE, room_id=room_id, user_id=user_id, - required_power_level=5, + required_power_level=50, content={"name": name}, ) @@ -155,7 +155,7 @@ class RoomCreationHandler(BaseRoomHandler): etype=RoomTopicEvent.TYPE, room_id=room_id, user_id=user_id, - required_power_level=5, + required_power_level=50, content={"topic": topic}, ) @@ -186,7 +186,7 @@ class RoomCreationHandler(BaseRoomHandler): event_keys = { "room_id": room_id, "user_id": creator.to_string(), - "required_power_level": 10, + "required_power_level": 100, } def create(etype, **content): @@ -203,7 +203,7 @@ class RoomCreationHandler(BaseRoomHandler): power_levels_event = self.event_factory.create_event( etype=RoomPowerLevelsEvent.TYPE, - content={creator.to_string(): 10, "default": 0}, + content={creator.to_string(): 100, "default": 0}, **event_keys ) @@ -215,7 +215,7 @@ class RoomCreationHandler(BaseRoomHandler): add_state_event = create( etype=RoomAddStateLevelEvent.TYPE, - level=10, + level=100, ) send_event = create( @@ -225,8 +225,8 @@ class RoomCreationHandler(BaseRoomHandler): ops = create( etype=RoomOpsPowerLevelsEvent.TYPE, - ban_level=5, - kick_level=5, + ban_level=50, + kick_level=50, ) return [ From 250ee2ea7db628036229a80b8317cd796a097ab2 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 4 Sep 2014 16:40:23 +0100 Subject: [PATCH 012/317] AUth the contents of power level events --- synapse/api/auth.py | 73 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 0681a1f71..5d7c60770 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -19,7 +19,7 @@ from twisted.internet import defer from synapse.api.constants import Membership, JoinRules from synapse.api.errors import AuthError, StoreError, Codes -from synapse.api.events.room import RoomMemberEvent +from synapse.api.events.room import RoomMemberEvent, RoomPowerLevelsEvent from synapse.util.logutils import log_function import logging @@ -67,6 +67,9 @@ class Auth(object): else: yield self._can_send_event(event) + if event.type == RoomPowerLevelsEvent.TYPE: + yield self._check_power_levels(event) + defer.returnValue(True) else: raise AuthError(500, "Unknown event: %s" % event) @@ -315,3 +318,71 @@ class Auth(object): 403, "You don't have permission to change that state" ) + + @defer.inlineCallbacks + def _check_power_levels(self, event): + current_state = yield self.store.get_current_state( + event.room_id, + event.type, + event.state_key, + ) + + user_level = yield self.store.get_power_level( + event.room_id, + event.user_id, + ) + + if user_level: + user_level = int(user_level) + else: + user_level = 0 + + old_list = current_state.content + + # FIXME (erikj) + old_people = {k: v for k, v in old_list.items() if k.startswith("@")} + new_people = {k: v for k, v in event.content if k.startswith("@")} + + removed = set(old_people.keys()) - set(new_people.keys()) + added = set(old_people.keys()) - set(new_people.keys()) + same = set(old_people.keys()) & set(new_people.keys()) + + for r in removed: + if int(old_list.content[r]) > user_level: + raise AuthError( + 403, + "You don't have permission to change that state" + ) + + for n in new_people: + if int(event.content[n]) > user_level: + raise AuthError( + 403, + "You don't have permission to change that state" + ) + + for s in same: + if int(event.content[s]) != int(old_list[s]): + if int(old_list[s]) > user_level: + raise AuthError( + 403, + "You don't have permission to change that state" + ) + + if "default" in old_list: + old_default = int(old_list["default"]) + + if old_default > user_level: + raise AuthError( + 403, + "You don't have permission to change that state" + ) + + if "default" in event.content: + new_default = int(event.content["default"]) + + if new_default > user_level: + raise AuthError( + 403, + "You don't have permission to change that state" + ) From 982604fbf2c96c05a98b5787f92c40d600c96c99 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Thu, 4 Sep 2014 18:09:17 +0100 Subject: [PATCH 013/317] Empty string is not a valid JSON object, so don't return them in HTTP responses. --- synapse/rest/login.py | 2 +- synapse/rest/profile.py | 4 ++-- synapse/rest/room.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/synapse/rest/login.py b/synapse/rest/login.py index c7bf901c8..ba49afcaa 100644 --- a/synapse/rest/login.py +++ b/synapse/rest/login.py @@ -70,7 +70,7 @@ class LoginFallbackRestServlet(RestServlet): def on_GET(self, request): # TODO(kegan): This should be returning some HTML which is capable of # hitting LoginRestServlet - return (200, "") + return (200, {}) def _parse_json(request): diff --git a/synapse/rest/profile.py b/synapse/rest/profile.py index 2e17f87fa..dad5a208c 100644 --- a/synapse/rest/profile.py +++ b/synapse/rest/profile.py @@ -51,7 +51,7 @@ class ProfileDisplaynameRestServlet(RestServlet): yield self.handlers.profile_handler.set_displayname( user, auth_user, new_name) - defer.returnValue((200, "")) + defer.returnValue((200, {})) def on_OPTIONS(self, request, user_id): return (200, {}) @@ -86,7 +86,7 @@ class ProfileAvatarURLRestServlet(RestServlet): yield self.handlers.profile_handler.set_avatar_url( user, auth_user, new_name) - defer.returnValue((200, "")) + defer.returnValue((200, {})) def on_OPTIONS(self, request, user_id): return (200, {}) diff --git a/synapse/rest/room.py b/synapse/rest/room.py index 308b44709..cef700c81 100644 --- a/synapse/rest/room.py +++ b/synapse/rest/room.py @@ -154,14 +154,14 @@ class RoomStateEventRestServlet(RestServlet): # membership events are special handler = self.handlers.room_member_handler yield handler.change_membership(event) - defer.returnValue((200, "")) + defer.returnValue((200, {})) else: # store random bits of state msg_handler = self.handlers.message_handler yield msg_handler.store_room_data( event=event ) - defer.returnValue((200, "")) + defer.returnValue((200, {})) # TODO: Needs unit testing for generic events + feedback @@ -249,7 +249,7 @@ class JoinRoomAliasServlet(RestServlet): ) handler = self.handlers.room_member_handler yield handler.change_membership(event) - defer.returnValue((200, "")) + defer.returnValue((200, {})) @defer.inlineCallbacks def on_PUT(self, request, room_identifier, txn_id): @@ -416,7 +416,7 @@ class RoomMembershipRestServlet(RestServlet): ) handler = self.handlers.room_member_handler yield handler.change_membership(event) - defer.returnValue((200, "")) + defer.returnValue((200, {})) @defer.inlineCallbacks def on_PUT(self, request, room_id, membership_action, txn_id): From b3be06667d7b0968b808c77487a2909578a729cd Mon Sep 17 00:00:00 2001 From: Emmanuel ROHEE Date: Fri, 5 Sep 2014 18:46:34 +0200 Subject: [PATCH 014/317] BF: tab completion did not work with commands. $scope.input contained only the typed chars not the result of the completion. Needed to fire an event so that ng update the input model --- webclient/room/room-directive.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/webclient/room/room-directive.js b/webclient/room/room-directive.js index 659bcbc60..e033b003e 100644 --- a/webclient/room/room-directive.js +++ b/webclient/room/room-directive.js @@ -48,6 +48,9 @@ angular.module('RoomController') var search = /@?([a-zA-Z0-9_\-:\.]+)$/.exec(text); if (targetIndex === 0) { element[0].value = text; + + // Force angular to wake up and update the input ng-model by firing up input event + angular.element(element[0]).triggerHandler('input'); } else if (search && search[1]) { // console.log("search found: " + search); @@ -81,7 +84,10 @@ angular.module('RoomController') expansion += " "; element[0].value = text.replace(/@?([a-zA-Z0-9_\-:\.]+)$/, expansion); // cancel blink - element[0].className = ""; + element[0].className = ""; + + // Force angular to wake up and update the input ng-model by firing up input event + angular.element(element[0]).triggerHandler('input'); } else { // console.log("wrapped!"); @@ -91,6 +97,9 @@ angular.module('RoomController') }, 150); element[0].value = text; scope.tabCompleteIndex = 0; + + // Force angular to wake up and update the input ng-model by firing up input event + angular.element(element[0]).triggerHandler('input'); } } else { From b4e1c1f51e42dee478443323a331499ce91cf8a7 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 5 Sep 2014 12:46:48 -0700 Subject: [PATCH 015/317] Minor spec tweaks. --- docs/specification.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/specification.rst b/docs/specification.rst index 239e51b4f..a329c0754 100644 --- a/docs/specification.rst +++ b/docs/specification.rst @@ -742,15 +742,17 @@ There are several APIs provided to ``GET`` events for a room: Description: Get all ``m.room.member`` state events. Response format: - ``{ "start": "token", "end": "token", "chunk": [ { m.room.member event }, ... ] }`` + ``{ "start": "", "end": "", "chunk": [ { m.room.member event }, ... ] }`` Example: TODO |/rooms//messages|_ Description: - Get all ``m.room.message`` events. + Get all ``m.room.message`` and ``m.room.member`` events. This API supports pagination + using ``from`` and ``to`` query parameters, coupled with the ``start`` and ``end`` + tokens from an |initialSync|_ API. Response format: - ``{ TODO }`` + ``{ "start": "", "end": "" }`` Example: TODO From 0280176ccddb9a1f142ad14a9a8a6e97686b0a4d Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 5 Sep 2014 13:31:47 -0700 Subject: [PATCH 016/317] Added basic captcha, not hooked up --- webclient/index.html | 3 ++- webclient/login/register-controller.js | 14 ++++++++++++++ webclient/login/register.html | 6 ++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/webclient/index.html b/webclient/index.html index 91b6bf27b..fe62d95bb 100644 --- a/webclient/index.html +++ b/webclient/index.html @@ -10,7 +10,8 @@ - + + diff --git a/webclient/login/register-controller.js b/webclient/login/register-controller.js index 5a1496424..1c1f4c42f 100644 --- a/webclient/login/register-controller.js +++ b/webclient/login/register-controller.js @@ -142,6 +142,20 @@ angular.module('RegisterController', ['matrixService']) } ); }; + + var setupCaptcha = function() { + console.log("Setting up ReCaptcha") + Recaptcha.create("6Le31_kSAAAAAK-54VKccKamtr-MFA_3WS1d_fGV", + "regcaptcha", + { + theme: "red", + callback: Recaptcha.focus_response_field + }); + }; + $scope.init = function() { + setupCaptcha(); + }; + }]); diff --git a/webclient/login/register.html b/webclient/login/register.html index 06a6526b7..a27f9ad4e 100644 --- a/webclient/login/register.html +++ b/webclient/login/register.html @@ -12,7 +12,6 @@

    -
    Specifying an email address lets other users find you on Matrix more easily,
    and will give you a way to reset your password in the future
    @@ -26,7 +25,10 @@

    - + + +
    +
    From 9dd4570b68fea123fda216b8fc8625fafc9d8e0a Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Fri, 5 Sep 2014 21:35:56 +0100 Subject: [PATCH 017/317] Generate m.room.aliases event when the HS creates a room alias --- synapse/api/auth.py | 7 +++++- synapse/api/events/room.py | 7 ++++++ synapse/app/homeserver.py | 2 +- synapse/handlers/_base.py | 3 --- synapse/handlers/directory.py | 38 +++++++++++++++++++++++++---- synapse/handlers/message.py | 4 +-- synapse/handlers/room.py | 12 +++++---- synapse/rest/directory.py | 5 +++- synapse/storage/directory.py | 7 ++++++ synapse/storage/schema/delta/v3.sql | 27 ++++++++++++++++++++ 10 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 synapse/storage/schema/delta/v3.sql diff --git a/synapse/api/auth.py b/synapse/api/auth.py index 5d7c60770..df6179455 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -327,6 +327,11 @@ class Auth(object): event.state_key, ) + if not current_state: + return + else: + current_state = current_state[0] + user_level = yield self.store.get_power_level( event.room_id, event.user_id, @@ -341,7 +346,7 @@ class Auth(object): # FIXME (erikj) old_people = {k: v for k, v in old_list.items() if k.startswith("@")} - new_people = {k: v for k, v in event.content if k.startswith("@")} + new_people = {k: v for k, v in event.content.items() if k.startswith("@")} removed = set(old_people.keys()) - set(new_people.keys()) added = set(old_people.keys()) - set(new_people.keys()) diff --git a/synapse/api/events/room.py b/synapse/api/events/room.py index 33f0f0cb9..3a4dbc58c 100644 --- a/synapse/api/events/room.py +++ b/synapse/api/events/room.py @@ -173,3 +173,10 @@ class RoomOpsPowerLevelsEvent(SynapseStateEvent): def get_content_template(self): return {} + + +class RoomAliasesEvent(SynapseStateEvent): + TYPE = "m.room.aliases" + + def get_content_template(self): + return {} diff --git a/synapse/app/homeserver.py b/synapse/app/homeserver.py index 49cf928cc..d675d8c8f 100755 --- a/synapse/app/homeserver.py +++ b/synapse/app/homeserver.py @@ -57,7 +57,7 @@ SCHEMAS = [ # Remember to update this number every time an incompatible change is made to # database schema files, so the users will be informed on server restarts. -SCHEMA_VERSION = 2 +SCHEMA_VERSION = 3 class SynapseHomeServer(HomeServer): diff --git a/synapse/handlers/_base.py b/synapse/handlers/_base.py index 9989fe867..de4d23bbb 100644 --- a/synapse/handlers/_base.py +++ b/synapse/handlers/_base.py @@ -42,9 +42,6 @@ class BaseHandler(object): retry_after_ms=int(1000*(time_allowed - time_now)), ) - -class BaseRoomHandler(BaseHandler): - @defer.inlineCallbacks def _on_new_room_event(self, event, snapshot, extra_destinations=[], extra_users=[]): diff --git a/synapse/handlers/directory.py b/synapse/handlers/directory.py index 1b9e831fc..4ab00a761 100644 --- a/synapse/handlers/directory.py +++ b/synapse/handlers/directory.py @@ -19,8 +19,10 @@ from ._base import BaseHandler from synapse.api.errors import SynapseError from synapse.http.client import HttpClient +from synapse.api.events.room import RoomAliasesEvent import logging +import sqlite3 logger = logging.getLogger(__name__) @@ -37,7 +39,8 @@ class DirectoryHandler(BaseHandler): ) @defer.inlineCallbacks - def create_association(self, room_alias, room_id, servers=None): + def create_association(self, user_id, room_alias, room_id, servers=None): + # TODO(erikj): Do auth. if not room_alias.is_mine: @@ -54,12 +57,37 @@ class DirectoryHandler(BaseHandler): if not servers: raise SynapseError(400, "Failed to get server list") - yield self.store.create_room_alias_association( - room_alias, - room_id, - servers + + try: + yield self.store.create_room_alias_association( + room_alias, + room_id, + servers + ) + except sqlite3.IntegrityError: + defer.returnValue("Already exists") + + # TODO: Send the room event. + + aliases = yield self.store.get_aliases_for_room(room_id) + + event = self.event_factory.create_event( + etype=RoomAliasesEvent.TYPE, + state_key=self.hs.hostname, + room_id=room_id, + user_id=user_id, + content={"aliases": aliases}, ) + snapshot = yield self.store.snapshot_room( + room_id=room_id, + user_id=user_id, + ) + + yield self.state_handler.handle_new_event(event, snapshot) + yield self._on_new_room_event(event, snapshot, extra_users=[user_id]) + + @defer.inlineCallbacks def get_association(self, room_alias): room_id = None diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py index dad2bbd1a..87fc04478 100644 --- a/synapse/handlers/message.py +++ b/synapse/handlers/message.py @@ -19,7 +19,7 @@ from synapse.api.constants import Membership from synapse.api.events.room import RoomTopicEvent from synapse.api.errors import RoomError from synapse.streams.config import PaginationConfig -from ._base import BaseRoomHandler +from ._base import BaseHandler import logging @@ -27,7 +27,7 @@ logger = logging.getLogger(__name__) -class MessageHandler(BaseRoomHandler): +class MessageHandler(BaseHandler): def __init__(self, hs): super(MessageHandler, self).__init__(hs) diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py index 171ca3d79..3fa12841c 100644 --- a/synapse/handlers/room.py +++ b/synapse/handlers/room.py @@ -25,14 +25,14 @@ from synapse.api.events.room import ( RoomSendEventLevelEvent, RoomOpsPowerLevelsEvent, RoomNameEvent, ) from synapse.util import stringutils -from ._base import BaseRoomHandler +from ._base import BaseHandler import logging logger = logging.getLogger(__name__) -class RoomCreationHandler(BaseRoomHandler): +class RoomCreationHandler(BaseHandler): @defer.inlineCallbacks def create_room(self, user_id, room_id, config): @@ -105,7 +105,9 @@ class RoomCreationHandler(BaseRoomHandler): ) if room_alias: - yield self.store.create_room_alias_association( + directory_handler = self.hs.get_handlers().directory_handler + yield directory_handler.create_association( + user_id=user_id, room_id=room_id, room_alias=room_alias, servers=[self.hs.hostname], @@ -239,7 +241,7 @@ class RoomCreationHandler(BaseRoomHandler): ] -class RoomMemberHandler(BaseRoomHandler): +class RoomMemberHandler(BaseHandler): # TODO(paul): This handler currently contains a messy conflation of # low-level API that works on UserID objects and so on, and REST-level # API that takes ID strings and returns pagination chunks. These concerns @@ -560,7 +562,7 @@ class RoomMemberHandler(BaseRoomHandler): extra_users=[target_user] ) -class RoomListHandler(BaseRoomHandler): +class RoomListHandler(BaseHandler): @defer.inlineCallbacks def get_public_room_list(self): diff --git a/synapse/rest/directory.py b/synapse/rest/directory.py index 18df7c8d8..31849246a 100644 --- a/synapse/rest/directory.py +++ b/synapse/rest/directory.py @@ -45,6 +45,8 @@ class ClientDirectoryServer(RestServlet): @defer.inlineCallbacks def on_PUT(self, request, room_alias): + user = yield self.auth.get_user_by_req(request) + content = _parse_json(request) if not "room_id" in content: raise SynapseError(400, "Missing room_id key", @@ -69,12 +71,13 @@ class ClientDirectoryServer(RestServlet): try: yield dir_handler.create_association( - room_alias, room_id, servers + user.to_string(), room_alias, room_id, servers ) except SynapseError as e: raise e except: logger.exception("Failed to create association") + raise defer.returnValue((200, {})) diff --git a/synapse/storage/directory.py b/synapse/storage/directory.py index bf5544925..540eb4c2c 100644 --- a/synapse/storage/directory.py +++ b/synapse/storage/directory.py @@ -92,3 +92,10 @@ class DirectoryStore(SQLBaseStore): "server": server, } ) + + def get_aliases_for_room(self, room_id): + return self._simple_select_onecol( + "room_aliases", + {"room_id": room_id}, + "room_alias", + ) diff --git a/synapse/storage/schema/delta/v3.sql b/synapse/storage/schema/delta/v3.sql new file mode 100644 index 000000000..cade29598 --- /dev/null +++ b/synapse/storage/schema/delta/v3.sql @@ -0,0 +1,27 @@ +/* Copyright 2014 OpenMarket 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. + */ + + +CREATE INDEX IF NOT EXISTS room_aliases_alias ON room_aliases(room_alias); +CREATE INDEX IF NOT EXISTS room_aliases_id ON room_aliases(room_id); + + +CREATE INDEX IF NOT EXISTS room_alias_servers_alias ON room_alias_servers(room_alias); + +DELETE FROM room_aliases WHERE rowid NOT IN (SELECT max(rowid) FROM room_aliases GROUP BY room_alias, room_id); + +CREATE UNIQUE INDEX IF NOT EXISTS room_aliases_uniq ON room_aliases(room_alias, room_id); + +PRAGMA user_version = 3; From 480438eee685c83384d59d8d07477f41e6afad6b Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Fri, 5 Sep 2014 21:54:16 +0100 Subject: [PATCH 018/317] Validate power levels event changes. Change error messages to be more helpful. Fix bug where we checked the wrong power levels --- synapse/api/auth.py | 47 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/synapse/api/auth.py b/synapse/api/auth.py index df6179455..8f32191b5 100644 --- a/synapse/api/auth.py +++ b/synapse/api/auth.py @@ -18,7 +18,7 @@ from twisted.internet import defer from synapse.api.constants import Membership, JoinRules -from synapse.api.errors import AuthError, StoreError, Codes +from synapse.api.errors import AuthError, StoreError, Codes, SynapseError from synapse.api.events.room import RoomMemberEvent, RoomPowerLevelsEvent from synapse.util.logutils import log_function @@ -308,7 +308,9 @@ class Auth(object): else: user_level = 0 - logger.debug("Checking power level for %s, %s", event.user_id, user_level) + logger.debug( + "Checking power level for %s, %s", event.user_id, user_level + ) if current_state and hasattr(current_state, "required_power_level"): req = current_state.required_power_level @@ -321,6 +323,24 @@ class Auth(object): @defer.inlineCallbacks def _check_power_levels(self, event): + for k, v in event.content.items(): + if k == "default": + continue + + # FIXME (erikj): We don't want hsob_Ts in content. + if k == "hsob_ts": + continue + + try: + self.hs.parse_userid(k) + except: + raise SynapseError(400, "Not a valid user_id: %s" % (k,)) + + try: + int(v) + except: + raise SynapseError(400, "Not a valid power level: %s" % (v,)) + current_state = yield self.store.get_current_state( event.room_id, event.type, @@ -346,7 +366,10 @@ class Auth(object): # FIXME (erikj) old_people = {k: v for k, v in old_list.items() if k.startswith("@")} - new_people = {k: v for k, v in event.content.items() if k.startswith("@")} + new_people = { + k: v for k, v in event.content.items() + if k.startswith("@") + } removed = set(old_people.keys()) - set(new_people.keys()) added = set(old_people.keys()) - set(new_people.keys()) @@ -356,22 +379,24 @@ class Auth(object): if int(old_list.content[r]) > user_level: raise AuthError( 403, - "You don't have permission to change that state" + "You don't have permission to remove user: %s" % (r, ) ) - for n in new_people: + for n in added: if int(event.content[n]) > user_level: raise AuthError( 403, - "You don't have permission to change that state" + "You don't have permission to add ops level greater " + "than your own" ) for s in same: if int(event.content[s]) != int(old_list[s]): - if int(old_list[s]) > user_level: + if int(event.content[s]) > user_level: raise AuthError( 403, - "You don't have permission to change that state" + "You don't have permission to add ops level greater " + "than your own" ) if "default" in old_list: @@ -380,7 +405,8 @@ class Auth(object): if old_default > user_level: raise AuthError( 403, - "You don't have permission to change that state" + "You don't have permission to add ops level greater than " + "your own" ) if "default" in event.content: @@ -389,5 +415,6 @@ class Auth(object): if new_default > user_level: raise AuthError( 403, - "You don't have permission to change that state" + "You don't have permission to add ops level greater " + "than your own" ) From 130458385e919d886fcdfc4203354e93e9e8f1b1 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Fri, 5 Sep 2014 13:56:36 -0700 Subject: [PATCH 019/317] Modified matrixService.register to specify if captcha results should be sent with the registration request. This is toggleable via useCaptcha in register-controller. --- webclient/components/matrix/matrix-service.js | 26 ++++++++++++++++--- webclient/login/register-controller.js | 8 ++++-- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index 8a0223979..4754dc87d 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -84,15 +84,33 @@ angular.module('matrixService', []) prefix: prefixPath, // Register an user - register: function(user_name, password, threepidCreds) { + register: function(user_name, password, threepidCreds, useCaptcha) { // The REST path spec var path = "/register"; - - return doRequest("POST", path, undefined, { + + var data = { user_id: user_name, password: password, threepidCreds: threepidCreds - }); + }; + + if (useCaptcha) { + // Not all home servers will require captcha on signup, but if this flag is checked, + // send captcha information. + // TODO: Might be nice to make this a bit more flexible.. + var challengeToken = Recaptcha.get_challenge(); + var captchaEntry = Recaptcha.get_response(); + var captchaType = "m.login.recaptcha"; + + data.captcha = { + type: captchaType, + challenge: challengeToken, + response: captchaEntry + }; + console.log("Sending Captcha info: " + JSON.stringify(data.captcha)); + } + + return doRequest("POST", path, undefined, data); }, // Create a room diff --git a/webclient/login/register-controller.js b/webclient/login/register-controller.js index 1c1f4c42f..9d02f274d 100644 --- a/webclient/login/register-controller.js +++ b/webclient/login/register-controller.js @@ -19,6 +19,8 @@ angular.module('RegisterController', ['matrixService']) function($scope, $rootScope, $location, matrixService, eventStreamService) { 'use strict'; + var useCaptcha = false; + // FIXME: factor out duplication with login-controller.js // Assume that this is hosted on the home server, in which case the URL @@ -87,7 +89,7 @@ angular.module('RegisterController', ['matrixService']) }; $scope.registerWithMxidAndPassword = function(mxid, password, threepidCreds) { - matrixService.register(mxid, password, threepidCreds).then( + matrixService.register(mxid, password, threepidCreds, useCaptcha).then( function(response) { $scope.feedback = "Success"; // Update the current config @@ -154,7 +156,9 @@ angular.module('RegisterController', ['matrixService']) }; $scope.init = function() { - setupCaptcha(); + if (useCaptcha) { + setupCaptcha(); + } }; }]); From fc65b68f3095f446d00ac4c6bae106883b3f5eb9 Mon Sep 17 00:00:00 2001 From: Erik Johnston Date: Fri, 5 Sep 2014 22:01:10 +0100 Subject: [PATCH 020/317] Add m.roo.aliases --- docs/specification.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/specification.rst b/docs/specification.rst index 1d3c28333..6776579d3 100644 --- a/docs/specification.rst +++ b/docs/specification.rst @@ -909,6 +909,22 @@ prefixed with ``m.`` ``ban_level`` will be greater than or equal to ``kick_level`` since banning is more severe than kicking. +``m.room.aliases`` + Summary: + These state events are used to inform the room about what room aliases it has. + Type: + State event + JSON format: + ``{ "aliases": ["string", ...] }`` + Example: + ``{ "aliases": ["#foo:example.com"] }`` + Description: + A server `may` inform the room that it has added or removed an alias for + the room. This is purely for informational purposes and may become stale. + Clients `should` check that the room alias is still valid before using it. + The ``state_key`` of the event is the homeserver which owns the room + alias. + ``m.room.message`` Summary: A message. From c03c25530487e5051affb9685f6f7b0c37abf8e5 Mon Sep 17 00:00:00 2001 From: David Baker Date: Sat, 6 Sep 2014 00:14:02 +0100 Subject: [PATCH 021/317] Better call bar (visually: still lacks ring[back] tones). --- webclient/app-controller.js | 41 ++++++++++++++-- webclient/app.css | 44 +++++++++++++++++- webclient/components/matrix/matrix-call.js | 5 ++ webclient/components/matrix/matrix-service.js | 9 +++- webclient/img/green_phone.png | Bin 0 -> 434 bytes webclient/img/red_phone.png | Bin 0 -> 378 bytes webclient/index.html | 31 ++++++++---- 7 files changed, 112 insertions(+), 18 deletions(-) create mode 100644 webclient/img/green_phone.png create mode 100644 webclient/img/red_phone.png diff --git a/webclient/app-controller.js b/webclient/app-controller.js index ea48cbb01..064bde3ab 100644 --- a/webclient/app-controller.js +++ b/webclient/app-controller.js @@ -21,8 +21,8 @@ limitations under the License. 'use strict'; angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'eventStreamService']) -.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', 'matrixService', 'mPresence', 'eventStreamService', 'matrixPhoneService', - function($scope, $location, $rootScope, matrixService, mPresence, eventStreamService, matrixPhoneService) { +.controller('MatrixWebClientController', ['$scope', '$location', '$rootScope', '$timeout', '$animate', 'matrixService', 'mPresence', 'eventStreamService', 'matrixPhoneService', + function($scope, $location, $rootScope, $timeout, $animate, matrixService, mPresence, eventStreamService, matrixPhoneService) { // Check current URL to avoid to display the logout button on the login page $scope.location = $location.path(); @@ -89,6 +89,23 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even $scope.user_id = matrixService.config().user_id; }; + $rootScope.$watch('currentCall', function(newVal, oldVal) { + if (!$rootScope.currentCall) return; + + var roomMembers = angular.copy($rootScope.events.rooms[$rootScope.currentCall.room_id].members); + delete roomMembers[matrixService.config().user_id]; + + $rootScope.currentCall.user_id = Object.keys(roomMembers)[0]; + matrixService.getProfile($rootScope.currentCall.user_id).then( + function(response) { + $rootScope.currentCall.userProfile = response.data; + }, + function(error) { + $scope.feedback = "Can't load user profile"; + } + ); + }); + $rootScope.$on(matrixPhoneService.INCOMING_CALL_EVENT, function(ngEvent, call) { console.trace("incoming call"); call.onError = $scope.onCallError; @@ -97,12 +114,19 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even }); $scope.answerCall = function() { - $scope.currentCall.answer(); + $rootScope.currentCall.answer(); }; $scope.hangupCall = function() { - $scope.currentCall.hangup(); - $scope.currentCall = undefined; + $rootScope.currentCall.hangup(); + + $timeout(function() { + var icon = angular.element('#callEndedIcon'); + $animate.addClass(icon, 'callIconRotate'); + $timeout(function(){ + $rootScope.currentCall = undefined; + }, 2000); + }, 100); }; $rootScope.onCallError = function(errStr) { @@ -110,5 +134,12 @@ angular.module('MatrixWebClientController', ['matrixService', 'mPresence', 'even } $rootScope.onCallHangup = function() { + $timeout(function() { + var icon = angular.element('#callEndedIcon'); + $animate.addClass(icon, 'callIconRotate'); + $timeout(function(){ + $rootScope.currentCall = undefined; + }, 2000); + }, 100); } }]); diff --git a/webclient/app.css b/webclient/app.css index dbee02f83..e0ca2f77a 100755 --- a/webclient/app.css +++ b/webclient/app.css @@ -44,7 +44,49 @@ a:active { color: #000; } } #callBar { - float: left; + float: left; + height: 32px; + margin: auto; + text-align: right; + line-height: 16px; +} + +.callIcon { + margin-left: 4px; + margin-right: 4px; + margin-top: 8px; + -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; +} + +.callIconRotate { + -webkit-transform: rotateZ(45deg); + -moz-transform: rotateZ(45deg); + -ms-transform: rotateZ(45deg); + -o-transform: rotateZ(45deg); + transform: rotateZ(45deg); +} + +#callPeerImage { + width: 32px; + height: 32px; + border: none; + float: left; +} + +#callPeerNameAndState { + float: left; + margin-left: 4px; +} + +#callState { + font-size: 60%; +} + +#callPeerName { + font-size: 80%; } #headerContent { diff --git a/webclient/components/matrix/matrix-call.js b/webclient/components/matrix/matrix-call.js index 3e13e4e81..3cb5e8b69 100644 --- a/webclient/components/matrix/matrix-call.js +++ b/webclient/components/matrix/matrix-call.js @@ -41,6 +41,7 @@ angular.module('MatrixCall', []) this.room_id = room_id; this.call_id = "c" + new Date().getTime(); this.state = 'fledgling'; + this.didConnect = false; } navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia; @@ -52,6 +53,7 @@ angular.module('MatrixCall', []) matrixPhoneService.callPlaced(this); navigator.getUserMedia({audio: true, video: false}, function(s) { self.gotUserMediaForInvite(s); }, function(e) { self.getUserMediaFailed(e); }); self.state = 'wait_local_media'; + this.direction = 'outbound'; }; MatrixCall.prototype.initWithInvite = function(msg) { @@ -64,6 +66,7 @@ angular.module('MatrixCall', []) this.peerConn.onaddstream = function(s) { self.onAddStream(s); }; this.peerConn.setRemoteDescription(new RTCSessionDescription(this.msg.offer), self.onSetRemoteDescriptionSuccess, self.onSetRemoteDescriptionError); this.state = 'ringing'; + this.direction = 'inbound'; }; MatrixCall.prototype.answer = function() { @@ -204,10 +207,12 @@ angular.module('MatrixCall', []) }; MatrixCall.prototype.onIceConnectionStateChanged = function() { + if (this.state == 'ended') return; // because ICE can still complete as we're ending the call console.trace("Ice connection state changed to: "+this.peerConn.iceConnectionState); // ideally we'd consider the call to be connected when we get media but chrome doesn't implement nay of the 'onstarted' events yet if (this.peerConn.iceConnectionState == 'completed' || this.peerConn.iceConnectionState == 'connected') { this.state = 'connected'; + this.didConnect = true; $rootScope.$apply(); } }; diff --git a/webclient/components/matrix/matrix-service.js b/webclient/components/matrix/matrix-service.js index 8a0223979..55cbd4bc1 100644 --- a/webclient/components/matrix/matrix-service.js +++ b/webclient/components/matrix/matrix-service.js @@ -295,6 +295,11 @@ angular.module('matrixService', []) return doRequest("GET", path); }, + // get a user's profile + getProfile: function(userId) { + return this.getProfileInfo(userId); + }, + // get a display name for this user ID getDisplayName: function(userId) { return this.getProfileInfo(userId, "displayname"); @@ -328,8 +333,8 @@ angular.module('matrixService', []) }, getProfileInfo: function(userId, info_segment) { - var path = "/profile/$user_id/" + info_segment; - path = path.replace("$user_id", userId); + var path = "/profile/"+userId + if (info_segment) path += '/' + info_segment; return doRequest("GET", path); }, diff --git a/webclient/img/green_phone.png b/webclient/img/green_phone.png new file mode 100644 index 0000000000000000000000000000000000000000..28807c749b91fd7a9a12ee95b53874cd5baedd0e GIT binary patch literal 434 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf2?- z3=n2q{H)3b$dD{?jVKAuPb(=;EJ|f4FE7{2%*!rLPAo{(%P&fw{mw>;fq_xq)5S5w zqIc?~{n|$gWsb$4zs@TWxM^d=3Hbvmu98c5x>%bJCI|}etDWkzF0Yg=b+dQZMf1vUN{)|rCYnWq>6-@315C_lh#@Vqwu-%YMB Zyn42AHwsql=L3c>gQu&X%Q~loCIBpAtk?hm literal 0 HcmV?d00001 diff --git a/webclient/img/red_phone.png b/webclient/img/red_phone.png new file mode 100644 index 0000000000000000000000000000000000000000..11fc44940cb35654bc24874c4cd3c2377dbb353f GIT binary patch literal 378 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf2?- z3=n2q{H)3b$dD{?jVKAuPb(=;EJ|f4FE7{2%*!rLPAo{(%P&fw{mw=TsOY<=i(`mI z@6<`Vy__9IT(7r895HGtOG-^mpZ&!1xo_U++9`m1-Tm}*Rr>4aN<~a!*R!X zmJ}_W>E9k`KHkl^X6iJ9lW+DfzI;ijsX)~BfAkIg^9OYzS0Bo~+TW~Z`GfJKc8`$R T(~5b(AYkxx^>bP0l+XkKHXN5X literal 0 HcmV?d00001 diff --git a/webclient/index.html b/webclient/index.html index 91b6bf27b..a7ed9aa15 100644 --- a/webclient/index.html +++ b/webclient/index.html @@ -45,18 +45,29 @@