slidge 0.1.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- slidge/__init__.py +61 -0
- slidge/__main__.py +192 -0
- slidge/command/__init__.py +28 -0
- slidge/command/adhoc.py +258 -0
- slidge/command/admin.py +193 -0
- slidge/command/base.py +441 -0
- slidge/command/categories.py +3 -0
- slidge/command/chat_command.py +288 -0
- slidge/command/register.py +179 -0
- slidge/command/user.py +250 -0
- slidge/contact/__init__.py +8 -0
- slidge/contact/contact.py +452 -0
- slidge/contact/roster.py +192 -0
- slidge/core/__init__.py +3 -0
- slidge/core/cache.py +183 -0
- slidge/core/config.py +209 -0
- slidge/core/gateway/__init__.py +3 -0
- slidge/core/gateway/base.py +892 -0
- slidge/core/gateway/caps.py +63 -0
- slidge/core/gateway/delivery_receipt.py +52 -0
- slidge/core/gateway/disco.py +80 -0
- slidge/core/gateway/mam.py +75 -0
- slidge/core/gateway/muc_admin.py +35 -0
- slidge/core/gateway/ping.py +66 -0
- slidge/core/gateway/presence.py +95 -0
- slidge/core/gateway/registration.py +53 -0
- slidge/core/gateway/search.py +102 -0
- slidge/core/gateway/session_dispatcher.py +757 -0
- slidge/core/gateway/vcard_temp.py +130 -0
- slidge/core/mixins/__init__.py +19 -0
- slidge/core/mixins/attachment.py +506 -0
- slidge/core/mixins/avatar.py +167 -0
- slidge/core/mixins/base.py +31 -0
- slidge/core/mixins/disco.py +130 -0
- slidge/core/mixins/lock.py +31 -0
- slidge/core/mixins/message.py +398 -0
- slidge/core/mixins/message_maker.py +154 -0
- slidge/core/mixins/presence.py +217 -0
- slidge/core/mixins/recipient.py +43 -0
- slidge/core/pubsub.py +525 -0
- slidge/core/session.py +752 -0
- slidge/group/__init__.py +10 -0
- slidge/group/archive.py +125 -0
- slidge/group/bookmarks.py +163 -0
- slidge/group/participant.py +440 -0
- slidge/group/room.py +1095 -0
- slidge/migration.py +18 -0
- slidge/py.typed +0 -0
- slidge/slixfix/__init__.py +68 -0
- slidge/slixfix/link_preview/__init__.py +10 -0
- slidge/slixfix/link_preview/link_preview.py +17 -0
- slidge/slixfix/link_preview/stanza.py +99 -0
- slidge/slixfix/roster.py +60 -0
- slidge/slixfix/xep_0077/__init__.py +10 -0
- slidge/slixfix/xep_0077/register.py +289 -0
- slidge/slixfix/xep_0077/stanza.py +104 -0
- slidge/slixfix/xep_0100/__init__.py +5 -0
- slidge/slixfix/xep_0100/gateway.py +121 -0
- slidge/slixfix/xep_0100/stanza.py +9 -0
- slidge/slixfix/xep_0153/__init__.py +10 -0
- slidge/slixfix/xep_0153/stanza.py +25 -0
- slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
- slidge/slixfix/xep_0264/__init__.py +5 -0
- slidge/slixfix/xep_0264/stanza.py +36 -0
- slidge/slixfix/xep_0264/thumbnail.py +23 -0
- slidge/slixfix/xep_0292/__init__.py +5 -0
- slidge/slixfix/xep_0292/vcard4.py +100 -0
- slidge/slixfix/xep_0313/__init__.py +12 -0
- slidge/slixfix/xep_0313/mam.py +262 -0
- slidge/slixfix/xep_0313/stanza.py +359 -0
- slidge/slixfix/xep_0317/__init__.py +5 -0
- slidge/slixfix/xep_0317/hats.py +17 -0
- slidge/slixfix/xep_0317/stanza.py +28 -0
- slidge/slixfix/xep_0356_old/__init__.py +7 -0
- slidge/slixfix/xep_0356_old/privilege.py +167 -0
- slidge/slixfix/xep_0356_old/stanza.py +44 -0
- slidge/slixfix/xep_0424/__init__.py +9 -0
- slidge/slixfix/xep_0424/retraction.py +77 -0
- slidge/slixfix/xep_0424/stanza.py +28 -0
- slidge/slixfix/xep_0490/__init__.py +8 -0
- slidge/slixfix/xep_0490/mds.py +47 -0
- slidge/slixfix/xep_0490/stanza.py +17 -0
- slidge/util/__init__.py +15 -0
- slidge/util/archive_msg.py +61 -0
- slidge/util/conf.py +206 -0
- slidge/util/db.py +229 -0
- slidge/util/schema.sql +126 -0
- slidge/util/sql.py +508 -0
- slidge/util/test.py +295 -0
- slidge/util/types.py +180 -0
- slidge/util/util.py +295 -0
- slidge-0.1.0.dist-info/LICENSE +661 -0
- slidge-0.1.0.dist-info/METADATA +109 -0
- slidge-0.1.0.dist-info/RECORD +96 -0
- slidge-0.1.0.dist-info/WHEEL +4 -0
- slidge-0.1.0.dist-info/entry_points.txt +3 -0
slidge/migration.py
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
import logging
|
2
|
+
import shutil
|
3
|
+
|
4
|
+
from .core import config
|
5
|
+
|
6
|
+
|
7
|
+
def remove_avatar_cache_v1():
|
8
|
+
old_dir = config.HOME_DIR / "slidge_avatars"
|
9
|
+
if old_dir.exists():
|
10
|
+
log.info("Avatar cache dir v1 found, clearing it.")
|
11
|
+
shutil.rmtree(old_dir)
|
12
|
+
|
13
|
+
|
14
|
+
def migrate():
|
15
|
+
remove_avatar_cache_v1()
|
16
|
+
|
17
|
+
|
18
|
+
log = logging.getLogger(__name__)
|
slidge/py.typed
ADDED
File without changes
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# This module contains patches for slixmpp; some have pending requests upstream
|
2
|
+
# and should be removed on the next slixmpp release.
|
3
|
+
|
4
|
+
# ruff: noqa: F401
|
5
|
+
|
6
|
+
import slixmpp.plugins
|
7
|
+
from slixmpp import Message
|
8
|
+
from slixmpp.plugins.xep_0050 import XEP_0050, Command
|
9
|
+
from slixmpp.xmlstream import StanzaBase
|
10
|
+
|
11
|
+
from . import ( # xep_0356,
|
12
|
+
link_preview,
|
13
|
+
xep_0077,
|
14
|
+
xep_0100,
|
15
|
+
xep_0153,
|
16
|
+
xep_0264,
|
17
|
+
xep_0292,
|
18
|
+
xep_0313,
|
19
|
+
xep_0317,
|
20
|
+
xep_0356_old,
|
21
|
+
xep_0424,
|
22
|
+
xep_0490,
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
def session_bind(self, jid):
|
27
|
+
self.xmpp["xep_0030"].add_feature(Command.namespace)
|
28
|
+
# awful hack to for the disco items: we need to comment this line
|
29
|
+
# related issue: https://todo.sr.ht/~nicoco/slidge/131
|
30
|
+
# self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
|
31
|
+
|
32
|
+
|
33
|
+
XEP_0050.session_bind = session_bind # type:ignore
|
34
|
+
|
35
|
+
|
36
|
+
def reply(self, body=None, clear=True):
|
37
|
+
"""
|
38
|
+
Overrides slixmpp's Message.reply(), since it strips to sender's resource
|
39
|
+
for mtype=groupchat, and we do not want that, because when we raise an XMPPError,
|
40
|
+
we actually want to preserve the resource.
|
41
|
+
(this is called in RootStanza.exception() to handle XMPPErrors)
|
42
|
+
"""
|
43
|
+
new_message = StanzaBase.reply(self, clear)
|
44
|
+
new_message["thread"] = self["thread"]
|
45
|
+
new_message["parent_thread"] = self["parent_thread"]
|
46
|
+
|
47
|
+
del new_message["id"]
|
48
|
+
if self.stream is not None and self.stream.use_message_ids:
|
49
|
+
new_message["id"] = self.stream.new_id()
|
50
|
+
|
51
|
+
if body is not None:
|
52
|
+
new_message["body"] = body
|
53
|
+
return new_message
|
54
|
+
|
55
|
+
|
56
|
+
slixmpp.plugins.PLUGINS.extend(
|
57
|
+
[
|
58
|
+
"link_preview",
|
59
|
+
"xep_0264",
|
60
|
+
"xep_0292_provider",
|
61
|
+
"xep_0317",
|
62
|
+
"xep_0356_old",
|
63
|
+
"xep_0490",
|
64
|
+
]
|
65
|
+
)
|
66
|
+
|
67
|
+
|
68
|
+
Message.reply = reply # type: ignore
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Slixmpp: The Slick XMPP Library
|
2
|
+
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
3
|
+
# This file is part of Slixmpp.
|
4
|
+
# See the file LICENSE for copying permission.
|
5
|
+
|
6
|
+
from slixmpp.plugins.base import register_plugin
|
7
|
+
|
8
|
+
from .link_preview import LinkPreview
|
9
|
+
|
10
|
+
register_plugin(LinkPreview)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Slixmpp: The Slick XMPP Library
|
2
|
+
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
3
|
+
# This file is part of Slixmpp.
|
4
|
+
# See the file LICENSE for copying permission.
|
5
|
+
from slixmpp.plugins import BasePlugin
|
6
|
+
|
7
|
+
from . import stanza
|
8
|
+
|
9
|
+
|
10
|
+
class LinkPreview(BasePlugin):
|
11
|
+
name = "link_preview"
|
12
|
+
description = "Sender-generated link previews"
|
13
|
+
dependencies = set()
|
14
|
+
stanza = stanza
|
15
|
+
|
16
|
+
def plugin_init(self):
|
17
|
+
stanza.register_plugin()
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# Slixmpp: The Slick XMPP Library
|
2
|
+
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
3
|
+
# This file is part of Slixmpp.
|
4
|
+
# See the file LICENSE for copying permission.
|
5
|
+
from typing import Optional, Type
|
6
|
+
|
7
|
+
from slixmpp.stanza.message import Message
|
8
|
+
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
|
9
|
+
|
10
|
+
|
11
|
+
class LinkPreview(ElementBase):
|
12
|
+
name = "Description"
|
13
|
+
namespace = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
14
|
+
plugin_attrib = "link_preview"
|
15
|
+
plugin_multi_attrib = "link_previews"
|
16
|
+
interfaces = {"about", "title", "description", "url", "image", "type", "site_name"}
|
17
|
+
|
18
|
+
def _set_og(self, el: ElementBase, value: str) -> None:
|
19
|
+
el.xml.text = value
|
20
|
+
self.xml.append(el.xml)
|
21
|
+
|
22
|
+
def _get_og(self, el: Type[ElementBase]) -> Optional[str]:
|
23
|
+
child = self.xml.find(f"{{{el.namespace}}}{el.name}")
|
24
|
+
if child is None:
|
25
|
+
return None
|
26
|
+
return child.text
|
27
|
+
|
28
|
+
def set_title(self, v: str) -> None:
|
29
|
+
self._set_og(Title(), v)
|
30
|
+
|
31
|
+
def get_title(self) -> Optional[str]:
|
32
|
+
return self._get_og(Title)
|
33
|
+
|
34
|
+
def set_description(self, v: str) -> None:
|
35
|
+
self._set_og(Description(), v)
|
36
|
+
|
37
|
+
def get_description(self) -> Optional[str]:
|
38
|
+
return self._get_og(Description)
|
39
|
+
|
40
|
+
def set_url(self, v: str) -> None:
|
41
|
+
self._set_og(Url(), v)
|
42
|
+
|
43
|
+
def get_url(self) -> Optional[str]:
|
44
|
+
return self._get_og(Url)
|
45
|
+
|
46
|
+
def set_image(self, v: str) -> None:
|
47
|
+
self._set_og(Image(), v)
|
48
|
+
|
49
|
+
def get_image(self) -> Optional[str]:
|
50
|
+
return self._get_og(Image)
|
51
|
+
|
52
|
+
def set_type(self, v: str) -> None:
|
53
|
+
self._set_og(Type_(), v)
|
54
|
+
|
55
|
+
def get_type(self) -> Optional[str]:
|
56
|
+
return self._get_og(Type_)
|
57
|
+
|
58
|
+
def set_site_name(self, v: str) -> None:
|
59
|
+
self._set_og(SiteName(), v)
|
60
|
+
|
61
|
+
def get_site_name(self) -> Optional[str]:
|
62
|
+
return self._get_og(SiteName)
|
63
|
+
|
64
|
+
def get_about(self) -> Optional[str]:
|
65
|
+
return self.xml.attrib.get(f"{{{self.namespace}}}about")
|
66
|
+
|
67
|
+
|
68
|
+
class OpenGraphMixin(ElementBase):
|
69
|
+
namespace = "https://ogp.me/ns#"
|
70
|
+
|
71
|
+
|
72
|
+
class Title(OpenGraphMixin):
|
73
|
+
name = plugin_attrib = "title"
|
74
|
+
|
75
|
+
|
76
|
+
class Description(OpenGraphMixin):
|
77
|
+
name = plugin_attrib = "description"
|
78
|
+
|
79
|
+
|
80
|
+
class Url(OpenGraphMixin):
|
81
|
+
name = plugin_attrib = "url"
|
82
|
+
|
83
|
+
|
84
|
+
class Image(OpenGraphMixin):
|
85
|
+
name = plugin_attrib = "image"
|
86
|
+
|
87
|
+
|
88
|
+
class Type_(OpenGraphMixin):
|
89
|
+
name = plugin_attrib = "type"
|
90
|
+
|
91
|
+
|
92
|
+
class SiteName(OpenGraphMixin):
|
93
|
+
name = plugin_attrib = "site_name"
|
94
|
+
|
95
|
+
|
96
|
+
def register_plugin():
|
97
|
+
for plugin in Title, Description, Url, Image, Type_, SiteName:
|
98
|
+
register_stanza_plugin(plugin, Title)
|
99
|
+
register_stanza_plugin(Message, LinkPreview, iterable=True)
|
slidge/slixfix/roster.py
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
from slixmpp import JID
|
2
|
+
|
3
|
+
from ..util.db import log, user_store
|
4
|
+
|
5
|
+
|
6
|
+
class YesSet(set):
|
7
|
+
"""
|
8
|
+
A pseudo-set which always test True for membership
|
9
|
+
"""
|
10
|
+
|
11
|
+
def __contains__(self, item):
|
12
|
+
log.debug("Test in")
|
13
|
+
return True
|
14
|
+
|
15
|
+
|
16
|
+
class RosterBackend:
|
17
|
+
"""
|
18
|
+
A pseudo-roster for the gateway component.
|
19
|
+
|
20
|
+
If a user is in the user store, this will behave as if the user is part of the
|
21
|
+
roster with subscription "both", and "none" otherwise.
|
22
|
+
|
23
|
+
This is rudimentary but the only sane way I could come up with so far.
|
24
|
+
"""
|
25
|
+
|
26
|
+
@staticmethod
|
27
|
+
def entries(_owner_jid, _default=None):
|
28
|
+
return YesSet()
|
29
|
+
|
30
|
+
@staticmethod
|
31
|
+
def save(_owner_jid, _jid, _item_state, _db_state):
|
32
|
+
pass
|
33
|
+
|
34
|
+
@staticmethod
|
35
|
+
def load(_owner_jid, jid, _db_state):
|
36
|
+
log.debug("Load %s", jid)
|
37
|
+
user = user_store.get_by_jid(JID(jid))
|
38
|
+
log.debug("User %s", user)
|
39
|
+
if user is None:
|
40
|
+
return {
|
41
|
+
"name": "",
|
42
|
+
"groups": [],
|
43
|
+
"from": False,
|
44
|
+
"to": False,
|
45
|
+
"pending_in": False,
|
46
|
+
"pending_out": False,
|
47
|
+
"whitelisted": False,
|
48
|
+
"subscription": "both",
|
49
|
+
}
|
50
|
+
else:
|
51
|
+
return {
|
52
|
+
"name": "",
|
53
|
+
"groups": [],
|
54
|
+
"from": True,
|
55
|
+
"to": True,
|
56
|
+
"pending_in": False,
|
57
|
+
"pending_out": False,
|
58
|
+
"whitelisted": False,
|
59
|
+
"subscription": "none",
|
60
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# Slixmpp: The Slick XMPP Library
|
2
|
+
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
3
|
+
# This file is part of Slixmpp.
|
4
|
+
# See the file LICENSE for copying permission.
|
5
|
+
from slixmpp.plugins.base import register_plugin
|
6
|
+
|
7
|
+
from .register import XEP_0077
|
8
|
+
from .stanza import Register, RegisterFeature
|
9
|
+
|
10
|
+
register_plugin(XEP_0077)
|
@@ -0,0 +1,289 @@
|
|
1
|
+
# Slixmpp: The Slick XMPP Library
|
2
|
+
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
3
|
+
# This file is part of Slixmpp.
|
4
|
+
# See the file LICENSE for copying permission.
|
5
|
+
import logging
|
6
|
+
import ssl
|
7
|
+
|
8
|
+
from slixmpp.exceptions import XMPPError
|
9
|
+
from slixmpp.plugins import BasePlugin
|
10
|
+
from slixmpp.stanza import Iq, StreamFeatures
|
11
|
+
from slixmpp.xmlstream import JID, StanzaBase, register_stanza_plugin
|
12
|
+
from slixmpp.xmlstream.handler import CoroutineCallback
|
13
|
+
from slixmpp.xmlstream.matcher import StanzaPath
|
14
|
+
|
15
|
+
from . import stanza
|
16
|
+
from .stanza import Register, RegisterFeature
|
17
|
+
|
18
|
+
log = logging.getLogger(__name__)
|
19
|
+
|
20
|
+
|
21
|
+
# noinspection PyPep8Naming
|
22
|
+
class XEP_0077(BasePlugin):
|
23
|
+
"""
|
24
|
+
XEP-0077: In-Band Registration
|
25
|
+
|
26
|
+
Events:
|
27
|
+
|
28
|
+
::
|
29
|
+
|
30
|
+
user_register -- After successful validation and add to the user store
|
31
|
+
in api["user_validate"]
|
32
|
+
user_unregister -- After successful user removal in api["user_remove"]
|
33
|
+
user_modify -- After successful user modify in api["user_modify"]
|
34
|
+
|
35
|
+
Config:
|
36
|
+
|
37
|
+
::
|
38
|
+
|
39
|
+
form_fields and form_instructions are only used if api["make_registration_form"] is
|
40
|
+
not overridden; in this case form_fields MUST be None
|
41
|
+
|
42
|
+
API:
|
43
|
+
|
44
|
+
::
|
45
|
+
|
46
|
+
user_get(jid, node, ifrom, iq)
|
47
|
+
Returns a dict-like object containing `form_fields` for this user or None
|
48
|
+
user_remove(jid, node, ifrom, iq)
|
49
|
+
Removes a user or raise KeyError in case the user is not found in the user store
|
50
|
+
make_registration_form(self, jid, node, ifrom, iq)
|
51
|
+
Returns an iq reply to a registration form request, pre-filled and with
|
52
|
+
<registered/> in case the requesting entity is already registered to us
|
53
|
+
user_validate((self, jid, node, ifrom, registration)
|
54
|
+
Add the user to the user store or raise ValueError(msg) if any problem is encountered
|
55
|
+
msg is sent back to the XMPP client as an error message.
|
56
|
+
user_modify(jid, node, ifrom, iq)
|
57
|
+
Modify the user in the user store or raise ValueError(msg) (similarly to user_validate)
|
58
|
+
"""
|
59
|
+
|
60
|
+
name = "xep_0077"
|
61
|
+
description = "XEP-0077: In-Band Registration (slidge)"
|
62
|
+
dependencies = {"xep_0004", "xep_0066"}
|
63
|
+
stanza = stanza
|
64
|
+
default_config = {
|
65
|
+
"create_account": True,
|
66
|
+
"force_registration": False,
|
67
|
+
"order": 50,
|
68
|
+
"form_fields": {"username", "password"},
|
69
|
+
"form_instructions": "Enter your credentials",
|
70
|
+
"enable_subscription": True,
|
71
|
+
}
|
72
|
+
_user_store: dict[str, dict[str, str]]
|
73
|
+
|
74
|
+
def plugin_init(self):
|
75
|
+
register_stanza_plugin(StreamFeatures, RegisterFeature)
|
76
|
+
register_stanza_plugin(Iq, Register)
|
77
|
+
|
78
|
+
if self.xmpp.is_component:
|
79
|
+
self.xmpp["xep_0030"].add_feature("jabber:iq:register")
|
80
|
+
self.xmpp.register_handler(
|
81
|
+
CoroutineCallback(
|
82
|
+
"registration",
|
83
|
+
StanzaPath(f"/iq@to={self.xmpp.boundjid.bare}/register"),
|
84
|
+
self._handle_registration,
|
85
|
+
)
|
86
|
+
)
|
87
|
+
self._user_store = {}
|
88
|
+
self.api.register(self._user_get, "user_get")
|
89
|
+
self.api.register(self._user_remove, "user_remove")
|
90
|
+
self.api.register(self._user_modify, "user_modify")
|
91
|
+
self.api.register(self._make_registration_form, "make_registration_form")
|
92
|
+
self.api.register(self._user_validate, "user_validate")
|
93
|
+
else:
|
94
|
+
self.xmpp.register_feature(
|
95
|
+
"register",
|
96
|
+
self._handle_register_feature,
|
97
|
+
restart=False,
|
98
|
+
order=self.order,
|
99
|
+
)
|
100
|
+
|
101
|
+
register_stanza_plugin(Register, self.xmpp["xep_0004"].stanza.Form)
|
102
|
+
register_stanza_plugin(Register, self.xmpp["xep_0066"].stanza.OOB)
|
103
|
+
|
104
|
+
self.xmpp.add_event_handler("connected", self._force_registration)
|
105
|
+
|
106
|
+
def plugin_end(self):
|
107
|
+
if not self.xmpp.is_component:
|
108
|
+
self.xmpp.unregister_feature("register", self.order)
|
109
|
+
|
110
|
+
def _user_get(self, _jid, _node, _ifrom, iq):
|
111
|
+
return self._user_store.get(iq["from"].bare)
|
112
|
+
|
113
|
+
def _user_remove(self, _jid, _node, _ifrom, iq):
|
114
|
+
return self._user_store.pop(iq["from"].bare)
|
115
|
+
|
116
|
+
async def _make_registration_form(self, _jid, _node, _ifrom, iq: Iq):
|
117
|
+
reg = iq["register"]
|
118
|
+
user = await self.api["user_get"](None, None, iq["from"], iq)
|
119
|
+
|
120
|
+
if user is None:
|
121
|
+
user = {}
|
122
|
+
else:
|
123
|
+
reg["registered"] = True
|
124
|
+
|
125
|
+
reg["instructions"] = self.form_instructions
|
126
|
+
|
127
|
+
for field in self.form_fields:
|
128
|
+
data = user.get(field, "")
|
129
|
+
if data:
|
130
|
+
reg[field] = data
|
131
|
+
else:
|
132
|
+
# Add a blank field
|
133
|
+
reg.add_field(field)
|
134
|
+
|
135
|
+
reply = iq.reply()
|
136
|
+
reply.set_payload(reg.xml)
|
137
|
+
return reply
|
138
|
+
|
139
|
+
def _user_validate(self, _jid, _node, ifrom, registration):
|
140
|
+
self._user_store[ifrom.bare] = {
|
141
|
+
key: registration[key] for key in self.form_fields
|
142
|
+
}
|
143
|
+
|
144
|
+
def _user_modify(self, _jid, _node, ifrom, registration):
|
145
|
+
self._user_store[ifrom.bare] = {
|
146
|
+
key: registration[key] for key in self.form_fields
|
147
|
+
}
|
148
|
+
|
149
|
+
async def _handle_registration(self, iq: StanzaBase):
|
150
|
+
if iq["type"] == "get":
|
151
|
+
if not self.enable_subscription:
|
152
|
+
raise XMPPError(
|
153
|
+
"bad-request",
|
154
|
+
text="You must use adhoc commands to register to this gateway.",
|
155
|
+
)
|
156
|
+
await self._send_form(iq)
|
157
|
+
elif iq["type"] == "set":
|
158
|
+
form_dict = iq["register"]["form"].get_values() or iq["register"]
|
159
|
+
|
160
|
+
if form_dict.get("remove"):
|
161
|
+
try:
|
162
|
+
await self.api["user_remove"](None, None, iq["from"], iq)
|
163
|
+
except KeyError:
|
164
|
+
_send_error(
|
165
|
+
iq,
|
166
|
+
"404",
|
167
|
+
"cancel",
|
168
|
+
"item-not-found",
|
169
|
+
"User not found",
|
170
|
+
)
|
171
|
+
else:
|
172
|
+
reply = iq.reply()
|
173
|
+
reply.send()
|
174
|
+
self.xmpp.event("user_unregister", iq)
|
175
|
+
return
|
176
|
+
|
177
|
+
if not self.enable_subscription:
|
178
|
+
raise XMPPError(
|
179
|
+
"bad-request",
|
180
|
+
text="You must use adhoc commands to register to this gateway.",
|
181
|
+
)
|
182
|
+
|
183
|
+
if self.form_fields is not None:
|
184
|
+
for field in self.form_fields:
|
185
|
+
if not iq["register"][field]:
|
186
|
+
# Incomplete Registration
|
187
|
+
_send_error(
|
188
|
+
iq,
|
189
|
+
"406",
|
190
|
+
"modify",
|
191
|
+
"not-acceptable",
|
192
|
+
"Please fill in all fields.",
|
193
|
+
)
|
194
|
+
return
|
195
|
+
|
196
|
+
user = await self.api["user_get"](None, None, iq["from"], iq)
|
197
|
+
|
198
|
+
try:
|
199
|
+
if user is None:
|
200
|
+
await self.api["user_validate"](None, None, iq["from"], form_dict)
|
201
|
+
else:
|
202
|
+
await self.api["user_modify"](None, None, iq["from"], form_dict)
|
203
|
+
except ValueError as e:
|
204
|
+
_send_error(iq, "406", "modify", "not-acceptable", "\n".join(e.args))
|
205
|
+
return
|
206
|
+
|
207
|
+
reply = iq.reply()
|
208
|
+
reply.send()
|
209
|
+
|
210
|
+
if user is None:
|
211
|
+
self.xmpp.event("user_register", iq)
|
212
|
+
else:
|
213
|
+
self.xmpp.event("user_modify", iq)
|
214
|
+
|
215
|
+
async def _send_form(self, iq):
|
216
|
+
reply = await self.api["make_registration_form"](None, None, iq["from"], iq)
|
217
|
+
reply.send()
|
218
|
+
|
219
|
+
def _force_registration(self, _event):
|
220
|
+
if self.force_registration:
|
221
|
+
self.xmpp.add_filter("in", self._force_stream_feature)
|
222
|
+
|
223
|
+
def _force_stream_feature(self, stanza_):
|
224
|
+
if isinstance(stanza_, StreamFeatures):
|
225
|
+
if not self.xmpp.disable_starttls:
|
226
|
+
if "starttls" not in self.xmpp.features:
|
227
|
+
return stanza_
|
228
|
+
elif not isinstance(self.xmpp.socket, ssl.SSLSocket):
|
229
|
+
return stanza_
|
230
|
+
if "mechanisms" not in self.xmpp.features:
|
231
|
+
log.debug("Forced adding in-band registration stream feature")
|
232
|
+
stanza_.enable("register")
|
233
|
+
self.xmpp.del_filter("in", self._force_stream_feature)
|
234
|
+
return stanza_
|
235
|
+
|
236
|
+
async def _handle_register_feature(self, _features):
|
237
|
+
if "mechanisms" in self.xmpp.features:
|
238
|
+
# We have already logged in with an account
|
239
|
+
return False
|
240
|
+
|
241
|
+
if self.create_account and self.xmpp.event_handled("register"):
|
242
|
+
form = await self.get_registration()
|
243
|
+
await self.xmpp.event_async("register", form)
|
244
|
+
return True
|
245
|
+
return False
|
246
|
+
|
247
|
+
def get_registration(self, jid=None, ifrom=None, timeout=None, callback=None):
|
248
|
+
iq = self.xmpp.Iq()
|
249
|
+
iq["type"] = "get"
|
250
|
+
iq["to"] = jid
|
251
|
+
iq["from"] = ifrom
|
252
|
+
iq.enable("register")
|
253
|
+
return iq.send(timeout=timeout, callback=callback)
|
254
|
+
|
255
|
+
def cancel_registration(self, jid=None, ifrom=None, timeout=None, callback=None):
|
256
|
+
iq = self.xmpp.Iq()
|
257
|
+
iq["type"] = "set"
|
258
|
+
iq["to"] = jid
|
259
|
+
iq["from"] = ifrom
|
260
|
+
iq["register"]["remove"] = True
|
261
|
+
return iq.send(timeout=timeout, callback=callback)
|
262
|
+
|
263
|
+
def change_password(
|
264
|
+
self, password, jid=None, ifrom=None, timeout=None, callback=None
|
265
|
+
):
|
266
|
+
iq = self.xmpp.Iq()
|
267
|
+
iq["type"] = "set"
|
268
|
+
iq["to"] = jid
|
269
|
+
iq["from"] = ifrom
|
270
|
+
if self.xmpp.is_component:
|
271
|
+
ifrom = JID(ifrom)
|
272
|
+
iq["register"]["username"] = ifrom.user
|
273
|
+
else:
|
274
|
+
iq["register"]["username"] = self.xmpp.boundjid.user
|
275
|
+
iq["register"]["password"] = password
|
276
|
+
return iq.send(timeout=timeout, callback=callback)
|
277
|
+
|
278
|
+
|
279
|
+
def _send_error(iq, code, error_type, name, text=""):
|
280
|
+
# It would be nice to raise XMPPError but the iq payload
|
281
|
+
# should include the register info
|
282
|
+
reply = iq.reply()
|
283
|
+
reply.set_payload(iq["register"].xml)
|
284
|
+
reply.error()
|
285
|
+
reply["error"]["code"] = code
|
286
|
+
reply["error"]["type"] = error_type
|
287
|
+
reply["error"]["condition"] = name
|
288
|
+
reply["error"]["text"] = text
|
289
|
+
reply.send()
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# Slixmpp: The Slick XMPP Library
|
2
|
+
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
3
|
+
# This file is part of Slixmpp.
|
4
|
+
# See the file LICENSE for copying permission.
|
5
|
+
from __future__ import unicode_literals
|
6
|
+
|
7
|
+
from typing import ClassVar, Set
|
8
|
+
|
9
|
+
from slixmpp.xmlstream import ElementBase
|
10
|
+
|
11
|
+
|
12
|
+
class Register(ElementBase):
|
13
|
+
namespace = "jabber:iq:register"
|
14
|
+
name = "query"
|
15
|
+
plugin_attrib = "register"
|
16
|
+
interfaces = {
|
17
|
+
"username",
|
18
|
+
"password",
|
19
|
+
"email",
|
20
|
+
"nick",
|
21
|
+
"name",
|
22
|
+
"first",
|
23
|
+
"last",
|
24
|
+
"address",
|
25
|
+
"city",
|
26
|
+
"state",
|
27
|
+
"zip",
|
28
|
+
"phone",
|
29
|
+
"url",
|
30
|
+
"date",
|
31
|
+
"misc",
|
32
|
+
"text",
|
33
|
+
"key",
|
34
|
+
"registered",
|
35
|
+
"remove",
|
36
|
+
"instructions",
|
37
|
+
"fields",
|
38
|
+
}
|
39
|
+
sub_interfaces = interfaces
|
40
|
+
form_fields = {
|
41
|
+
"username",
|
42
|
+
"password",
|
43
|
+
"email",
|
44
|
+
"nick",
|
45
|
+
"name",
|
46
|
+
"first",
|
47
|
+
"last",
|
48
|
+
"address",
|
49
|
+
"city",
|
50
|
+
"state",
|
51
|
+
"zip",
|
52
|
+
"phone",
|
53
|
+
"url",
|
54
|
+
"date",
|
55
|
+
"misc",
|
56
|
+
"text",
|
57
|
+
"key",
|
58
|
+
}
|
59
|
+
|
60
|
+
def get_registered(self):
|
61
|
+
present = self.xml.find("{%s}registered" % self.namespace)
|
62
|
+
return present is not None
|
63
|
+
|
64
|
+
def get_remove(self):
|
65
|
+
present = self.xml.find("{%s}remove" % self.namespace)
|
66
|
+
return present is not None
|
67
|
+
|
68
|
+
def set_registered(self, value):
|
69
|
+
if value:
|
70
|
+
self.add_field("registered")
|
71
|
+
else:
|
72
|
+
del self["registered"]
|
73
|
+
|
74
|
+
def set_remove(self, value):
|
75
|
+
if value:
|
76
|
+
self.add_field("remove")
|
77
|
+
else:
|
78
|
+
del self["remove"]
|
79
|
+
|
80
|
+
def add_field(self, value):
|
81
|
+
self._set_sub_text(value, "", keep=True)
|
82
|
+
|
83
|
+
def get_fields(self):
|
84
|
+
fields = set()
|
85
|
+
for field in self.form_fields:
|
86
|
+
if self.xml.find("{%s}%s" % (self.namespace, field)) is not None:
|
87
|
+
fields.add(field)
|
88
|
+
return fields
|
89
|
+
|
90
|
+
def set_fields(self, fields):
|
91
|
+
del self["fields"]
|
92
|
+
for field in fields:
|
93
|
+
self._set_sub_text(field, "", keep=True)
|
94
|
+
|
95
|
+
def del_fields(self):
|
96
|
+
for field in self.form_fields:
|
97
|
+
self._del_sub(field)
|
98
|
+
|
99
|
+
|
100
|
+
class RegisterFeature(ElementBase):
|
101
|
+
name = "register"
|
102
|
+
namespace = "http://jabber.org/features/iq-register"
|
103
|
+
plugin_attrib = name
|
104
|
+
interfaces: ClassVar[Set[str]] = set()
|