diff --git a/skins/base/views/molecules/MemberInfo.js b/skins/base/views/molecules/MemberInfo.js
index 3a81185b..8e1e383f 100644
--- a/skins/base/views/molecules/MemberInfo.js
+++ b/skins/base/views/molecules/MemberInfo.js
@@ -67,7 +67,35 @@ module.exports = React.createClass({
         if (this.state.active >= 0) {
             activeAgo = this.getDuration(this.state.active);
         }
+        var kickButton, banButton, muteButton, giveModButton;
+        if (this.state.can.kick) {
+            kickButton = <div className="mx_MemberInfo_button" onClick={this.onKick}>
+                Kick
+            </div>;
+        }
+        if (this.state.can.ban) {
+            banButton = <div className="mx_MemberInfo_button" onClick={this.onBan}>
+                Ban
+            </div>;
+        }
+        if (this.state.can.mute) {
+            var muteLabel = this.state.muted ? "Unmute" : "Mute";
+            muteButton = <div className="mx_MemberInfo_button" onClick={this.onMuteToggle}>
+                {muteLabel}
+            </div>;
+        }
+        if (this.state.can.modifyLevel) {
+            var giveOpLabel = this.state.isTargetMod ? "Revoke Mod" : "Make Mod";
+            giveModButton = <div className="mx_MemberInfo_button" onClick={this.onModToggle}>
+                {giveOpLabel}
+            </div>
+        }
 
+        var opLabel;
+        if (this.state.isTargetMod) {
+            var level = this.props.member.powerLevelNorm + "%";
+            opLabel = <div className="mx_MemberInfo_field">Moderator ({level})</div>
+        }
         return (
             <div className="mx_MemberInfo">
                 <img className="mx_MemberInfo_chevron" src="img/chevron-right.png" width="9" height="16" />
@@ -78,9 +106,14 @@ module.exports = React.createClass({
                          width="128" height="128" alt=""/>
                 </div>
                 <div className="mx_MemberInfo_field">{this.props.member.userId}</div>
+                {opLabel}
                 <div className="mx_MemberInfo_field">Presence: {this.state.presence}</div>
                 <div className="mx_MemberInfo_field">Last active: {activeAgo}</div>
                 <div className="mx_MemberInfo_button" onClick={this.onChatClick}>Start chat</div>
+                {muteButton}
+                {kickButton}
+                {banButton}
+                {giveModButton}
             </div>
         );
     }
diff --git a/src/controllers/molecules/MemberInfo.js b/src/controllers/molecules/MemberInfo.js
index 96f8bb8c..4f222c5d 100644
--- a/src/controllers/molecules/MemberInfo.js
+++ b/src/controllers/molecules/MemberInfo.js
@@ -18,15 +18,27 @@ limitations under the License.
  * State vars:
  * 'presence' : string (online|offline|unavailable etc)
  * 'active' : number (ms ago; can be -1)
+ * 'can': {
+ *   kick: boolean,
+ *   ban: boolean,
+ *   mute: boolean,
+ *   modifyLevel: boolean
+ * },
+ * 'muted': boolean,
+ * 'isTargetMod': boolean
  */
 
 'use strict';
 var MatrixClientPeg = require("../../MatrixClientPeg");
 var dis = require("../../dispatcher");
+var Modal = require("../../Modal");
+var ComponentBroker = require('../../ComponentBroker');
+var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog");
 
 module.exports = {
     componentDidMount: function() {
         var self = this;
+        // listen for presence changes
         function updateUserState(event, user) {
             if (!self.props.member) { return; }
 
@@ -40,20 +52,145 @@ module.exports = {
         MatrixClientPeg.get().on("User.presence", updateUserState);
         this.userPresenceFn = updateUserState;
 
-        if (this.props.member) {
-            var usr = MatrixClientPeg.get().getUser(this.props.member.userId);
-            if (!usr) {
+        // listen for power level changes
+        function updatePowerLevel(event, member) {
+            if (!self.props.member) { return; }
+
+            if (member.roomId !== self.props.member.roomId) {
                 return;
             }
-            this.setState({
-                presence: usr.presence,
-                active: usr.lastActiveAgo
-            });
+            // only interested in changes to us or them
+            var myUserId = MatrixClientPeg.get().credentials.userId;
+            if ([myUserId, self.props.member.userId].indexOf(member.userId) === -1) {
+                return;
+            }
+            self.setState(self._calculateOpsPermissions());
+        }
+        MatrixClientPeg.get().on("RoomMember.powerLevel", updatePowerLevel);
+        this.updatePowerLevelFn = updatePowerLevel;
+
+        // work out the current state
+        if (this.props.member) {
+            var usr = MatrixClientPeg.get().getUser(this.props.member.userId) || {};
+            var memberState = this._calculateOpsPermissions();
+            memberState.presence = usr.presence || "offline";
+            memberState.active = usr.lastActiveAgo || -1;
+            this.setState(memberState);
         }
     },
 
     componentWillUnmount: function() {
         MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn);
+        MatrixClientPeg.get().removeListener(
+            "RoomMember.powerLevel", this.updatePowerLevelFn
+        );
+    },
+
+    onKick: function() {
+        var roomId = this.props.member.roomId;
+        var target = this.props.member.userId;
+        var self = this;
+        MatrixClientPeg.get().kick(roomId, target).done(function() {
+            // NO-OP; rely on the m.room.member event coming down else we could
+            // get out of sync if we force setState here!
+            console.log("Kick success");
+        }, function(err) {
+            Modal.createDialog(ErrorDialog, {
+                title: "Kick error",
+                description: err.message
+            });
+        });
+    },
+
+    onBan: function() {
+        var roomId = this.props.member.roomId;
+        var target = this.props.member.userId;
+        var self = this;
+        MatrixClientPeg.get().ban(roomId, target).done(function() {
+            // NO-OP; rely on the m.room.member event coming down else we could
+            // get out of sync if we force setState here!
+            console.log("Ban success");
+        }, function(err) {
+            Modal.createDialog(ErrorDialog, {
+                title: "Ban error",
+                description: err.message
+            });
+        });
+    },
+
+    onMuteToggle: function() {
+        var roomId = this.props.member.roomId;
+        var target = this.props.member.userId;
+        var self = this;
+        var room = MatrixClientPeg.get().getRoom(roomId);
+        if (!room) {
+            return;
+        }
+        var powerLevelEvent = room.currentState.getStateEvents(
+            "m.room.power_levels", ""
+        );
+        if (!powerLevelEvent) {
+            return;
+        }
+        var isMuted = this.state.muted;
+        var powerLevels = powerLevelEvent.getContent();
+        var levelToSend = (
+            (powerLevels.events ? powerLevels.events["m.room.message"] : null) ||
+            powerLevels.events_default
+        );
+        var level;
+        if (isMuted) { // unmute
+            level = levelToSend;
+        }
+        else { // mute
+            level = levelToSend - 1;
+        }
+
+        MatrixClientPeg.get().setPowerLevel(roomId, target, level, powerLevelEvent).done(
+        function() {
+            // NO-OP; rely on the m.room.member event coming down else we could
+            // get out of sync if we force setState here!
+            console.log("Mute toggle success");
+        }, function(err) {
+            Modal.createDialog(ErrorDialog, {
+                title: "Mute error",
+                description: err.message
+            });
+        });
+    },
+
+    onModToggle: function() {
+        var roomId = this.props.member.roomId;
+        var target = this.props.member.userId;
+        var room = MatrixClientPeg.get().getRoom(roomId);
+        if (!room) {
+            return;
+        }
+        var powerLevelEvent = room.currentState.getStateEvents(
+            "m.room.power_levels", ""
+        );
+        if (!powerLevelEvent) {
+            return;
+        }
+        var me = room.getMember(MatrixClientPeg.get().credentials.userId);
+        if (!me) {
+            return;
+        }
+        var defaultLevel = powerLevelEvent.getContent().users_default;
+        var modLevel = me.powerLevel - 1;
+        // toggle the level
+        var newLevel = this.state.isTargetMod ? defaultLevel : modLevel;
+        MatrixClientPeg.get().setPowerLevel(roomId, target, newLevel, powerLevelEvent).done(
+        function() {
+            // NO-OP; rely on the m.room.member event coming down else we could
+            // get out of sync if we force setState here!
+            console.log("Mod toggle success");
+        }, function(err) {
+            Modal.createDialog(ErrorDialog, {
+                title: "Mod error",
+                description: err.message
+            });
+        });
     },
 
     onChatClick: function() {
@@ -108,8 +245,77 @@ module.exports = {
     getInitialState: function() {
         return {
             presence: "offline",
-            active: -1
+            active: -1,
+            can: {
+                kick: false,
+                ban: false,
+                mute: false,
+                modifyLevel: false
+            },
+            muted: false,
+            isTargetMod: false
         }
+    },
+
+    _calculateOpsPermissions: function() {
+        var defaultPerms = {
+            can: {},
+            muted: false,
+            modifyLevel: false
+        };
+        var room = MatrixClientPeg.get().getRoom(this.props.member.roomId);
+        if (!room) {
+            return defaultPerms;
+        }
+        var powerLevels = room.currentState.getStateEvents(
+            "m.room.power_levels", ""
+        );
+        if (!powerLevels) {
+            return defaultPerms;
+        }
+        var me = room.getMember(MatrixClientPeg.get().credentials.userId);
+        var them = this.props.member;
+        return {
+            can: this._calculateCanPermissions(
+                me, them, powerLevels.getContent()
+            ),
+            muted: this._isMuted(them, powerLevels.getContent()),
+            isTargetMod: them.powerLevel > powerLevels.getContent().users_default
+        };
+    },
+
+    _calculateCanPermissions: function(me, them, powerLevels) {
+        var can = {
+            kick: false,
+            ban: false,
+            mute: false,
+            modifyLevel: false
+        };
+        var canAffectUser = them.powerLevel < me.powerLevel;
+        if (!canAffectUser) {
+            console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel);
+            return can;
+        }
+        var editPowerLevel = (
+            (powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) ||
+            powerLevels.state_default
+        );
+        can.kick = me.powerLevel >= powerLevels.kick;
+        can.ban = me.powerLevel >= powerLevels.ban;
+        can.mute = me.powerLevel >= editPowerLevel;
+        can.modifyLevel = me.powerLevel > them.powerLevel;
+        return can;
+    },
+
+    _isMuted: function(member, powerLevelContent) {
+        if (!powerLevelContent || !member) {
+            return false;
+        }
+        var levelToSend = (
+            (powerLevelContent.events ? powerLevelContent.events["m.room.message"] : null) ||
+            powerLevelContent.events_default
+        );
+        return member.powerLevel < levelToSend;
     }
 };