Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
Tryton
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
External wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Model registry
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Tryton
Tryton
Commits
92d47106
Commit
92d47106
authored
3 years ago
by
Cédric Krier
Browse files
Options
Downloads
Patches
Plain Diff
Add ip_address and device_cookie login method options
issue10987 review360491008
parent
0fc174ad
No related branches found
No related tags found
1 merge request
!114
Return an empty list when the treeview has no selection
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
CHANGELOG
+1
-0
1 addition, 0 deletions
CHANGELOG
doc/topics/configuration.rst
+24
-2
24 additions, 2 deletions
doc/topics/configuration.rst
trytond/res/user.py
+42
-0
42 additions, 0 deletions
trytond/res/user.py
trytond/tests/test_user.py
+62
-0
62 additions, 0 deletions
trytond/tests/test_user.py
with
129 additions
and
2 deletions
CHANGELOG
+
1
−
0
View file @
92d47106
* Add ip_address and device_cookie login method options
* Add support for Python 3.10
* Remove support for Python 3.6
* Add creatable attribute on tree and form views
...
...
This diff is collapsed.
Click to expand it.
doc/topics/configuration.rst
+
24
−
2
View file @
92d47106
...
...
@@ -405,5 +405,15 @@
authentications = password+sms,ldap
Each combined method can have options to skip them if they are met except for
the first method.
They are defined by appending their name to the method name after a question
mark (``?``) and separated by colons (``:``).
Example::
authentications = password+sms?ip_address:device_cookie
By default, Tryton only supports the ``password`` method. This method compares
the password entered by the user against a stored hash of the user's password.
...
...
@@ -408,7 +418,11 @@
By default, Tryton only supports the ``password`` method. This method compares
the password entered by the user against a stored hash of the user's password.
Other modules can define additional authentication methods, please refer to
their documentation for more information.
By default, Tryton supports the ``ip_address`` and ``device_cookie`` options.
The ``ip_address`` compares the client IP address with the known network list
defined in `authentication_ip_network`_.
The ``device_cookie`` checks the client device is a known device of the user.
Other modules can define additional authentication methods and options, please
refer to their documentation for more information.
Default: ``password``
...
...
@@ -412,6 +426,14 @@
Default: ``password``
authentication_ip_network
~~~~~~~~~~~~~~~~~~~~~~~~~
A comma separated list of known IP networks used to check for ``ip_address``
authentication method option.
Default: ``''``
max_age
~~~~~~~
...
...
This diff is collapsed.
Click to expand it.
trytond/res/user.py
+
42
−
0
View file @
92d47106
...
...
@@ -670,6 +670,7 @@
pool
=
Pool
()
LoginAttempt
=
pool
.
get
(
'
res.user.login.attempt
'
)
UserDevice
=
pool
.
get
(
'
res.user.device
'
)
parameters
=
parameters
.
copy
()
count_ip
=
LoginAttempt
.
count_ip
()
if
count_ip
>
config
.
getint
(
...
...
@@ -678,6 +679,7 @@
raise
RateLimitException
()
device_cookie
=
UserDevice
.
get_valid_cookie
(
login
,
parameters
.
get
(
'
device_cookie
'
))
parameters
[
'
device_cookie
'
]
=
device_cookie
count
=
LoginAttempt
.
count
(
login
,
device_cookie
)
if
count
>
config
.
getint
(
'
session
'
,
'
max_attempt
'
,
default
=
5
):
LoginAttempt
.
add
(
login
,
device_cookie
)
...
...
@@ -687,6 +689,15 @@
'
session
'
,
'
authentications
'
,
default
=
'
password
'
).
split
(
'
,
'
):
user_ids
=
set
()
for
method
in
methods
.
split
(
'
+
'
):
if
user_ids
:
try
:
method
,
options
=
method
.
split
(
'
?
'
,
1
)
except
ValueError
:
options
=
[]
else
:
options
=
options
.
split
(
'
:
'
)
if
cls
.
_check_login_options
(
options
,
login
,
parameters
):
continue
try
:
func
=
getattr
(
cls
,
'
_login_%s
'
%
method
)
except
AttributeError
:
...
...
@@ -701,6 +712,37 @@
LoginAttempt
.
add
(
login
,
device_cookie
)
@classmethod
def
_check_login_options
(
cls
,
options
,
login
,
parameters
):
for
option
in
options
:
try
:
func
=
getattr
(
cls
,
'
_check_login_options_%s
'
%
option
)
except
AttributeError
:
logger
.
info
(
"
Missing login option: %s
"
,
option
)
continue
if
func
(
login
,
parameters
):
return
True
else
:
return
False
@classmethod
def
_check_login_options_ip_address
(
cls
,
login
,
parameters
):
context
=
Transaction
().
context
if
context
.
get
(
'
_request
'
)
and
context
[
'
_request
'
].
get
(
'
remote_addr
'
):
ip_address
=
ipaddress
.
ip_address
(
str
(
context
[
'
_request
'
][
'
remote_addr
'
]))
network_list
=
config
.
get
(
'
session
'
,
'
authentication_ip_network
'
)
if
network_list
:
for
network
in
network_list
.
split
(
'
,
'
):
ip_network
=
ipaddress
.
ip_network
(
network
)
if
ip_address
in
ip_network
:
return
True
return
False
@classmethod
def
_check_login_options_device_cookie
(
cls
,
login
,
parameters
):
return
bool
(
parameters
.
get
(
'
device_cookie
'
))
@classmethod
def
_login_password
(
cls
,
login
,
parameters
):
if
'
password
'
not
in
parameters
:
msg
=
gettext
(
'
res.msg_user_password
'
,
login
=
login
)
...
...
This diff is collapsed.
Click to expand it.
trytond/tests/test_user.py
+
62
−
0
View file @
92d47106
...
...
@@ -291,6 +291,68 @@
self
.
assertEqual
(
LoginAttempt
.
count
(
'
user
'
,
None
),
1
)
self
.
assertEqual
(
LoginAttempt
.
count
(
'
user
'
,
cookie
),
0
)
@with_transaction
()
def
test_authentication_option_ip_address
(
self
):
"
Test authentication with ip_address option
"
pool
=
Pool
()
User
=
pool
.
get
(
'
res.user
'
)
user
=
User
(
login
=
'
user
'
)
user
.
save
()
ip_network
=
config
.
get
(
'
session
'
,
'
authentication_ip_network
'
,
default
=
''
)
config
.
set
(
'
session
'
,
'
authentication_ip_network
'
,
'
192.168.0.0/16,127.0.0.0/8
'
)
self
.
addCleanup
(
config
.
set
,
'
session
'
,
'
authentication_ip_network
'
,
ip_network
)
with
patch
.
object
(
User
,
'
_login_always
'
,
create
=
True
)
as
always
,
\
patch
.
object
(
User
,
'
_login_never
'
,
create
=
True
)
as
never
:
always
.
return_value
=
user
.
id
never
.
return_value
=
None
with
set_authentications
(
'
always+never?ip_address
'
):
for
address
,
result
in
[
(
'
192.168.0.1
'
,
user
.
id
),
(
'
172.17.0.1
'
,
None
),
(
'
127.0.0.1
'
,
user
.
id
),
]:
with
self
.
subTest
(
address
=
address
):
with
Transaction
().
set_context
(
_request
=
{
'
remote_addr
'
:
address
,
}):
self
.
assertEqual
(
User
.
get_login
(
'
user
'
,
{}),
result
)
@with_transaction
()
def
test_authentication_option_device_cookie
(
self
):
"
Test authentication with device cookie option
"
pool
=
Pool
()
User
=
pool
.
get
(
'
res.user
'
)
UserDevice
=
pool
.
get
(
'
res.user.device
'
)
user
=
User
(
login
=
'
user
'
)
user
.
save
()
with
Transaction
().
set_user
(
user
.
id
):
cookie
=
UserDevice
.
renew
(
None
)
with
patch
.
object
(
User
,
'
_login_always
'
,
create
=
True
)
as
always
,
\
patch
.
object
(
User
,
'
_login_never
'
,
create
=
True
)
as
never
:
always
.
return_value
=
user
.
id
never
.
return_value
=
None
with
set_authentications
(
'
always+never?device_cookie
'
):
for
value
,
result
in
[
(
cookie
,
user
.
id
),
(
'
not cookie
'
,
None
),
]:
with
self
.
subTest
(
cookie
=
value
):
self
.
assertEqual
(
User
.
get_login
(
'
user
'
,
{
'
device_cookie
'
:
value
}),
result
)
def
suite
():
return
unittest
.
TestLoader
().
loadTestsFromTestCase
(
UserTestCase
)
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
register
or
sign in
to comment