Skip to content

Commit 6523e74

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

9 files changed

Lines changed: 713 additions & 24 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: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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+
32+
33+
class HostedUserEnsureError(RuntimeError):
34+
"""Hosted user provisioning failed."""
35+
36+
37+
def raise_invalid_hosted_user_response() -> None:
38+
raise HostedUserEnsureError(gettext("Invalid hosted user response"))
39+
40+
41+
def ensure_hosted_user(email: str, full_name: str) -> tuple[dict[str, Any], bool]:
42+
if not settings.PAYMENT_SECRET:
43+
raise HostedUserEnsureError(gettext("Hosted user synchronization is disabled."))
44+
45+
try:
46+
response = requests.post(
47+
settings.HOSTED_USER_CREATE_API,
48+
data={
49+
"payload": dumps(
50+
{"email": email, "full_name": full_name},
51+
key=settings.PAYMENT_SECRET,
52+
salt=USER_ENSURE_SALT,
53+
)
54+
},
55+
timeout=60,
56+
)
57+
response.raise_for_status()
58+
except requests.RequestException as error:
59+
raise HostedUserEnsureError(str(error)) from error
60+
61+
try:
62+
response_payload = response.json()
63+
except ValueError as error:
64+
raise HostedUserEnsureError(gettext("Invalid hosted user response")) from error
65+
if not isinstance(response_payload, dict):
66+
raise_invalid_hosted_user_response()
67+
signed_payload = response_payload.get("payload")
68+
if not isinstance(signed_payload, str):
69+
raise_invalid_hosted_user_response()
70+
71+
try:
72+
payload = loads(
73+
signed_payload,
74+
key=settings.PAYMENT_SECRET,
75+
max_age=300,
76+
salt=USER_ENSURE_RESPONSE_SALT,
77+
)
78+
except (BadSignature, SignatureExpired) as error:
79+
raise HostedUserEnsureError(gettext("Invalid hosted user response")) from error
80+
if not isinstance(payload, dict):
81+
raise_invalid_hosted_user_response()
82+
user_payload = payload.get("user")
83+
if not isinstance(user_payload, dict):
84+
raise_invalid_hosted_user_response()
85+
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)