1
1
from __future__ import annotations
2
2
3
- from .. import crypto , db
3
+ from .. import crypto , db , config
4
4
from ..db import query
5
5
from ..web import app
6
6
from .exc import NoSuchUser , BadPermission
@@ -28,9 +28,10 @@ def __init__(self, row=None, *, id=None, session_id=None, autovivify=True, touch
28
28
"""
29
29
Constructs a user from a pre-retrieved row *or* a session id or user primary key value.
30
30
31
- autovivify - if True and we are given a session_id that doesn't exist, create a default user
32
- row and use it to populate the object. This is the default behaviour. If False and the
33
- session_id doesn't exist then a NoSuchUser is raised if the session id doesn't exist.
31
+ autovivify - if True and we are given a session_id that doesn't exist, either consider
32
+ importing from a pre-blinding user (if needed) or create a default user row and use it to
33
+ populate the object. This is the default behaviour. If False and the session_id doesn't
34
+ exist then a NoSuchUser is raised if the session id doesn't exist.
34
35
35
36
touch - if True (default is False) then update the last_activity time of this user before
36
37
returning it.
@@ -56,11 +57,15 @@ def _refresh(self, *, row=None, id=None, session_id=None, autovivify=True):
56
57
row = query ("SELECT * FROM users WHERE session_id = :s" , s = session_id ).first ()
57
58
58
59
if not row and autovivify :
59
- with db .transaction ():
60
- query ("INSERT INTO users (session_id) VALUES (:s)" , s = session_id )
61
- row = query ("SELECT * FROM users WHERE session_id = :s" , s = session_id ).first ()
62
- # No need to re-touch this user since we just created them:
63
- self ._touched = True
60
+ if config .REQUIRE_BLIND_KEYS :
61
+ row = self ._import_blinded (session_id )
62
+
63
+ if not row :
64
+ row = db .insert_and_get_row (
65
+ "INSERT INTO users (session_id) VALUES (:s)" , "users" , "id" , s = session_id
66
+ )
67
+ # No need to re-touch this user since we just created them:
68
+ self ._touched = True
64
69
65
70
elif id is not None :
66
71
row = query ("SELECT * FROM users WHERE id = :u" , u = id ).fetchone ()
@@ -75,6 +80,64 @@ def _refresh(self, *, row=None, id=None, session_id=None, autovivify=True):
75
80
bool (row [c ]) for c in ('banned' , 'moderator' , 'admin' , 'visible_mod' )
76
81
)
77
82
83
+ def _import_blinded (self , session_id ):
84
+ """
85
+ Attempts to import the user and permission rows from an unblinded session_id to a new,
86
+ blinded session_id row.
87
+
88
+ Any permissions/bans are *moved* from the old, unblinded id to the new blinded user record.
89
+ """
90
+
91
+ if not session_id .startswith ('15' ):
92
+ return
93
+ blind_abs = crypto .blinded_abs (session_id .lower ())
94
+ with db .transaction ():
95
+ to_import = query (
96
+ """
97
+ SELECT * FROM users WHERE id = (
98
+ SELECT "user" FROM needs_blinding WHERE blinded_abs = :ba
99
+ )
100
+ """ ,
101
+ ba = blind_abs ,
102
+ ).fetchone ()
103
+
104
+ if to_import is None :
105
+ return False
106
+
107
+ row = db .insert_and_get_row (
108
+ """
109
+ INSERT INTO users
110
+ (session_id, created, last_active, banned, moderator, admin, visible_mod)
111
+ VALUES (:sid, :cr, :la, :ban, :mod, :admin, :vis)
112
+ """ ,
113
+ "users" ,
114
+ "id" ,
115
+ sid = session_id ,
116
+ cr = to_import ["created" ],
117
+ la = to_import ["last_active" ],
118
+ ban = to_import ["banned" ],
119
+ mod = to_import ["moderator" ],
120
+ admin = to_import ["admin" ],
121
+ vis = to_import ["visible_mod" ],
122
+ )
123
+ # If we have any global ban/admin/mod then clear them (because we've just set up the
124
+ # global ban/mod/admin permissions for the blinded id in the query above).
125
+ query (
126
+ "UPDATE users SET banned = FALSE, admin = FALSE, moderator = FALSE WHERE id = :u" ,
127
+ u = to_import ["id" ],
128
+ )
129
+
130
+ for t in ("user_permission_overrides" , "user_permission_futures" , "user_ban_futures" ):
131
+ query (
132
+ f'UPDATE { t } SET "user" = :new WHERE "user" = :old' ,
133
+ new = row ["id" ],
134
+ old = to_import ["id" ],
135
+ )
136
+
137
+ query ('DELETE FROM needs_blinding WHERE "user" = :u' , u = to_import ["id" ])
138
+
139
+ return row
140
+
78
141
def __str__ (self ):
79
142
"""Returns string representation of a user: U[050123…cdef], the id prefixed with @ or % if
80
143
the user is a global admin or moderator, respectively."""
@@ -248,13 +311,6 @@ def system_user(self):
248
311
created for internal database tasks"""
249
312
return self .session_id [0 :2 ] == "ff" and self .session_id [2 :] == crypto .server_pubkey_hex
250
313
251
- @property
252
- def derived_key (self ):
253
- """get the derived key for this user"""
254
- if self .session_id [0 :2 ] == '15' :
255
- return self .session_id
256
- return crypto .compute_derived_id (self .session_id )
257
-
258
314
259
315
class SystemUser (User ):
260
316
"""
0 commit comments