Skip to content

Commit ba1085f

Browse files
committed
feat: add invitation flow to invite users
This allows bringing in users for newly created subscription.
1 parent 034881a commit ba1085f

8 files changed

Lines changed: 601 additions & 12 deletions

File tree

weblate_web/crm/forms.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ class ManualInteractionForm(forms.Form):
4242
)
4343

4444

45+
class CustomerUserForm(forms.Form):
46+
email = forms.EmailField(label=gettext_lazy("E-mail"))
47+
full_name = forms.CharField(label=gettext_lazy("Full name"), max_length=150)
48+
49+
4550
class ServiceMaintenanceWindowForm(forms.ModelForm):
4651
class Meta:
4752
model = Service

weblate_web/crm/hosted.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#
2+
# Copyright © Michal Čihař <michal@weblate.org>
3+
#
4+
# This file is part of Weblate <https://weblate.org/>
5+
#
6+
# This program is free software: you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation, either version 3 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
#
19+
20+
from __future__ import annotations
21+
22+
from typing import Any
23+
24+
import requests
25+
from django.conf import settings
26+
from django.core.signing import BadSignature, SignatureExpired, dumps, loads
27+
from django.utils.translation import gettext
28+
29+
USER_ENSURE_SALT = "weblate.user-ensure"
30+
USER_ENSURE_RESPONSE_SALT = "weblate.user-ensure-response"
31+
INVALID_HOSTED_USER_RESPONSE = "Invalid hosted user response"
32+
33+
34+
class HostedUserEnsureError(RuntimeError):
35+
"""Hosted user provisioning failed."""
36+
37+
38+
def raise_invalid_hosted_user_response() -> None:
39+
raise HostedUserEnsureError(gettext(INVALID_HOSTED_USER_RESPONSE))
40+
41+
42+
def ensure_hosted_user(email: str, full_name: str) -> tuple[dict[str, Any], bool]:
43+
if not settings.PAYMENT_SECRET:
44+
raise HostedUserEnsureError(gettext("Hosted user synchronization is disabled."))
45+
46+
try:
47+
response = requests.post(
48+
settings.HOSTED_USER_CREATE_API,
49+
data={
50+
"payload": dumps(
51+
{"email": email, "full_name": full_name},
52+
key=settings.PAYMENT_SECRET,
53+
salt=USER_ENSURE_SALT,
54+
)
55+
},
56+
timeout=60,
57+
)
58+
response.raise_for_status()
59+
except requests.RequestException as error:
60+
raise HostedUserEnsureError(str(error)) from error
61+
62+
try:
63+
response_payload = response.json()
64+
except ValueError as error:
65+
raise HostedUserEnsureError(gettext(INVALID_HOSTED_USER_RESPONSE)) from error
66+
if not isinstance(response_payload, dict):
67+
raise_invalid_hosted_user_response()
68+
signed_payload = response_payload.get("payload")
69+
if not isinstance(signed_payload, str):
70+
raise_invalid_hosted_user_response()
71+
72+
try:
73+
payload = loads(
74+
signed_payload,
75+
key=settings.PAYMENT_SECRET,
76+
max_age=300,
77+
salt=USER_ENSURE_RESPONSE_SALT,
78+
)
79+
except (BadSignature, SignatureExpired) as error:
80+
raise HostedUserEnsureError(gettext(INVALID_HOSTED_USER_RESPONSE)) from error
81+
if not isinstance(payload, dict):
82+
raise_invalid_hosted_user_response()
83+
user_payload = payload.get("user")
84+
if not isinstance(user_payload, dict):
85+
raise_invalid_hosted_user_response()
86+
return user_payload, bool(payload.get("created"))

weblate_web/crm/templates/payments/customer_detail.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ <h2>Actions</h2>
5252
</label>
5353
<input type="submit" value="Review merge">
5454
</form>
55+
<form method="post">
56+
<p>{% translate "Add user to customer" %}</p>
57+
{% csrf_token %}
58+
{{ customer_user_form }}
59+
<input type="submit" name="add_customer_user" value="{% translate "Add user" %}">
60+
</form>
5561

5662
<h2>Services</h2>
5763
{% include "weblate_web/service_list_content.html" with object_list=services %}

0 commit comments

Comments
 (0)