Skip to content

Commit 7bdc4db

Browse files
committed
Make it possible for an administrator so send activation links to users upon their creation (#4425)
1 parent f685b2c commit 7bdc4db

File tree

13 files changed

+211
-179
lines changed

13 files changed

+211
-179
lines changed

backend/globaleaks/handlers/admin/operation.py

+18-13
Original file line numberDiff line numberDiff line change
@@ -214,13 +214,14 @@ def set_user_password(session, tid, user_session, user_id, password):
214214
return db_set_user_password(session, tid, user_session, user_id, password)
215215

216216

217-
def set_tmp_key(user_session, user, token):
218-
crypto_escrow_prv_key = GCE.asymmetric_decrypt(user_session.cc, Base64Encoder.decode(user_session.ek))
217+
def set_tmp_key(user_session, user, token, user_cc=''):
218+
if not user_cc:
219+
crypto_escrow_prv_key = GCE.asymmetric_decrypt(user_session.cc, Base64Encoder.decode(user_session.ek))
219220

220-
if user_session.user_tid == 1:
221-
user_cc = GCE.asymmetric_decrypt(crypto_escrow_prv_key, Base64Encoder.decode(user.crypto_escrow_bkp1_key))
222-
else:
223-
user_cc = GCE.asymmetric_decrypt(crypto_escrow_prv_key, Base64Encoder.decode(user.crypto_escrow_bkp2_key))
221+
if user_session.user_tid == 1:
222+
user_cc = GCE.asymmetric_decrypt(crypto_escrow_prv_key, Base64Encoder.decode(user.crypto_escrow_bkp1_key))
223+
else:
224+
user_cc = GCE.asymmetric_decrypt(crypto_escrow_prv_key, Base64Encoder.decode(user.crypto_escrow_bkp2_key))
224225

225226
key = Base64Encoder.decode(GCE.derive_key(token, user.salt).encode())
226227
key = Base64Encoder.encode(GCE.symmetric_encrypt(key, user_cc))
@@ -233,16 +234,20 @@ def set_tmp_key(user_session, user, token):
233234
pass
234235

235236

236-
@transact
237-
def generate_password_reset_token(session, tid, user_session, user_id):
237+
def db_admin_generate_password_reset_token(session, tid, user_session, user_id, user_cc=''):
238238
user = session.query(User).filter(User.tid == tid, User.id == user_id).one_or_none()
239239
if user is None:
240240
return
241241

242242
token = db_generate_password_reset_token(session, user)
243243

244-
if user_session.ek and user.crypto_pub_key:
245-
set_tmp_key(user_session, user, token)
244+
if user.crypto_pub_key and (user_cc or user_session.ek):
245+
set_tmp_key(user_session, user, token, user_cc)
246+
247+
248+
@transact
249+
def send_password_reset_token(session, tid, user_session, user_id, user_cc=''):
250+
db_admin_generate_password_reset_token(session, tid, user_session, user_id, user_cc)
246251

247252
db_log(session, tid=tid, type='send_password_reset_email', user_id=user_session.user_id, object_id=user_id)
248253

@@ -285,9 +290,9 @@ def send_password_reset_email(self, req_args, *args, **kwargs):
285290
if self.session.user_id == req_args['value']:
286291
raise errors.ForbiddenOperation
287292

288-
return generate_password_reset_token(self.request.tid,
289-
self.session,
290-
req_args['value'])
293+
return send_password_reset_token(self.request.tid,
294+
self.session,
295+
req_args['value'])
291296

292297
@inlineCallbacks
293298
def reset_onion_private_key(self, req_args, *args, **kargs):

backend/globaleaks/handlers/admin/tenant.py

+136-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
# -*- coding: UTF-8
2+
from nacl.encoding import Base64Encoder
3+
24
from globaleaks import models
35
from globaleaks.db.appdata import load_appdata, db_load_defaults
6+
from globaleaks.handlers.admin.context import db_create_context
7+
from globaleaks.handlers.admin.node import db_update_enabled_languages
8+
from globaleaks.handlers.admin.user import db_create_user
49
from globaleaks.handlers.base import BaseHandler
5-
from globaleaks.handlers.wizard import db_wizard
6-
from globaleaks.models import config, serializers
10+
from globaleaks.models import config, profiles, serializers
711
from globaleaks.models.config import db_get_configs, \
812
db_get_config_variable, db_set_config_variable
913
from globaleaks.orm import db_del, db_get, transact, tw
1014
from globaleaks.rest import errors, requests
15+
from globaleaks.utils.crypto import GCE
16+
from globaleaks.utils.log import log
17+
from globaleaks.utils.sock import isIPAddress
1118
from globaleaks.utils.tls import gen_selfsigned_certificate
1219

1320

@@ -109,6 +116,133 @@ def get(session, tid):
109116
return serializers.serialize_tenant(session, db_get(session, models.Tenant, models.Tenant.id == tid))
110117

111118

119+
def db_wizard(session, tid, hostname, request):
120+
"""
121+
Transaction for the handling of wizard request
122+
123+
:param session: An ORM session
124+
:param tid: A tenant ID
125+
:param hostname: The hostname to be configured
126+
:param request: A user request
127+
"""
128+
admin_password = receiver_password = ''
129+
130+
language = request['node_language']
131+
132+
root_tenant_node = config.ConfigFactory(session, 1)
133+
134+
if tid == 1:
135+
node = root_tenant_node
136+
encryption = True
137+
escrow = request['admin_escrow']
138+
else:
139+
node = config.ConfigFactory(session, tid)
140+
encryption = root_tenant_node.get_val('encryption')
141+
escrow = root_tenant_node.get_val('crypto_escrow_pub_key') != ''
142+
143+
if node.get_val('wizard_done'):
144+
log.err("DANGER: Wizard already initialized!", tid=tid)
145+
raise errors.ForbiddenOperation
146+
147+
db_update_enabled_languages(session, tid, [language], language)
148+
149+
node.set_val('encryption', encryption)
150+
151+
node.set_val('name', request['node_name'])
152+
node.set_val('default_language', language)
153+
node.set_val('wizard_done', True)
154+
node.set_val('enable_developers_exception_notification', request['enable_developers_exception_notification'])
155+
156+
if tid == 1 and not isIPAddress(hostname):
157+
node.set_val('hostname', hostname)
158+
159+
profiles.load_profile(session, tid, request['profile'])
160+
161+
if encryption and escrow:
162+
crypto_escrow_prv_key, crypto_escrow_pub_key = GCE.generate_keypair()
163+
164+
node.set_val('crypto_escrow_pub_key', crypto_escrow_pub_key)
165+
166+
if tid != 1 and root_tenant_node.get_val('crypto_escrow_pub_key'):
167+
node.set_val('crypto_escrow_prv_key', Base64Encoder.encode(GCE.asymmetric_encrypt(root_tenant_node.get_val('crypto_escrow_pub_key'), crypto_escrow_prv_key)))
168+
169+
if not request['skip_admin_account_creation']:
170+
admin_desc = models.User().dict(language)
171+
admin_desc['username'] = request['admin_username']
172+
admin_desc['name'] = request['admin_name']
173+
admin_desc['password'] = request['admin_password']
174+
admin_desc['mail_address'] = request['admin_mail_address']
175+
admin_desc['language'] = language
176+
admin_desc['role'] = 'admin'
177+
admin_desc['pgp_key_remove'] = False
178+
admin_user = db_create_user(session, tid, None, admin_desc, language)
179+
admin_user.password_change_needed = (tid != 1)
180+
181+
if encryption and escrow:
182+
node.set_val('crypto_escrow_pub_key', crypto_escrow_pub_key)
183+
admin_user.crypto_escrow_prv_key = Base64Encoder.encode(GCE.asymmetric_encrypt(admin_user.crypto_pub_key, crypto_escrow_prv_key))
184+
185+
if not request['skip_recipient_account_creation']:
186+
receiver_desc = models.User().dict(language)
187+
receiver_desc['username'] = request['receiver_username']
188+
receiver_desc['password'] = request['receiver_password']
189+
receiver_desc['name'] = request['receiver_name']
190+
receiver_desc['mail_address'] = request['receiver_mail_address']
191+
receiver_desc['language'] = language
192+
receiver_desc['role'] = 'receiver'
193+
receiver_desc['pgp_key_remove'] = False
194+
receiver_user = db_create_user(session, tid, None, receiver_desc, language)
195+
receiver_user.password_change_needed = (tid != 1)
196+
197+
context_desc = models.Context().dict(language)
198+
context_desc['name'] = 'Default'
199+
context_desc['status'] = 'enabled'
200+
201+
if not request['skip_recipient_account_creation']:
202+
context_desc['receivers'] = [receiver_user.id]
203+
204+
context = db_create_context(session, tid, None, context_desc, language)
205+
206+
# Root tenants initialization terminates here
207+
208+
if tid == 1:
209+
return
210+
211+
# Secondary tenants initialization starts here
212+
subdomain = node.get_val('subdomain')
213+
rootdomain = root_tenant_node.get_val('rootdomain')
214+
if subdomain and rootdomain:
215+
node.set_val('hostname', subdomain + "." + rootdomain)
216+
217+
mode = node.get_val('mode')
218+
219+
if mode != 'default':
220+
node.set_val('tor', False)
221+
222+
if mode in ['wbpa']:
223+
node.set_val('simplified_login', True)
224+
225+
for varname in ['anonymize_outgoing_connections',
226+
'password_change_period',
227+
'default_questionnaire']:
228+
node.set_val(varname, root_tenant_node.get_val(varname))
229+
230+
context.questionnaire_id = root_tenant_node.get_val('default_questionnaire')
231+
232+
# Set data retention policy to 12 months
233+
context.tip_timetolive = 365
234+
235+
if not request['skip_recipient_account_creation']:
236+
receiver_user.can_edit_general_settings = True
237+
238+
# Set the recipient name equal to the node name
239+
receiver_user.name = receiver_user.public_name = request['node_name']
240+
241+
@transact
242+
def wizard(session, tid, hostname, request):
243+
return db_wizard(session, tid, hostname, request)
244+
245+
112246
@transact
113247
def update(session, tid, request):
114248
root_tenant_config = config.ConfigFactory(session, 1)

backend/globaleaks/handlers/admin/user.py

+21-6
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
from twisted.internet.defer import inlineCallbacks
33

44
from globaleaks import models
5+
from globaleaks.handlers.admin.operation import set_tmp_key
56
from globaleaks.handlers.base import BaseHandler
67
from globaleaks.handlers.user import parse_pgp_options, \
78
user_serialize_user
9+
from globaleaks.handlers.user.reset_password import db_generate_password_reset_token
810
from globaleaks.models import fill_localized_keys
911
from globaleaks.orm import db_del, db_get, db_log, transact, tw
1012
from globaleaks.rest import errors, requests
@@ -25,6 +27,10 @@ def db_create_user(session, tid, user_session, request, language):
2527
:param language: The language of the request
2628
:return: The serialized descriptor of the created object
2729
"""
30+
config = models.config.ConfigFactory(session, tid)
31+
32+
encryption = config.get_val('encryption')
33+
2834
request['tid'] = tid
2935

3036
fill_localized_keys(request, models.User.localized_keys, language)
@@ -41,7 +47,7 @@ def db_create_user(session, tid, user_session, request, language):
4147
if existing_user:
4248
raise errors.DuplicateUserError
4349

44-
salt = models.config.ConfigFactory(session, tid).get_val('receipt_salt')
50+
salt = config.get_val('receipt_salt')
4551
user.salt = GCE.generate_salt(salt + ":" + user.username)
4652

4753
user.language = request['language']
@@ -68,16 +74,25 @@ def db_create_user(session, tid, user_session, request, language):
6874
if user_session:
6975
db_log(session, tid=tid, type='create_user', user_id=user_session.user_id, object_id=user.id)
7076

77+
if request.get('send_activation_link', False):
78+
token = db_generate_password_reset_token(session, user)
79+
else:
80+
token = None
81+
7182
crypto_escrow_pub_key_tenant_1 = models.config.ConfigFactory(session, 1).get_val('crypto_escrow_pub_key')
72-
crypto_escrow_pub_key_tenant_n = models.config.ConfigFactory(session, tid).get_val('crypto_escrow_pub_key')
83+
crypto_escrow_pub_key_tenant_n = config.get_val('crypto_escrow_pub_key')
84+
85+
if encryption and crypto_escrow_pub_key_tenant_1 or crypto_escrow_pub_key_tenant_n:
86+
cc, user.crypto_pub_key = GCE.generate_keypair()
87+
user.crypto_prv_key = Base64Encoder.encode(GCE.symmetric_encrypt(key, cc))
88+
user.crypto_bkp_key, user.crypto_rec_key = GCE.generate_recovery_key(cc)
89+
90+
if user_session and token:
91+
set_tmp_key(user_session, user, token, cc)
7392

7493
if not crypto_escrow_pub_key_tenant_1 and not crypto_escrow_pub_key_tenant_n:
7594
return user
7695

77-
cc, user.crypto_pub_key = GCE.generate_keypair()
78-
user.crypto_prv_key = Base64Encoder.encode(GCE.symmetric_encrypt(key, cc))
79-
user.crypto_bkp_key, user.crypto_rec_key = GCE.generate_recovery_key(cc)
80-
8196
if crypto_escrow_pub_key_tenant_1:
8297
user.crypto_escrow_bkp1_key = Base64Encoder.encode(GCE.asymmetric_encrypt(crypto_escrow_pub_key_tenant_1, cc))
8398

backend/globaleaks/handlers/signup.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
from globaleaks.db import sync_refresh_tenant_cache
66
from globaleaks.handlers.admin.node import db_admin_serialize_node
77
from globaleaks.handlers.admin.notification import db_get_notification
8-
from globaleaks.handlers.admin.tenant import db_create as db_create_tenant
8+
from globaleaks.handlers.admin.tenant import db_create as db_create_tenant, db_wizard
99
from globaleaks.handlers.admin.user import db_get_users
1010
from globaleaks.handlers.base import BaseHandler
11-
from globaleaks.handlers.wizard import db_wizard
1211
from globaleaks.models import serializers
1312
from globaleaks.models.config import ConfigFactory
1413
from globaleaks.orm import db_del, transact

backend/globaleaks/handlers/user/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ def user_serialize_user(session, user, language):
8484
'can_edit_general_settings': user.can_edit_general_settings,
8585
'clicked_recovery_key': user.clicked_recovery_key,
8686
'accepted_privacy_policy': user.accepted_privacy_policy,
87-
'contexts': contexts
87+
'contexts': contexts,
88+
'send_activation_link': False
8889

8990
}
9091

0 commit comments

Comments
 (0)