Compare commits

...

4 Commits

Author SHA1 Message Date
Matthew Wild
db363367da Support circles with multiple group chats, remove default group chat 2023-11-06 13:52:30 +00:00
Matthew Wild
7ce13b55ac Merge pull request #162 from snikket-im/feature/policies-and-contacts
Add policy URLs and contact addresses for instances in the relevant places
2023-10-25 16:19:44 +01:00
Kim Alvefur
da52771ebe Merge pull request #161 from Zash/fix-logout
Fix revokation of token on logout
2023-10-21 15:57:46 +02:00
Kim Alvefur
e39b6ca8bb Fix revokation of token on logout
In OAuth 2.0, you don't authenticate with the revocation endpoint using
the token you are revoking, but rather the OAuth client credentials.
2023-10-07 17:39:37 +02:00
7 changed files with 275 additions and 71 deletions

View File

@@ -458,6 +458,8 @@ class EditCircleForm(BaseForm):
_l("Add user")
)
action_remove_group_chat = wtforms.StringField()
@bp.route("/circle/<id_>", methods=["GET", "POST"])
@client.require_admin_session()
@@ -530,6 +532,15 @@ async def edit_circle(id_: str) -> typing.Union[str, werkzeug.Response]:
_("User removed from circle"),
"success",
)
elif form.action_remove_group_chat.data:
await client.remove_group_chat(
id_,
form.action_remove_group_chat.data,
)
await flash(
_("Chat removed from circle"),
"success",
)
return redirect(url_for(".edit_circle", id_=id_))
@@ -537,6 +548,7 @@ async def edit_circle(id_: str) -> typing.Union[str, werkzeug.Response]:
"admin_edit_circle.html",
target_circle=circle,
form=form,
circle_chats=circle.chats,
circle_members=circle_members,
invite_form=invite_form,
)
@@ -583,6 +595,56 @@ async def delete_circle(id_: str) -> typing.Union[str, werkzeug.Response]:
)
class AddCircleChatForm(BaseForm):
name = wtforms.StringField(
_l("Group chat name"),
validators=[wtforms.validators.InputRequired()],
)
action_save = wtforms.SubmitField(
_l("Create group chat")
)
@bp.route("/circle/<id_>/add_chat", methods=["GET", "POST"])
@client.require_admin_session()
async def edit_circle_add_chat(
id_: str
) -> typing.Union[str, werkzeug.Response]:
async with client.authenticated_session() as session:
try:
circle = await client.get_group_by_id(
id_,
session=session,
)
except aiohttp.ClientResponseError as exc:
if exc.status == 404:
await flash(
_("No such circle exists"),
"alert",
)
return redirect(url_for(".circles"))
raise
form = AddCircleChatForm()
if form.validate_on_submit():
if form.action_save.data:
await client.add_group_chat(id_, form.name.data)
await flash(
_("New group chat added to circle"),
"success",
)
return redirect(url_for(".edit_circle", id_=id_))
return await render_template(
"admin_create_circle_chat.html",
target_circle=circle,
group_chat_form=form,
)
_CPU_EPOCH = time.process_time()
_MONOTONIC_EPOCH = time.monotonic()

View File

@@ -117,12 +117,30 @@ class AdminInviteInfo:
)
@dataclasses.dataclass(frozen=True)
class AdminGroupChatInfo:
id_: str
jid: str
name: str
@classmethod
def from_api_response(
cls,
data: typing.Mapping[str, typing.Any],
) -> "AdminGroupChatInfo":
return cls(
id_=data["id"],
jid=data["jid"],
name=data["name"],
)
@dataclasses.dataclass(frozen=True)
class AdminGroupInfo:
id_: str
name: str
muc_jid: typing.Optional[str]
members: typing.Collection[str]
chats: typing.Collection[AdminGroupChatInfo]
@classmethod
def from_api_response(
@@ -132,8 +150,11 @@ class AdminGroupInfo:
return cls(
id_=data["id"],
name=data["name"],
muc_jid=data.get("muc_jid") or None,
members=data.get("members", []),
chats=[
AdminGroupChatInfo.from_api_response(x)
for x in data.get("chats", [])
]
)
@@ -1032,7 +1053,7 @@ class ProsodyClient:
self,
name: str,
*,
create_muc: bool = True,
create_muc: bool = False,
session: aiohttp.ClientSession,
) -> AdminGroupInfo:
payload = {
@@ -1107,6 +1128,27 @@ class ProsodyClient:
) as resp:
self._raise_error_from_response(resp)
@autosession
async def add_group_chat(
self,
id_: str,
name: str,
*,
session: aiohttp.ClientSession,
) -> None:
payload: typing.Dict[str, typing.Any] = {
"name": name,
}
async with session.post(
self._admin_v1_endpoint(
"/groups/{}/chats".format(id_)
),
json=payload,
) as resp:
self._raise_error_from_response(resp)
@autosession
async def remove_group_member(
self,
@@ -1122,6 +1164,21 @@ class ProsodyClient:
) as resp:
self._raise_error_from_response(resp)
@autosession
async def remove_group_chat(
self,
group_id: str,
chat_id: str,
*,
session: aiohttp.ClientSession,
) -> None:
async with session.delete(
self._admin_v1_endpoint(
"/groups/{}/chats/{}".format(group_id, chat_id)
),
) as resp:
self._raise_error_from_response(resp)
@autosession
async def delete_group(
self,
@@ -1162,7 +1219,6 @@ class ProsodyClient:
self._raise_error_from_response(resp)
return True
@autosession
async def revoke_token(
self,
*,
@@ -1176,7 +1232,8 @@ class ProsodyClient:
async def logout(self) -> None:
try:
await self.revoke_token()
async with self._plain_session as session:
await self.revoke_token(session=session)
except aiohttp.ClientError:
self.logger.warn("failed to revoke token!",
exc_info=True)

View File

@@ -3,7 +3,7 @@
{% block content %}
<h1>{% trans %}Manage circles{% endtrans %}</h1>
<p>{% trans %}<em>Circles</em> aim to help people who are in the same social circle find each other on your service.{% endtrans %}</p>
<p>{% trans %}Users who are in the same circle will see each other in their contact list. In addition, each circle has a group chat where the circle members are included.{% endtrans %}</p>
<p>{% trans %}Users who are in the same circle will see each other in their contact list. In addition, each circle may have group chats where the circle members are included.{% endtrans %}</p>
{%- if circles -%}
<form method="POST" action="{{ url_for(".create_invite") }}">
{{- invite_form.csrf_token -}}

View File

@@ -0,0 +1,5 @@
{% extends "admin_app.html" %}
{% block content %}
<h1>{{ target_circle.name }}</h1>
{%- include "admin_create_circle_group_chat_form.html" -%}
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% from "library.j2" import form_button, render_errors %}
<form method="POST" action="{{ url_for(".edit_circle_add_chat", id_=target_circle.id_) }}">
{{- group_chat_form.csrf_token -}}
<div class="form layout-expanded">
<h2 class="form-title">{% trans %}Create new circle group chat{% endtrans %}</h2>
<p class="form-descr weak">{% trans %}Add a chat to your circle so its members can hold group discussions.{% endtrans %}</p>
<p class="form-descr weak"><strong>{% trans %}Tip:{% endtrans %}</strong> {% trans %}This is only for creating group chats that automatically include <em>all</em> members of the circle. If you want a normal group chat, create it in the Snikket app instead.{% endtrans %}</p>
<div class="f-ebox">
{{ group_chat_form.name.label }}
{{ group_chat_form.name }}
</div>
<div class="f-bbox">
{%- call form_button("add", group_chat_form.action_save, class="primary") %}{% endcall -%}
</div>
</div></form>

View File

@@ -13,13 +13,6 @@
<div class="box hint form layout-expanded">
<header>{% trans %}This is your main circle{% endtrans %}</header>
<p>{% trans %}This circle is managed automatically and cannot be removed or renamed.{% endtrans %}</p>
{%- if target_circle.muc_jid -%}
<div><label for="circle-muc-jid">{% trans %}Group chat address{% endtrans %}</label></div>
<div><input type="text" readonly="readonly" id="circle-muc-jid" value="{{ target_circle.muc_jid }}"></div>
{%- call clipboard_button(target_circle.muc_jid, show_label=True) -%}
{%- trans -%}Copy address{%- endtrans -%}
{%- endcall -%}
{%- endif -%}
</div>
{%- else -%}
<div class="form layout-expanded">
@@ -28,17 +21,6 @@
{{ form.name.label }}
{{ form.name }}
</div>
<div class="f-ebox">
{%- if target_circle.muc_jid -%}
<label for="circle-muc-jid">{% trans %}Group chat address{% endtrans %}</label>
<input type="text" readonly="readonly" id="circle-muc-jid" value="{{ target_circle.muc_jid }}">
{%- call clipboard_button(target_circle.muc_jid, show_label=True) -%}
{%- trans -%}Copy address{%- endtrans -%}
{%- endcall -%}
{%- else -%}
<p>{% trans %}This circle has no group chat associated.{% endtrans %}<p>
{%- endif -%}
</div>
<div class="f-bbox">
{%- call standard_button("back", url_for(".circles"), class="tertiary") -%}
{% trans %}Return to circle list{% endtrans %}
@@ -52,7 +34,39 @@
</div>
</div>
{%- endif -%}
<h2 id="chats">{% trans %}Group chats{% endtrans %}</h2>
<p>{% trans %}These group chats will be available to all members of the circle.{% endtrans %}</p>
{%- if circle_chats -%}
<div class="el-2 elevated"><table>
<thead>
<th>{% trans %}Name{% endtrans %}</th>
<th>{% trans %}Actions{% endtrans %}</th>
</thead>
<tbody>
{%- for chat in circle_chats -%}
<tr>
<td class="collapsible">{% call value_or_hint(chat.name) %}{% endcall %}</td>
<td class="nowrap">
{%- call custom_form_button("delete", form.action_remove_group_chat.name, chat.id_, class="primary danger", slim=True) -%}
{% trans name=chat.name %}Delete group chat '{{ name }}'{% endtrans %}
{%- endcall -%}
</td>
</tr>
{%- endfor -%}
</tbody>
</table></div>
{%- else -%}
<p>{% trans %}This circle currently has no group chats.{% endtrans %}</p>
{%- endif -%}
{%- call standard_button("add", url_for(".edit_circle_add_chat", id_=target_circle.id_), class="secondary") -%}
{% trans %}Add group chat{% endtrans %}
{%- endcall -%}
<h2 id="members">{% trans %}Circle members{% endtrans %}</h2>
<p>{% trans %}All members of the circle will see each other in their contact list.{% endtrans %}</p>
{%- if circle_members -%}
<div class="el-2 elevated"><table>
<thead>

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-10-25 16:15+0100\n"
"POT-Creation-Date: 2023-11-06 13:46+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,13 +18,13 @@ msgstr ""
"Generated-By: Babel 2.13.1\n"
#: snikket_web/admin.py:69 snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_edit_circle.html:59
#: snikket_web/templates/admin_edit_circle.html:73
#: snikket_web/templates/admin_users.html:8
msgid "Login name"
msgstr ""
#: snikket_web/admin.py:73 snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_edit_circle.html:60
#: snikket_web/templates/admin_edit_circle.html:74
#: snikket_web/templates/admin_users.html:9 snikket_web/user.py:63
msgid "Display name"
msgstr ""
@@ -143,6 +143,7 @@ msgstr ""
#: snikket_web/admin.py:394 snikket_web/admin.py:442
#: snikket_web/templates/admin_delete_circle.html:10
#: snikket_web/templates/admin_edit_circle.html:44
msgid "Name"
msgstr ""
@@ -166,47 +167,63 @@ msgstr ""
msgid "Add user"
msgstr ""
#: snikket_web/admin.py:474 snikket_web/admin.py:563
#: snikket_web/admin.py:476 snikket_web/admin.py:575 snikket_web/admin.py:623
msgid "No such circle exists"
msgstr ""
#: snikket_web/admin.py:511
#: snikket_web/admin.py:513
msgid "Circle data updated"
msgstr ""
#: snikket_web/admin.py:521
#: snikket_web/admin.py:523
msgid "User added to circle"
msgstr ""
#: snikket_web/admin.py:530
#: snikket_web/admin.py:532
msgid "User removed from circle"
msgstr ""
#: snikket_web/admin.py:547
#: snikket_web/admin.py:541
msgid "Chat removed from circle"
msgstr ""
#: snikket_web/admin.py:559
msgid "Delete circle permanently"
msgstr ""
#: snikket_web/admin.py:574
#: snikket_web/admin.py:586
msgid "Circle deleted"
msgstr ""
#: snikket_web/admin.py:640
#: snikket_web/admin.py:600
msgid "Group chat name"
msgstr ""
#: snikket_web/admin.py:605
msgid "Create group chat"
msgstr ""
#: snikket_web/admin.py:635
msgid "New group chat added to circle"
msgstr ""
#: snikket_web/admin.py:702
msgid "Message contents"
msgstr ""
#: snikket_web/admin.py:646
#: snikket_web/admin.py:708
msgid "Only send to online users"
msgstr ""
#: snikket_web/admin.py:650
#: snikket_web/admin.py:712
msgid "Post to all users"
msgstr ""
#: snikket_web/admin.py:654
#: snikket_web/admin.py:716
msgid "Send preview to yourself"
msgstr ""
#: snikket_web/admin.py:676
#: snikket_web/admin.py:738
msgid "Announcement sent!"
msgstr ""
@@ -474,8 +491,8 @@ msgstr ""
#: snikket_web/templates/admin_circles.html:6
msgid ""
"Users who are in the same circle will see each other in their contact "
"list. In addition, each circle has a group chat where the circle members "
"are included."
"list. In addition, each circle may have group chats where the circle "
"members are included."
msgstr ""
#: snikket_web/templates/admin_circles.html:13
@@ -487,7 +504,8 @@ msgid "Members"
msgstr ""
#: snikket_web/templates/admin_circles.html:15
#: snikket_web/templates/admin_edit_circle.html:61
#: snikket_web/templates/admin_edit_circle.html:45
#: snikket_web/templates/admin_edit_circle.html:75
#: snikket_web/templates/admin_invites.html:24
#: snikket_web/templates/admin_users.html:10
msgid "Actions"
@@ -523,6 +541,25 @@ msgstr ""
msgid "New circle"
msgstr ""
#: snikket_web/templates/admin_create_circle_group_chat_form.html:5
msgid "Create new circle group chat"
msgstr ""
#: snikket_web/templates/admin_create_circle_group_chat_form.html:6
msgid "Add a chat to your circle so its members can hold group discussions."
msgstr ""
#: snikket_web/templates/admin_create_circle_group_chat_form.html:7
msgid "Tip:"
msgstr ""
#: snikket_web/templates/admin_create_circle_group_chat_form.html:7
msgid ""
"This is only for creating group chats that automatically include "
"<em>all</em> members of the circle. If you want a normal group chat, "
"create it in the Snikket app instead."
msgstr ""
#: snikket_web/templates/admin_create_invite.html:3
msgid "Create invitation"
msgstr ""
@@ -565,8 +602,8 @@ msgid "Delete circle %(circle_name)s"
msgstr ""
#: snikket_web/templates/admin_delete_circle.html:6
#: snikket_web/templates/admin_edit_circle.html:48
#: snikket_web/templates/admin_edit_circle.html:51
#: snikket_web/templates/admin_edit_circle.html:30
#: snikket_web/templates/admin_edit_circle.html:33
msgid "Delete circle"
msgstr ""
@@ -625,69 +662,78 @@ msgstr ""
msgid "This circle is managed automatically and cannot be removed or renamed."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:17
#: snikket_web/templates/admin_edit_circle.html:33
msgid "Group chat address"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:20
#: snikket_web/templates/admin_edit_circle.html:36
#: snikket_web/templates/invite_success.html:15
#: snikket_web/templates/user_home.html:21
msgid "Copy address"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:26
#: snikket_web/templates/admin_edit_circle.html:19
msgid "Circle information"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:39
msgid "This circle has no group chat associated."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:44
#: snikket_web/templates/admin_edit_circle.html:26
msgid "Return to circle list"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:49
#: snikket_web/templates/admin_edit_circle.html:31
msgid "Deleting a circle does not delete any users in the circle."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:55
#: snikket_web/templates/admin_edit_circle.html:38
msgid "Group chats"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:39
msgid "These group chats will be available to all members of the circle."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:53
#, python-format
msgid "Delete group chat '%(name)s'"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:61
msgid "This circle currently has no group chats."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:64
msgid "Add group chat"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:67
msgid "Circle members"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:71
#: snikket_web/templates/admin_edit_circle.html:68
msgid "All members of the circle will see each other in their contact list."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:85
msgid "The user has been deleted from the server."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:71
#: snikket_web/templates/admin_edit_circle.html:85
#: snikket_web/templates/library.j2:108
msgid "deleted"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:77
#: snikket_web/templates/admin_edit_circle.html:91
#, python-format
msgid "Remove user %(username)s from circle"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:85
#: snikket_web/templates/admin_edit_circle.html:99
msgid "This circle currently has no members."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:87
#: snikket_web/templates/admin_edit_circle.html:101
msgid "Invite more members"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:90
#: snikket_web/templates/admin_edit_circle.html:104
msgid "Add existing user"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:101
#: snikket_web/templates/admin_edit_circle.html:115
msgid "All users added"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:102
#: snikket_web/templates/admin_edit_circle.html:116
msgid "All users on this service are already in this circle."
msgstr ""
@@ -1238,6 +1284,11 @@ msgstr ""
msgid "Your address"
msgstr ""
#: snikket_web/templates/invite_success.html:15
#: snikket_web/templates/user_home.html:21
msgid "Copy address"
msgstr ""
#: snikket_web/templates/invite_success.html:17
msgid ""
"You can now set up your legacy XMPP client with the above address and the"