Implement channel ownership transfer.

This commit is contained in:
John Preston 2019-06-14 16:04:30 +02:00
parent a68a53d768
commit 8f3f072b50
5 changed files with 133 additions and 18 deletions

View File

@ -1048,6 +1048,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_channels_too_much_public_revoke_confirm_group" = "Are you sure you want to revoke the link {link}?\n\nThe group «{group}» will become private.";
"lng_channels_too_much_public_revoke_confirm_channel" = "Are you sure you want to revoke the link {link}?\n\nThe channel «{group}» will become private.";
"lng_channels_too_much_public_revoke" = "Revoke";
"lng_channels_too_much_public_other" = "Sorry, the target user has too many public groups or channels already. Please ask them to make one of their existing groups or channels private first.";
"lng_group_invite_bad_link" = "This invite link is broken or has expired.";
"lng_group_invite_join" = "Join";
@ -1661,7 +1662,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_transfer_set_password" = "Set password";
"lng_rights_transfer_about" = "This will transfer the full **owner rights** for {group} to {user}.";
"lng_rights_transfer_sure" = "Change owner";
"lng_rights_transfer_password" = "Please enter your password to complete the transfer.";
"lng_rights_transfer_password_title" = "Two-step verification";
"lng_rights_transfer_password_description" = "Please enter your password to complete the transfer.";
"lng_rights_transfer_done_group" = "{user} is now the owner of the group.";
"lng_rights_transfer_done_channel" = "{user} is now the owner of the channel.";

View File

@ -88,12 +88,12 @@ bool PasscodeBox::currentlyHave() const {
}
bool PasscodeBox::onlyCheckCurrent() const {
return _turningOff || _cloudFields.customCheckedCallback;
return _turningOff || _cloudFields.customCheckCallback;
}
void PasscodeBox::prepare() {
addButton([=] {
return _cloudFields.button.value_or(lang(_turningOff
return _cloudFields.customSubmitButton.value_or(lang(_turningOff
? lng_passcode_remove_button
: lng_settings_save));
}, [=] { save(); });
@ -101,7 +101,7 @@ void PasscodeBox::prepare() {
_about.setText(
st::passcodeTextStyle,
_cloudFields.description.value_or(lang(_cloudPwd
_cloudFields.customDescription.value_or(lang(_cloudPwd
? lng_cloud_password_about
: lng_passcode_about)));
_aboutHeight = _about.countHeight(st::boxWidth - st::boxPadding.left() * 1.5);
@ -109,7 +109,7 @@ void PasscodeBox::prepare() {
if (onlyCheck) {
_oldPasscode->show();
setTitle([=] {
return _cloudFields.title.value_or(lang(_cloudPwd
return _cloudFields.customTitle.value_or(lang(_cloudPwd
? lng_cloud_password_remove
: lng_passcode_remove));
});
@ -286,7 +286,7 @@ void PasscodeBox::setPasswordFail(const RPCError &error) {
closeReplacedBy();
_setRequest = 0;
const auto err = error.type();
const auto &err = error.type();
if (err == qstr("PASSWORD_HASH_INVALID")
|| err == qstr("SRP_PASSWORD_CHANGED")) {
if (_oldPasscode->isHidden()) {
@ -295,7 +295,7 @@ void PasscodeBox::setPasswordFail(const RPCError &error) {
} else {
badOldPasscode();
}
} else if (error.type() == qstr("SRP_ID_INVALID")) {
} else if (err == qstr("SRP_ID_INVALID")) {
handleSrpIdInvalid();
//} else if (err == qstr("NEW_PASSWORD_BAD")) {
//} else if (err == qstr("NEW_SALT_INVALID")) {
@ -504,7 +504,7 @@ void PasscodeBox::submitOnlyCheckCloudPassword(const QString &oldPassword) {
sendOnlyCheckCloudPassword(oldPassword);
};
if (_cloudFields.turningOff && _cloudFields.notEmptyPassport) {
Assert(!_cloudFields.customCheckedCallback);
Assert(!_cloudFields.customCheckCallback);
const auto box = std::make_shared<QPointer<BoxContent>>();
const auto confirmed = [=] {
@ -524,8 +524,8 @@ void PasscodeBox::submitOnlyCheckCloudPassword(const QString &oldPassword) {
void PasscodeBox::sendOnlyCheckCloudPassword(const QString &oldPassword) {
checkPassword(oldPassword, [=](const Core::CloudPasswordResult &check) {
if (const auto onstack = _cloudFields.customCheckedCallback) {
onstack(check.result);
if (const auto onstack = _cloudFields.customCheckCallback) {
onstack(check);
} else {
Assert(_cloudFields.turningOff);
sendClearCloudPassword(check);
@ -588,6 +588,18 @@ void PasscodeBox::serverError() {
closeBox();
}
bool PasscodeBox::handleCustomCheckError(const RPCError &error) {
const auto &type = error.type();
if (MTP::isFloodError(error)
|| type == qstr("PASSWORD_HASH_INVALID")
|| type == qstr("SRP_PASSWORD_CHANGED")
|| type == qstr("SRP_ID_INVALID")) {
setPasswordFail(error);
return true;
}
return false;
}
void PasscodeBox::sendClearCloudPassword(
const Core::CloudPasswordResult &check) {
const auto newPasswordData = QByteArray();

View File

@ -37,10 +37,10 @@ public:
bool turningOff = false;
// Check cloud password for some action.
Fn<void(const MTPInputCheckPasswordSRP &)> customCheckedCallback;
std::optional<QString> title;
std::optional<QString> description;
std::optional<QString> button;
Fn<void(const Core::CloudPasswordResult &)> customCheckCallback;
std::optional<QString> customTitle;
std::optional<QString> customDescription;
std::optional<QString> customSubmitButton;
};
PasscodeBox(QWidget*, const CloudFields &fields);
@ -48,6 +48,8 @@ public:
rpl::producer<> passwordReloadNeeded() const;
rpl::producer<> clearUnconfirmedPassword() const;
bool handleCustomCheckError(const RPCError &error);
protected:
void prepare() override;
void setInnerFocus() override;

View File

@ -21,6 +21,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_privacy_security.h"
#include "boxes/calendar_box.h"
#include "boxes/generic_box.h"
#include "boxes/confirm_box.h"
#include "boxes/passcode_box.h"
#include "boxes/peers/edit_peer_permissions_box.h"
#include "boxes/peers/edit_peer_info_box.h"
#include "data/data_peer_values.h"
@ -46,7 +48,6 @@ enum class PasswordErrorType {
};
void SetCloudPassword(not_null<GenericBox*> box, not_null<UserData*> user) {
user->session().api().reloadPasswordState();
user->session().api().passwordState(
) | rpl::start_with_next([=] {
using namespace Settings;
@ -414,10 +415,12 @@ void EditAdminBox::transferOwnership() {
if (_checkTransferRequestId) {
return;
}
const auto channel = peer()->isChannel()
? peer()->asChannel()->inputChannel
: MTP_inputChannelEmpty();
const auto api = &peer()->session().api();
api->reloadPasswordState();
_checkTransferRequestId = api->request(MTPchannels_EditCreator(
channel,
MTP_inputUserEmpty(),
@ -425,7 +428,7 @@ void EditAdminBox::transferOwnership() {
)).fail([=](const RPCError &error) {
_checkTransferRequestId = 0;
if (!handleTransferPasswordError(error)) {
requestTransferPassword();
transferOwnershipChecked();
}
}).send();
}
@ -449,7 +452,90 @@ bool EditAdminBox::handleTransferPasswordError(const RPCError &error) {
return true;
}
void EditAdminBox::requestTransferPassword() {
void EditAdminBox::transferOwnershipChecked() {
if (const auto chat = peer()->asChatNotMigrated()) {
peer()->session().api().migrateChat(chat, crl::guard(this, [=](
not_null<ChannelData*> channel) {
requestTransferPassword(channel);
}));
} else if (const auto channel = peer()->asChannelOrMigrated()) {
requestTransferPassword(channel);
} else {
Unexpected("Peer in SaveAdminCallback.");
}
}
void EditAdminBox::requestTransferPassword(not_null<ChannelData*> channel) {
peer()->session().api().passwordState(
) | rpl::take(
1
) | rpl::start_with_next([=](const Core::CloudPasswordState &state) {
const auto box = std::make_shared<QPointer<PasscodeBox>>();
auto fields = PasscodeBox::CloudFields::From(state);
fields.customTitle = lang(lng_rights_transfer_password_title);
fields.customDescription
= lang(lng_rights_transfer_password_description);
fields.customSubmitButton = lang(lng_passcode_submit);
fields.customCheckCallback = crl::guard(this, [=](
const Core::CloudPasswordResult &result) {
sendTransferRequestFrom(*box, channel, result);
});
*box = getDelegate()->show(Box<PasscodeBox>(fields));
}, lifetime());
}
void EditAdminBox::sendTransferRequestFrom(
QPointer<PasscodeBox> box,
not_null<ChannelData*> channel,
const Core::CloudPasswordResult &result) {
const auto weak = make_weak(this);
channel->session().api().request(MTPchannels_EditCreator(
channel->inputChannel,
user()->inputUser,
result.result
)).done([=](const MTPUpdates &result) {
channel->session().api().applyUpdates(result);
if (weak) {
closeBox();
}
if (box) {
box->closeBox();
}
}).fail(crl::guard(this, [=](const RPCError &error) {
if (box && box->handleCustomCheckError(error)) {
return;
}
const auto &type = error.type();
const auto problem = [&] {
if (type == qstr("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) {
return lang(lng_channels_too_much_public_other);
} else if (type == qstr("ADMINS_TOO_MUCH")) {
return lang(channel->isBroadcast()
? lng_error_admin_limit_channel
: lng_error_admin_limit);
} else if (type == qstr("CHANNEL_INVALID")) {
return lang(channel->isBroadcast()
? lng_channel_not_accessible
: lng_group_not_accessible);
}
return Lang::Hard::ServerError();
}();
const auto recoverable = [&] {
return (type == qstr("PASSWORD_MISSING"))
|| (type == qstr("PASSWORD_TOO_FRESH_XXX"))
|| (type == qstr("SESSION_TOO_FRESH_XXX"));
}();
const auto weak = make_weak(this);
getDelegate()->show(Box<InformBox>(problem));
if (box) {
box->closeBox();
}
if (weak && !recoverable) {
closeBox();
}
})).send();
}

View File

@ -20,7 +20,12 @@ template <typename Widget>
class SlideWrap;
} // namespace Ui
namespace Core {
struct CloudPasswordResult;
} // namespace Core
class CalendarBox;
class PasscodeBox;
class EditParticipantBox : public BoxContent {
public:
@ -70,6 +75,9 @@ public:
_saveCallback = std::move(callback);
}
~EditAdminBox() {
}
protected:
void prepare() override;
@ -80,8 +88,13 @@ private:
static MTPChatAdminRights Defaults(not_null<PeerData*> peer);
void transferOwnership();
void transferOwnershipChecked();
bool handleTransferPasswordError(const RPCError &error);
void requestTransferPassword();
void requestTransferPassword(not_null<ChannelData*> channel);
void sendTransferRequestFrom(
QPointer<PasscodeBox> box,
not_null<ChannelData*> channel,
const Core::CloudPasswordResult &result);
bool canSave() const {
return !!_saveCallback;
}