Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
Odevzdávací Systém MO
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Deploy
Model registry
Analyze
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
MO-P
Odevzdávací Systém MO
Commits
7f3b1b67
Project 'mj/mo-submit' was moved to 'mo-p/osmo'. Please update any links and bookmarks that may still have the old path.
Commit
7f3b1b67
authored
8 months ago
by
Martin Mareš
Browse files
Options
Downloads
Patches
Plain Diff
Mail: DSN tokeny posíláme i v registraci
parent
11eae74c
No related branches found
No related tags found
No related merge requests found
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
mo/email.py
+52
-27
52 additions, 27 deletions
mo/email.py
mo/web/acct.py
+4
-2
4 additions, 2 deletions
mo/web/acct.py
with
56 additions
and
29 deletions
mo/email.py
+
52
−
27
View file @
7f3b1b67
...
@@ -29,7 +29,6 @@ def send_email(send_to: str, full_name: str, subject: str, body: str, dsn_token:
...
@@ -29,7 +29,6 @@ def send_email(send_to: str, full_name: str, subject: str, body: str, dsn_token:
env_from
=
mail_from
env_from
=
mail_from
if
dsn_token
is
not
None
:
if
dsn_token
is
not
None
:
logger
.
info
(
f
'
Mail: DSN token
{
dsn_token
}
'
)
env_from
=
env_from
.
replace
(
'
@
'
,
f
'
+
{
dsn_token
}
@
'
)
env_from
=
env_from
.
replace
(
'
@
'
,
f
'
+
{
dsn_token
}
@
'
)
msg
=
email
.
message
.
EmailMessage
()
msg
=
email
.
message
.
EmailMessage
()
...
@@ -79,22 +78,25 @@ def send_email(send_to: str, full_name: str, subject: str, body: str, dsn_token:
...
@@ -79,22 +78,25 @@ def send_email(send_to: str, full_name: str, subject: str, body: str, dsn_token:
return
True
return
True
def
dsn_token_signature
(
raw_token
:
str
,
user
:
db
.
Use
r
,
secret
:
str
)
->
str
:
def
dsn_token_signature
(
raw_token
:
str
,
email
:
st
r
,
secret
:
str
)
->
str
:
body
=
f
'
{
raw_token
}
-
{
user
.
email
}
'
body
=
f
'
{
raw_token
}
-
{
email
}
'
raw_sig
=
hmac
.
digest
(
secret
.
encode
(
'
utf-8
'
),
body
.
encode
(
'
utf-8
'
),
'
sha1
'
)[:
5
]
raw_sig
=
hmac
.
digest
(
secret
.
encode
(
'
utf-8
'
),
body
.
encode
(
'
utf-8
'
),
'
sha1
'
)[:
5
]
return
base64
.
b32encode
(
raw_sig
).
decode
(
'
utf-8
'
).
lower
()
return
base64
.
b32encode
(
raw_sig
).
decode
(
'
utf-8
'
).
lower
()
def
gen_dsn_token
(
user
:
db
.
User
)
->
Optional
[
str
]:
def
gen_dsn_token
(
user
:
db
.
User
,
rr
:
Optional
[
db
.
RegRequest
]
=
None
)
->
Optional
[
str
]:
# E-maily posílané na adresy našich uživatelů mají obálkového odesilatele
# E-maily posílané na adresy našich uživatelů mají obálkového odesilatele
# parametrizovaného tokenem, který používáme při párování Delivery Status
# parametrizovaného tokenem, který používáme při párování Delivery Status
# Notifications s účtem.
# Notifications s účtem.
#
#
# Tokeny obsahují user_id, čas a kryptografický podpis (HMAC), který
# Podobné tokeny mají e-maily z registrace (nové účty, resety hesla, změny
# adres). Ty jsou vázané na reg_requests v DB.
#
# Tokeny obsahují user_id nebo reg_id, čas a kryptografický podpis (HMAC), který
# podepisuje i e-mailovou adresu účtu, abychom se nenechali zmást ani falešnou
# podepisuje i e-mailovou adresu účtu, abychom se nenechali zmást ani falešnou
# nedoručenkou, ani opožděnou nedoručenkou, zatímco si uživatel změnil adresu.
# nedoručenkou, ani opožděnou nedoručenkou, zatímco si uživatel změnil adresu.
#
#
# Formát tokenu: <user_id>-<unix_timestamp>-<podpis>
# Formát tokenu:
(
<user_id>
|r<reg_id>)
-<unix_timestamp>-<podpis>
secret
=
getattr
(
config
,
'
MAIL_TOKEN_SECRET
'
,
None
)
secret
=
getattr
(
config
,
'
MAIL_TOKEN_SECRET
'
,
None
)
if
secret
is
None
:
if
secret
is
None
:
...
@@ -102,22 +104,30 @@ def gen_dsn_token(user: db.User) -> Optional[str]:
...
@@ -102,22 +104,30 @@ def gen_dsn_token(user: db.User) -> Optional[str]:
now
=
int
(
mo
.
now
.
timestamp
())
now
=
int
(
mo
.
now
.
timestamp
())
assert
now
>
0
assert
now
>
0
raw_token
=
f
'
{
user
.
user_id
}
-
{
now
}
'
sig
=
dsn_token_signature
(
raw_token
,
user
,
secret
)
if
rr
is
not
None
:
ident
=
f
'
r
{
rr
.
reg_id
}
'
email
=
rr
.
email
else
:
ident
=
str
(
user
.
user_id
)
email
=
user
.
email
raw_token
=
f
'
{
ident
}
-
{
now
}
'
sig
=
dsn_token_signature
(
raw_token
,
email
,
secret
)
return
f
'
{
raw_token
}
-
{
sig
}
'
return
f
'
{
raw_token
}
-
{
sig
}
'
def
validate_dsn_token
(
token
:
str
)
->
Tuple
[
db
.
User
,
datetime
]:
def
validate_dsn_token
(
token
:
str
)
->
Tuple
[
Optional
[
db
.
User
],
Optional
[
db
.
RegRequest
]
,
datetime
]:
secret
=
getattr
(
config
,
'
MAIL_TOKEN_SECRET
'
,
None
)
secret
=
getattr
(
config
,
'
MAIL_TOKEN_SECRET
'
,
None
)
if
secret
is
None
:
if
secret
is
None
:
raise
ValueError
(
"
MAIL_TOKEN_SECRET nenastaven
"
)
raise
ValueError
(
"
MAIL_TOKEN_SECRET nenastaven
"
)
fields
=
token
.
split
(
'
-
'
)
fields
=
token
.
split
(
'
-
'
)
if
(
len
(
fields
)
!=
3
or
if
(
len
(
fields
)
!=
3
or
not
re
.
match
(
r
'
[1-9]\d{0,9}
'
,
fields
[
0
])
or
not
re
.
match
(
r
'
r?
[1-9]\d{0,9}
'
,
fields
[
0
])
or
not
re
.
match
(
r
'
[1-9]\d{0,9}
'
,
fields
[
1
])):
not
re
.
match
(
r
'
[1-9]\d{0,9}
'
,
fields
[
1
])):
raise
ValueError
(
"
Chybná syntaxe
"
)
raise
ValueError
(
"
Chybná syntaxe
"
)
user_id
,
timestamp
,
given_sig
=
int
(
fields
[
0
]
)
,
int
(
fields
[
1
]),
fields
[
2
]
ident
,
timestamp
,
given_sig
=
fields
[
0
],
int
(
fields
[
1
]),
fields
[
2
]
when
=
datetime
.
fromtimestamp
(
timestamp
).
astimezone
(
dateutil
.
tz
.
UTC
)
when
=
datetime
.
fromtimestamp
(
timestamp
).
astimezone
(
dateutil
.
tz
.
UTC
)
age
=
mo
.
now
-
when
age
=
mo
.
now
-
when
...
@@ -127,19 +137,35 @@ def validate_dsn_token(token: str) -> Tuple[db.User, datetime]:
...
@@ -127,19 +137,35 @@ def validate_dsn_token(token: str) -> Tuple[db.User, datetime]:
raise
ValueError
(
"
Token z budoucnosti
"
)
raise
ValueError
(
"
Token z budoucnosti
"
)
sess
=
db
.
get_session
()
sess
=
db
.
get_session
()
if
ident
.
startswith
(
'
r
'
):
reg_id
=
int
(
ident
[
1
:])
user
=
None
rr
=
sess
.
query
(
db
.
RegRequest
).
get
(
reg_id
)
if
rr
is
None
:
raise
ValueError
(
"
Registrace neexistuje
"
)
email
=
rr
.
email
else
:
user_id
=
int
(
ident
)
user
=
sess
.
query
(
db
.
User
).
get
(
user_id
)
user
=
sess
.
query
(
db
.
User
).
get
(
user_id
)
if
user
is
None
:
if
user
is
None
:
raise
ValueError
(
"
Uživatel neexistuje
"
)
raise
ValueError
(
"
Uživatel neexistuje
"
)
rr
=
None
email
=
user
.
email
raw_token
=
f
'
{
user_id
}
-
{
timestamp
}
'
raw_token
=
f
'
{
ident
}
-
{
timestamp
}
'
correct_sig
=
dsn_token_signature
(
raw_token
,
user
,
secret
)
correct_sig
=
dsn_token_signature
(
raw_token
,
email
,
secret
)
if
given_sig
!=
correct_sig
:
if
given_sig
!=
correct_sig
:
raise
ValueError
(
"
Nesouhlasí podpis
"
)
raise
ValueError
(
"
Nesouhlasí podpis
"
)
return
user
,
when
return
user
,
rr
,
when
def
send_user_email
(
user
:
db
.
User
,
subject
:
str
,
body
:
str
,
add_footer
:
bool
=
False
,
override_email
:
Optional
[
str
]
=
None
,
send_dsn_token
:
bool
=
True
)
->
bool
:
def
send_user_email
(
user
:
db
.
User
,
subject
:
str
,
body
:
str
,
add_footer
:
bool
=
False
,
override_email
:
Optional
[
str
]
=
None
,
rr
:
Optional
[
db
.
RegRequest
]
=
None
)
->
bool
:
if
override_email
:
if
override_email
:
email
=
override_email
email
=
override_email
elif
user
.
user_id
==
0
:
elif
user
.
user_id
==
0
:
...
@@ -149,12 +175,11 @@ def send_user_email(user: db.User, subject: str, body: str, add_footer: bool = F
...
@@ -149,12 +175,11 @@ def send_user_email(user: db.User, subject: str, body: str, add_footer: bool = F
else
:
else
:
email
=
user
.
email
email
=
user
.
email
logger
.
info
(
f
'
Mail:
"
{
subject
}
"
->
{
email
}
(#
{
user
.
user_id
}
)
'
)
dsn_token
=
gen_dsn_token
(
user
,
rr
)
if
send_dsn_token
:
user_suffix
=
f
'
(#
{
user
.
user_id
}
)
'
if
user
.
user_id
is
not
None
else
""
dsn_token
=
gen_dsn_token
(
user
)
dsn_suffix
=
f
'
, DSN token
{
dsn_token
}
'
if
dsn_token
else
""
else
:
logger
.
info
(
f
'
Mail:
"
{
subject
}
"
->
{
email
}{
user_suffix
}{
dsn_suffix
}
'
)
dsn_token
=
None
if
add_footer
:
if
add_footer
:
body
+=
"
\n
"
+
(
"
=
"
*
76
)
+
"
\n
"
body
+=
"
\n
"
+
(
"
=
"
*
76
)
+
"
\n
"
...
@@ -213,7 +238,7 @@ def send_password_reset_email(user: db.User, token: str) -> bool:
...
@@ -213,7 +238,7 @@ def send_password_reset_email(user: db.User, token: str) -> bool:
'''
.
format
(
confirm_url
(
'
p
'
,
token
))))
'''
.
format
(
confirm_url
(
'
p
'
,
token
))))
def
send_confirm_create_email
(
user
:
db
.
User
,
token
:
st
r
)
->
bool
:
def
send_confirm_create_email
(
user
:
db
.
User
,
rr
:
db
.
RegReque
st
)
->
bool
:
return
send_user_email
(
user
,
'
Založení účtu
'
,
textwrap
.
dedent
(
'''
\
return
send_user_email
(
user
,
'
Založení účtu
'
,
textwrap
.
dedent
(
'''
\
Někdo požádal o založení účtu s touto e-mailovou adresou v Odevzdávacím
Někdo požádal o založení účtu s touto e-mailovou adresou v Odevzdávacím
systému Matematické olympiády.
systému Matematické olympiády.
...
@@ -223,10 +248,10 @@ def send_confirm_create_email(user: db.User, token: str) -> bool:
...
@@ -223,10 +248,10 @@ def send_confirm_create_email(user: db.User, token: str) -> bool:
{}
{}
Váš OSMO
Váš OSMO
'''
.
format
(
confirm_url
(
'
r
'
,
token
))),
send_dsn_token
=
False
)
'''
.
format
(
confirm_url
(
'
r
'
,
rr
.
email_
token
))),
rr
=
rr
)
def
send_confirm_change_email
(
user
:
db
.
User
,
token
:
str
,
new_email
:
st
r
)
->
bool
:
def
send_confirm_change_email
(
user
:
db
.
User
,
rr
:
db
.
RegReque
st
)
->
bool
:
return
send_user_email
(
user
,
'
Změna e-mailové adresy
'
,
textwrap
.
dedent
(
'''
\
return
send_user_email
(
user
,
'
Změna e-mailové adresy
'
,
textwrap
.
dedent
(
'''
\
Někdo požádal o nastavení e-mailové adresy k účtu v Odevzdávacím
Někdo požádal o nastavení e-mailové adresy k účtu v Odevzdávacím
systému Matematické olympiády na tuto adresu.
systému Matematické olympiády na tuto adresu.
...
@@ -236,7 +261,7 @@ def send_confirm_change_email(user: db.User, token: str, new_email: str) -> bool
...
@@ -236,7 +261,7 @@ def send_confirm_change_email(user: db.User, token: str, new_email: str) -> bool
{}
{}
Váš OSMO
Váš OSMO
'''
.
format
(
confirm_url
(
'
e
'
,
token
))),
override_email
=
new_
email
)
'''
.
format
(
confirm_url
(
'
e
'
,
rr
.
email_
token
))),
override_email
=
rr
.
email
,
rr
=
rr
)
def
send_join_notify_email
(
dest
:
db
.
User
,
who
:
db
.
User
,
contest
:
db
.
Contest
)
->
bool
:
def
send_join_notify_email
(
dest
:
db
.
User
,
who
:
db
.
User
,
contest
:
db
.
Contest
)
->
bool
:
...
...
This diff is collapsed.
Click to expand it.
mo/web/acct.py
+
4
−
2
View file @
7f3b1b67
...
@@ -208,7 +208,7 @@ def user_settings_personal():
...
@@ -208,7 +208,7 @@ def user_settings_personal():
sess
.
commit
()
sess
.
commit
()
app
.
logger
.
info
(
f
'
Settings: Požadavek na změnu e-mailu uživatele #
{
user
.
user_id
}
'
)
app
.
logger
.
info
(
f
'
Settings: Požadavek na změnu e-mailu uživatele #
{
user
.
user_id
}
'
)
flash
(
'
Odeslán e-mail s odkazem na potvrzení nové adresy.
'
,
'
success
'
)
flash
(
'
Odeslán e-mail s odkazem na potvrzení nové adresy.
'
,
'
success
'
)
mo
.
email
.
send_confirm_change_email
(
user
,
rr
.
email_token
,
new_email
=
rr
.
email
)
mo
.
email
.
send_confirm_change_email
(
user
,
rr
)
else
:
else
:
app
.
logger
.
info
(
'
Settings: Rate limit
'
)
app
.
logger
.
info
(
'
Settings: Rate limit
'
)
flash
(
'
Příliš mnoho požadavků na změny e-mailu. Počkejte prosím chvíli a zkuste to znovu.
'
,
'
danger
'
)
flash
(
'
Příliš mnoho požadavků na změny e-mailu. Počkejte prosím chvíli a zkuste to znovu.
'
,
'
danger
'
)
...
@@ -278,6 +278,7 @@ class Reg1:
...
@@ -278,6 +278,7 @@ class Reg1:
create_time
:
datetime
.
datetime
create_time
:
datetime
.
datetime
seed
:
str
seed
:
str
status
:
RegStatus
status
:
RegStatus
rr
:
db
.
RegRequest
email_token
:
str
email_token
:
str
x
:
int
x
:
int
y
:
int
y
:
int
...
@@ -361,6 +362,7 @@ class Reg1:
...
@@ -361,6 +362,7 @@ class Reg1:
app
.
logger
.
info
(
'
Reg1: Rate limit
'
)
app
.
logger
.
info
(
'
Reg1: Rate limit
'
)
return
False
return
False
self
.
rr
=
rr
self
.
email_token
=
rr
.
email_token
self
.
email_token
=
rr
.
email_token
rr
.
email
=
email
rr
.
email
=
email
rr
.
captcha_token
=
self
.
seed
rr
.
captcha_token
=
self
.
seed
...
@@ -406,7 +408,7 @@ def create_acct():
...
@@ -406,7 +408,7 @@ def create_acct():
app
.
logger
.
debug
(
f
'
Reg1: E-mailový token
{
reg1
.
email_token
}
'
)
app
.
logger
.
debug
(
f
'
Reg1: E-mailový token
{
reg1
.
email_token
}
'
)
flash
(
'
Odeslán e-mail s odkazem na založení účtu.
'
,
'
success
'
)
flash
(
'
Odeslán e-mail s odkazem na založení účtu.
'
,
'
success
'
)
user
=
db
.
User
(
email
=
form
.
email
.
data
,
first_name
=
'
Nový
'
,
last_name
=
'
Uživatel
'
)
user
=
db
.
User
(
email
=
form
.
email
.
data
,
first_name
=
'
Nový
'
,
last_name
=
'
Uživatel
'
)
mo
.
email
.
send_confirm_create_email
(
user
,
reg1
.
email_token
)
mo
.
email
.
send_confirm_create_email
(
user
,
reg1
.
rr
)
return
redirect
(
url_for
(
'
confirm_reg
'
))
return
redirect
(
url_for
(
'
confirm_reg
'
))
form
.
captcha
.
description
=
reg1
.
captcha_task
()
form
.
captcha
.
description
=
reg1
.
captcha_task
()
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
sign in
to comment