plugin account: user can change his password or delete his XMPP account
This commit is contained in:
parent
88baf0791b
commit
5b2e60e22e
1 changed files with 136 additions and 10 deletions
|
|
@ -17,7 +17,7 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from sat.core.i18n import _
|
||||
from sat.core.i18n import _, D_
|
||||
from logging import debug, info, warning, error
|
||||
from sat.core import exceptions
|
||||
from twisted.internet import reactor, defer, protocol
|
||||
|
|
@ -26,6 +26,7 @@ from twisted.python.procutils import which
|
|||
from twisted.python.failure import Failure
|
||||
from email.mime.text import MIMEText
|
||||
from twisted.mail.smtp import sendmail
|
||||
from sat.tools import xml_tools
|
||||
|
||||
PLUGIN_INFO = {
|
||||
"name": "Account Plugin",
|
||||
|
|
@ -56,16 +57,26 @@ default_conf = {"email_from": "NOREPLY@example.net",
|
|||
}
|
||||
|
||||
|
||||
class PasswordsMatchingError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ProsodyRegisterProtocol(protocol.ProcessProtocol):
|
||||
""" Try to register an account with prosody """
|
||||
|
||||
def __init__(self, password, deferred=None):
|
||||
def __init__(self, password=None, deferred=None):
|
||||
"""
|
||||
@param password: new user password
|
||||
@param deferred
|
||||
"""
|
||||
self.password = password
|
||||
self.deferred = deferred
|
||||
self.data = ''
|
||||
|
||||
def connectionMade(self):
|
||||
self.transport.write("%s\n%s" % ((self.password.encode('utf-8'), ) * 2))
|
||||
if self.password is None:
|
||||
return
|
||||
self.transport.write("%s\n%s" % ((self.password.encode('utf-8'),) * 2))
|
||||
self.transport.closeStdin()
|
||||
|
||||
def outReceived(self, data):
|
||||
|
|
@ -76,12 +87,27 @@ class ProsodyRegisterProtocol(protocol.ProcessProtocol):
|
|||
|
||||
def processEnded(self, reason):
|
||||
if (reason.value.exitCode == 0):
|
||||
info(_('Prosody registration success'))
|
||||
info(_('Prosody command succeed'))
|
||||
self.deferred.callback(None)
|
||||
else:
|
||||
error(_(u"Can't register Prosody account (error code: %(code)d): %(message)s") % {'code': reason.value.exitCode, 'message': self.data})
|
||||
error(_(u"Can't complete Prosody command (error code: %(code)d): %(message)s") % {'code': reason.value.exitCode, 'message': self.data})
|
||||
self.deferred.errback(Failure(exceptions.InternalError))
|
||||
|
||||
@classmethod
|
||||
def prosodyctl(cls, plugin, command, password=None, profile=None):
|
||||
"""Create a new ProsodyRegisterProtocol and execute the given prosodyctl command.
|
||||
@param plugin: instance of MiscAccount
|
||||
@param command: the command to execute: "adduser", "passwd" or "deluser"
|
||||
@param password: the user new password (leave to None for "deluser" command)
|
||||
@param profile: the user profile
|
||||
@return a Deferred instance
|
||||
"""
|
||||
d = defer.Deferred()
|
||||
prosody_reg = ProsodyRegisterProtocol(password, d)
|
||||
prosody_exe = join(plugin._prosody_path, plugin.getConfig('prosodyctl'))
|
||||
reactor.spawnProcess(prosody_reg, prosody_exe, [prosody_exe, command, "%s@%s" % (profile, plugin.getConfig('new_account_domain'))], path=plugin._prosody_path)
|
||||
return d
|
||||
|
||||
|
||||
class MiscAccount(object):
|
||||
"""Account plugin: create a SàT + Prosody account, used by Libervia"""
|
||||
|
|
@ -93,6 +119,7 @@ class MiscAccount(object):
|
|||
self.host = host
|
||||
host.bridge.addMethod("registerSatAccount", ".plugin", in_sign='sss', out_sign='', method=self._registerAccount, async=True)
|
||||
host.bridge.addMethod("getNewAccountDomain", ".plugin", in_sign='', out_sign='s', method=self._getNewAccountDomain, async=False)
|
||||
host.bridge.addMethod("getAccountDialogUI", ".plugin", in_sign='s', out_sign='s', method=self._getAccountDialogUI, async=False)
|
||||
self._prosody_path = self.getConfig('prosody_path')
|
||||
if self._prosody_path is None:
|
||||
paths = which(self.getConfig('prosodyctl'))
|
||||
|
|
@ -102,6 +129,9 @@ class MiscAccount(object):
|
|||
self._prosody_path = dirname(paths[0])
|
||||
info(_('Prosody path found: %s') % (self._prosody_path, ))
|
||||
|
||||
self.__account_cb_id = host.registerCallback(self._accountDialogCb, with_data=True)
|
||||
self.__delete_account_id = host.registerCallback(self.__deleteAccountCb, with_data=True)
|
||||
|
||||
def getConfig(self, name):
|
||||
return self.host.memory.getConfig(CONFIG_SECTION, name) or default_conf[name]
|
||||
|
||||
|
|
@ -138,11 +168,7 @@ class MiscAccount(object):
|
|||
|
||||
#XXX: we use "prosodyctl adduser" because "register" doesn't check conflict
|
||||
# and just change the password if the account already exists
|
||||
d = defer.Deferred()
|
||||
prosody_reg = ProsodyRegisterProtocol(password, d)
|
||||
prosody_exe = join(self._prosody_path, self.getConfig('prosodyctl'))
|
||||
reactor.spawnProcess(prosody_reg, prosody_exe, [prosody_exe, 'adduser', "%s@%s" % (profile, self.getConfig('new_account_domain'))], path=self._prosody_path)
|
||||
|
||||
d = ProsodyRegisterProtocol.prosodyctl(self, 'adduser', password, profile)
|
||||
d.addCallback(self._sendEmails, profile, email, password)
|
||||
d.addCallback(lambda ignore: None)
|
||||
return d
|
||||
|
|
@ -204,3 +230,103 @@ SàT team""" % {'login': login, 'password': password, 'jid': "%s@%s" % (login, s
|
|||
"""@return: the domain that will be set to new account"""
|
||||
return self.getConfig('new_account_domain')
|
||||
|
||||
def _getAccountDialogUI(self, profile):
|
||||
"""Get the main dialog to manage your account
|
||||
@param menu_data
|
||||
@param profile: %(doc_profile)s
|
||||
@return XML of the dialog
|
||||
"""
|
||||
form_ui = xml_tools.XMLUI("form", "tabs", title=D_("Manage your XMPP account"), submit_id=self.__account_cb_id)
|
||||
tab_container = form_ui.current_container
|
||||
tab_container.addTab("update", D_("Change your password"), container=xml_tools.PairsContainer)
|
||||
form_ui.addLabel(D_("Current password"))
|
||||
form_ui.addPassword("current_passwd", value="")
|
||||
form_ui.addLabel(D_("New password"))
|
||||
form_ui.addPassword("new_passwd1", value="")
|
||||
form_ui.addLabel(D_("New password (again)"))
|
||||
form_ui.addPassword("new_passwd2", value="")
|
||||
tab_container.addTab("delete", D_("Delete your account"), container=xml_tools.PairsContainer)
|
||||
form_ui.addLabel(D_("Current password"))
|
||||
form_ui.addPassword("delete_passwd", value="")
|
||||
form_ui.addLabel(D_("Delete your account"))
|
||||
form_ui.addBool("delete_checkbox", "false")
|
||||
return form_ui.toXml()
|
||||
|
||||
def _accountDialogCb(self, data, profile):
|
||||
"""Called when the user submits the main account dialog
|
||||
@param data
|
||||
@param profile
|
||||
"""
|
||||
password = self.host.memory.getParamA("Password", "Connection", profile_key=profile)
|
||||
|
||||
def error_ui():
|
||||
error_ui = xml_tools.XMLUI("popup", title="Error")
|
||||
error_ui.addText(D_("Passwords don't match!"))
|
||||
return defer.succeed({'xmlui': error_ui.toXml()})
|
||||
|
||||
# check for account deletion
|
||||
delete_passwd = data[xml_tools.SAT_FORM_PREFIX + 'delete_passwd']
|
||||
delete_checkbox = data[xml_tools.SAT_FORM_PREFIX + 'delete_checkbox']
|
||||
if delete_checkbox == 'true':
|
||||
if password == delete_passwd:
|
||||
return self.__deleteAccount(profile)
|
||||
return error_ui()
|
||||
|
||||
# check for password modification
|
||||
current_passwd = data[xml_tools.SAT_FORM_PREFIX + 'current_passwd']
|
||||
new_passwd1 = data[xml_tools.SAT_FORM_PREFIX + 'new_passwd1']
|
||||
new_passwd2 = data[xml_tools.SAT_FORM_PREFIX + 'new_passwd2']
|
||||
if new_passwd1 or new_passwd2:
|
||||
if password == current_passwd and new_passwd1 == new_passwd2:
|
||||
return self.__changePassword(new_passwd1, profile=profile)
|
||||
return error_ui()
|
||||
|
||||
return defer.succeed({})
|
||||
|
||||
def __changePassword(self, password, profile):
|
||||
"""Actually change the user XMPP account and SàT profile password
|
||||
@param password: new password
|
||||
@profile
|
||||
"""
|
||||
def passwordChanged(result):
|
||||
self.host.memory.setParam("Password", password, "Connection", profile_key=profile)
|
||||
confirm_ui = xml_tools.XMLUI("popup", title="Confirmation")
|
||||
confirm_ui.addText(D_("Your password has been changed."))
|
||||
return defer.succeed({'xmlui': confirm_ui.toXml()})
|
||||
|
||||
def errback(failure):
|
||||
error_ui = xml_tools.XMLUI("popup", title="Error")
|
||||
error_ui.addText(D_("Your password could not be changed: %s") % failure.getErrorMessage())
|
||||
return defer.succeed({'xmlui': error_ui.toXml()})
|
||||
|
||||
d = ProsodyRegisterProtocol.prosodyctl(self, 'passwd', password, profile=profile)
|
||||
d.addCallbacks(passwordChanged, errback)
|
||||
return d
|
||||
|
||||
def __deleteAccount(self, profile):
|
||||
"""Ask for a confirmation before deleting the XMPP account and SàT profile
|
||||
@param profile
|
||||
"""
|
||||
form_ui = xml_tools.XMLUI("form", title=D_("Delete your account ?"), submit_id=self.__delete_account_id)
|
||||
form_ui.addText(D_("If you confirm this dialog, you will be disconnected and then your XMPP account AND your SàT profile will both be DELETED."))
|
||||
form_ui.addText(D_("There is no other confirmation dialog, this is the very last one! Are you sure?"))
|
||||
return {'xmlui': form_ui.toXml()}
|
||||
|
||||
def __deleteAccountCb(self, data, profile):
|
||||
"""Actually delete the XMPP account and SàT profile
|
||||
@param data
|
||||
@param profile
|
||||
"""
|
||||
def userDeleted(result):
|
||||
self.host.disconnect(profile)
|
||||
self.host.memory.asyncDeleteProfile(profile, force=True)
|
||||
return defer.succeed({})
|
||||
|
||||
def errback(failure):
|
||||
error_ui = xml_tools.XMLUI("popup", title="Error")
|
||||
error_ui.addText(D_("Your XMPP account could not be deleted: %s") % failure.getErrorMessage())
|
||||
return defer.succeed({'xmlui': error_ui.toXml()})
|
||||
|
||||
d = ProsodyRegisterProtocol.prosodyctl(self, 'deluser', profile=profile)
|
||||
d.addCallbacks(userDeleted, errback)
|
||||
return d
|
||||
|
|
|
|||
Loading…
Reference in a new issue