network, citra_qt: Give moderation permission to community mods
Based on the `roles` payload in the JWT, the rooms will now give mod permission to Citra Community Moderators. To notify the client of its permissions, a new response, IdJoinSuccessAsMod is added, and there's now a new RoomMember::State called Moderator.
This commit is contained in:
parent
94be4050bc
commit
9d062d63da
15 changed files with 73 additions and 27 deletions
|
@ -81,6 +81,9 @@ static void OnStateChanged(const Network::RoomMember::State& state) {
|
||||||
case Network::RoomMember::State::Joined:
|
case Network::RoomMember::State::Joined:
|
||||||
LOG_DEBUG(Network, "Successfully joined to the room");
|
LOG_DEBUG(Network, "Successfully joined to the room");
|
||||||
break;
|
break;
|
||||||
|
case Network::RoomMember::State::Moderator:
|
||||||
|
LOG_DEBUG(Network, "Successfully joined the room as a moderator");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -306,7 +306,9 @@ void ChatRoom::OnStatusMessageReceive(const Network::StatusMessageEntry& status_
|
||||||
|
|
||||||
void ChatRoom::OnSendChat() {
|
void ChatRoom::OnSendChat() {
|
||||||
if (auto room = Network::GetRoomMember().lock()) {
|
if (auto room = Network::GetRoomMember().lock()) {
|
||||||
if (room->GetState() != Network::RoomMember::State::Joined) {
|
if (room->GetState() != Network::RoomMember::State::Joined &&
|
||||||
|
room->GetState() != Network::RoomMember::State::Moderator) {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto message = ui->chat_message->text().toStdString();
|
auto message = ui->chat_message->text().toStdString();
|
||||||
|
|
|
@ -72,9 +72,12 @@ void ClientRoomWindow::OnRoomUpdate(const Network::RoomInformation& info) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientRoomWindow::OnStateChange(const Network::RoomMember::State& state) {
|
void ClientRoomWindow::OnStateChange(const Network::RoomMember::State& state) {
|
||||||
if (state == Network::RoomMember::State::Joined) {
|
if (state == Network::RoomMember::State::Joined ||
|
||||||
|
state == Network::RoomMember::State::Moderator) {
|
||||||
|
|
||||||
ui->chat->Clear();
|
ui->chat->Clear();
|
||||||
ui->chat->AppendStatusMessage(tr("Connected"));
|
ui->chat->AppendStatusMessage(tr("Connected"));
|
||||||
|
SetModPerms(state == Network::RoomMember::State::Moderator);
|
||||||
}
|
}
|
||||||
UpdateView();
|
UpdateView();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ public:
|
||||||
~ClientRoomWindow();
|
~ClientRoomWindow();
|
||||||
|
|
||||||
void RetranslateUi();
|
void RetranslateUi();
|
||||||
void SetModPerms(bool is_mod);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void OnRoomUpdate(const Network::RoomInformation&);
|
void OnRoomUpdate(const Network::RoomInformation&);
|
||||||
|
@ -32,6 +31,7 @@ signals:
|
||||||
private:
|
private:
|
||||||
void Disconnect();
|
void Disconnect();
|
||||||
void UpdateView();
|
void UpdateView();
|
||||||
|
void SetModPerms(bool is_mod);
|
||||||
|
|
||||||
QStandardItemModel* player_list;
|
QStandardItemModel* player_list;
|
||||||
std::unique_ptr<Ui::ClientRoom> ui;
|
std::unique_ptr<Ui::ClientRoom> ui;
|
||||||
|
|
|
@ -63,7 +63,7 @@ void DirectConnectWindow::Connect() {
|
||||||
// Prevent the user from trying to join a room while they are already joining.
|
// Prevent the user from trying to join a room while they are already joining.
|
||||||
if (member->GetState() == Network::RoomMember::State::Joining) {
|
if (member->GetState() == Network::RoomMember::State::Joining) {
|
||||||
return;
|
return;
|
||||||
} else if (member->GetState() == Network::RoomMember::State::Joined) {
|
} else if (member->IsConnected()) {
|
||||||
// And ask if they want to leave the room if they are already in one.
|
// And ask if they want to leave the room if they are already in one.
|
||||||
if (!NetworkMessage::WarnDisconnect()) {
|
if (!NetworkMessage::WarnDisconnect()) {
|
||||||
return;
|
return;
|
||||||
|
@ -122,7 +122,9 @@ void DirectConnectWindow::OnConnection() {
|
||||||
EndConnecting();
|
EndConnecting();
|
||||||
|
|
||||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
if (room_member->GetState() == Network::RoomMember::State::Joined) {
|
if (room_member->GetState() == Network::RoomMember::State::Joined ||
|
||||||
|
room_member->GetState() == Network::RoomMember::State::Moderator) {
|
||||||
|
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,7 +113,7 @@ void HostRoomWindow::Host() {
|
||||||
if (auto member = Network::GetRoomMember().lock()) {
|
if (auto member = Network::GetRoomMember().lock()) {
|
||||||
if (member->GetState() == Network::RoomMember::State::Joining) {
|
if (member->GetState() == Network::RoomMember::State::Joining) {
|
||||||
return;
|
return;
|
||||||
} else if (member->GetState() == Network::RoomMember::State::Joined) {
|
} else if (member->IsConnected()) {
|
||||||
auto parent = static_cast<MultiplayerState*>(parentWidget());
|
auto parent = static_cast<MultiplayerState*>(parentWidget());
|
||||||
if (!parent->OnCloseRoom()) {
|
if (!parent->OnCloseRoom()) {
|
||||||
close();
|
close();
|
||||||
|
|
|
@ -109,7 +109,7 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
|
||||||
// Prevent the user from trying to join a room while they are already joining.
|
// Prevent the user from trying to join a room while they are already joining.
|
||||||
if (member->GetState() == Network::RoomMember::State::Joining) {
|
if (member->GetState() == Network::RoomMember::State::Joining) {
|
||||||
return;
|
return;
|
||||||
} else if (member->GetState() == Network::RoomMember::State::Joined) {
|
} else if (member->IsConnected()) {
|
||||||
// And ask if they want to leave the room if they are already in one.
|
// And ask if they want to leave the room if they are already in one.
|
||||||
if (!NetworkMessage::WarnDisconnect()) {
|
if (!NetworkMessage::WarnDisconnect()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -89,7 +89,9 @@ void MultiplayerState::retranslateUi() {
|
||||||
|
|
||||||
if (current_state == Network::RoomMember::State::Uninitialized) {
|
if (current_state == Network::RoomMember::State::Uninitialized) {
|
||||||
status_text->setText(tr("Not Connected. Click here to find a room!"));
|
status_text->setText(tr("Not Connected. Click here to find a room!"));
|
||||||
} else if (current_state == Network::RoomMember::State::Joined) {
|
} else if (current_state == Network::RoomMember::State::Joined ||
|
||||||
|
current_state == Network::RoomMember::State::Moderator) {
|
||||||
|
|
||||||
status_text->setText(tr("Connected"));
|
status_text->setText(tr("Connected"));
|
||||||
} else {
|
} else {
|
||||||
status_text->setText(tr("Not Connected"));
|
status_text->setText(tr("Not Connected"));
|
||||||
|
@ -107,7 +109,9 @@ void MultiplayerState::retranslateUi() {
|
||||||
|
|
||||||
void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) {
|
void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) {
|
||||||
LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state));
|
LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state));
|
||||||
if (state == Network::RoomMember::State::Joined) {
|
if (state == Network::RoomMember::State::Joined ||
|
||||||
|
state == Network::RoomMember::State::Moderator) {
|
||||||
|
|
||||||
OnOpenNetworkRoom();
|
OnOpenNetworkRoom();
|
||||||
status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16));
|
status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16));
|
||||||
status_text->setText(tr("Connected"));
|
status_text->setText(tr("Connected"));
|
||||||
|
@ -183,7 +187,9 @@ void MultiplayerState::OnAnnounceFailed(const Common::WebResult& result) {
|
||||||
void MultiplayerState::UpdateThemedIcons() {
|
void MultiplayerState::UpdateThemedIcons() {
|
||||||
if (show_notification) {
|
if (show_notification) {
|
||||||
status_icon->setPixmap(QIcon::fromTheme("connected_notification").pixmap(16));
|
status_icon->setPixmap(QIcon::fromTheme("connected_notification").pixmap(16));
|
||||||
} else if (current_state == Network::RoomMember::State::Joined) {
|
} else if (current_state == Network::RoomMember::State::Joined ||
|
||||||
|
current_state == Network::RoomMember::State::Moderator) {
|
||||||
|
|
||||||
status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16));
|
status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16));
|
||||||
} else {
|
} else {
|
||||||
status_icon->setPixmap(QIcon::fromTheme("disconnected").pixmap(16));
|
status_icon->setPixmap(QIcon::fromTheme("disconnected").pixmap(16));
|
||||||
|
@ -258,12 +264,6 @@ void MultiplayerState::OnOpenNetworkRoom() {
|
||||||
connect(client_room, &ClientRoomWindow::ShowNotification, this,
|
connect(client_room, &ClientRoomWindow::ShowNotification, this,
|
||||||
&MultiplayerState::ShowNotification);
|
&MultiplayerState::ShowNotification);
|
||||||
}
|
}
|
||||||
const std::string host_username = member->GetRoomInformation().host_username;
|
|
||||||
if (host_username.empty()) {
|
|
||||||
client_room->SetModPerms(false);
|
|
||||||
} else {
|
|
||||||
client_room->SetModPerms(member->GetUsername() == host_username);
|
|
||||||
}
|
|
||||||
BringWidgetToFront(client_room);
|
BringWidgetToFront(client_room);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,7 +140,9 @@ std::list<Network::WifiPacket> GetReceivedBeacons(const MacAddress& sender) {
|
||||||
/// Sends a WifiPacket to the room we're currently connected to.
|
/// Sends a WifiPacket to the room we're currently connected to.
|
||||||
void SendPacket(Network::WifiPacket& packet) {
|
void SendPacket(Network::WifiPacket& packet) {
|
||||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
if (room_member->GetState() == Network::RoomMember::State::Joined) {
|
if (room_member->GetState() == Network::RoomMember::State::Joined ||
|
||||||
|
room_member->GetState() == Network::RoomMember::State::Moderator) {
|
||||||
|
|
||||||
packet.transmitter_address = room_member->GetMacAddress();
|
packet.transmitter_address = room_member->GetMacAddress();
|
||||||
room_member->SendWifiPacket(packet);
|
room_member->SendWifiPacket(packet);
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,12 @@ public:
|
||||||
*/
|
*/
|
||||||
void SendJoinSuccess(ENetPeer* client, MacAddress mac_address);
|
void SendJoinSuccess(ENetPeer* client, MacAddress mac_address);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the member that its connection attempt was successful,
|
||||||
|
* and it is now part of the room, and it has been granted mod permissions.
|
||||||
|
*/
|
||||||
|
void SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a IdHostKicked message telling the client that they have been kicked.
|
* Sends a IdHostKicked message telling the client that they have been kicked.
|
||||||
*/
|
*/
|
||||||
|
@ -401,7 +407,11 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
|
||||||
|
|
||||||
// Notify everyone that the room information has changed.
|
// Notify everyone that the room information has changed.
|
||||||
BroadcastRoomInformation();
|
BroadcastRoomInformation();
|
||||||
|
if (HasModPermission(event->peer)) {
|
||||||
|
SendJoinSuccessAsMod(event->peer, preferred_mac);
|
||||||
|
} else {
|
||||||
SendJoinSuccess(event->peer, preferred_mac);
|
SendJoinSuccess(event->peer, preferred_mac);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::RoomImpl::HandleModKickPacket(const ENetEvent* event) {
|
void Room::RoomImpl::HandleModKickPacket(const ENetEvent* event) {
|
||||||
|
@ -588,10 +598,11 @@ bool Room::RoomImpl::HasModPermission(const ENetPeer* client) const {
|
||||||
if (sending_member == members.end()) {
|
if (sending_member == members.end()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (sending_member->user_data.username != room_information.host_username) {
|
if (sending_member->user_data.moderator) // Community moderator
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
|
if (sending_member->user_data.username == room_information.host_username) // Room host
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Room::RoomImpl::SendNameCollision(ENetPeer* client) {
|
void Room::RoomImpl::SendNameCollision(ENetPeer* client) {
|
||||||
|
@ -665,6 +676,16 @@ void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) {
|
||||||
enet_host_flush(server);
|
enet_host_flush(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Room::RoomImpl::SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address) {
|
||||||
|
Packet packet;
|
||||||
|
packet << static_cast<u8>(IdJoinSuccessAsMod);
|
||||||
|
packet << mac_address;
|
||||||
|
ENetPacket* enet_packet =
|
||||||
|
enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
|
||||||
|
enet_peer_send(client, 0, enet_packet);
|
||||||
|
enet_host_flush(server);
|
||||||
|
}
|
||||||
|
|
||||||
void Room::RoomImpl::SendUserKicked(ENetPeer* client) {
|
void Room::RoomImpl::SendUserKicked(ENetPeer* client) {
|
||||||
Packet packet;
|
Packet packet;
|
||||||
packet << static_cast<u8>(IdHostKicked);
|
packet << static_cast<u8>(IdHostKicked);
|
||||||
|
|
|
@ -74,6 +74,7 @@ enum RoomMessageTypes : u8 {
|
||||||
IdModBanListResponse,
|
IdModBanListResponse,
|
||||||
IdModPermissionDenied,
|
IdModPermissionDenied,
|
||||||
IdModNoSuchUser,
|
IdModNoSuchUser,
|
||||||
|
IdJoinSuccessAsMod,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Types of system status messages
|
/// Types of system status messages
|
||||||
|
|
|
@ -151,7 +151,7 @@ void RoomMember::RoomMemberImpl::SetError(const Error new_error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RoomMember::RoomMemberImpl::IsConnected() const {
|
bool RoomMember::RoomMemberImpl::IsConnected() const {
|
||||||
return state == State::Joining || state == State::Joined;
|
return state == State::Joining || state == State::Joined || state == State::Moderator;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RoomMember::RoomMemberImpl::MemberLoop() {
|
void RoomMember::RoomMemberImpl::MemberLoop() {
|
||||||
|
@ -176,12 +176,17 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
|
||||||
HandleRoomInformationPacket(&event);
|
HandleRoomInformationPacket(&event);
|
||||||
break;
|
break;
|
||||||
case IdJoinSuccess:
|
case IdJoinSuccess:
|
||||||
|
case IdJoinSuccessAsMod:
|
||||||
// The join request was successful, we are now in the room.
|
// The join request was successful, we are now in the room.
|
||||||
// If we joined successfully, there must be at least one client in the room: us.
|
// If we joined successfully, there must be at least one client in the room: us.
|
||||||
ASSERT_MSG(member_information.size() > 0,
|
ASSERT_MSG(member_information.size() > 0,
|
||||||
"We have not yet received member information.");
|
"We have not yet received member information.");
|
||||||
HandleJoinPacket(&event); // Get the MAC Address for the client
|
HandleJoinPacket(&event); // Get the MAC Address for the client
|
||||||
|
if (event.packet->data[0] == IdJoinSuccessAsMod) {
|
||||||
|
SetState(State::Moderator);
|
||||||
|
} else {
|
||||||
SetState(State::Joined);
|
SetState(State::Joined);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case IdModBanListResponse:
|
case IdModBanListResponse:
|
||||||
HandleModBanListResponsePacket(&event);
|
HandleModBanListResponsePacket(&event);
|
||||||
|
@ -232,7 +237,7 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
|
||||||
enet_packet_destroy(event.packet);
|
enet_packet_destroy(event.packet);
|
||||||
break;
|
break;
|
||||||
case ENET_EVENT_TYPE_DISCONNECT:
|
case ENET_EVENT_TYPE_DISCONNECT:
|
||||||
if (state == State::Joined) {
|
if (state == State::Joined || state == State::Moderator) {
|
||||||
SetState(State::Idle);
|
SetState(State::Idle);
|
||||||
SetError(Error::LostConnection);
|
SetError(Error::LostConnection);
|
||||||
}
|
}
|
||||||
|
@ -331,7 +336,6 @@ void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) {
|
||||||
|
|
||||||
// Parse the MAC Address from the packet
|
// Parse the MAC Address from the packet
|
||||||
packet >> mac_address;
|
packet >> mac_address;
|
||||||
SetState(State::Joined);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) {
|
void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) {
|
||||||
|
|
|
@ -60,6 +60,7 @@ public:
|
||||||
Idle, ///< Default state (i.e. not connected)
|
Idle, ///< Default state (i.e. not connected)
|
||||||
Joining, ///< The client is attempting to join a room.
|
Joining, ///< The client is attempting to join a room.
|
||||||
Joined, ///< The client is connected to the room and is ready to send/receive packets.
|
Joined, ///< The client is connected to the room and is ready to send/receive packets.
|
||||||
|
Moderator, ///< The client is connnected to the room and is granted mod permissions.
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Error : u8 {
|
enum class Error : u8 {
|
||||||
|
@ -270,6 +271,8 @@ static const char* GetStateStr(const RoomMember::State& s) {
|
||||||
return "Joining";
|
return "Joining";
|
||||||
case RoomMember::State::Joined:
|
case RoomMember::State::Joined:
|
||||||
return "Joined";
|
return "Joined";
|
||||||
|
case RoomMember::State::Moderator:
|
||||||
|
return "Moderator";
|
||||||
}
|
}
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ struct UserData {
|
||||||
std::string username;
|
std::string username;
|
||||||
std::string display_name;
|
std::string display_name;
|
||||||
std::string avatar_url;
|
std::string avatar_url;
|
||||||
|
bool moderator = false; ///< Whether the user is a Citra Moderator.
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -50,6 +50,10 @@ Network::VerifyUser::UserData VerifyUserJWT::LoadUserData(const std::string& ver
|
||||||
if (decoded.payload().has_claim("avatarUrl")) {
|
if (decoded.payload().has_claim("avatarUrl")) {
|
||||||
user_data.avatar_url = decoded.payload().get_claim_value<std::string>("avatarUrl");
|
user_data.avatar_url = decoded.payload().get_claim_value<std::string>("avatarUrl");
|
||||||
}
|
}
|
||||||
|
if (decoded.payload().has_claim("roles")) {
|
||||||
|
auto roles = decoded.payload().get_claim_value<std::vector<std::string>>("roles");
|
||||||
|
user_data.moderator = std::find(roles.begin(), roles.end(), "moderator") != roles.end();
|
||||||
|
}
|
||||||
return user_data;
|
return user_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue