Skip to content

Commit

Permalink
Merge pull request privacyidea#4159 from privacyidea/test_max_token_e…
Browse files Browse the repository at this point in the history
…nroll_multichallenge

check for max token per realm, add tests
  • Loading branch information
plettich authored Nov 25, 2024
2 parents f166d4a + 4752151 commit 4ed9efd
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 79 deletions.
3 changes: 2 additions & 1 deletion privacyidea/api/lib/postpolicy.py
Original file line number Diff line number Diff line change
Expand Up @@ -802,9 +802,10 @@ def multichallenge_enroll_via_validate(request, response):
result = content.get("result")
# Check if the authentication was successful, only then attempt to enroll a new token
if result.get("value") and result.get("authentication") == "ACCEPT":
# Check if another policy restricts the token count
# Check if another policy restricts the token count and exit early if true
try:
check_max_token_user(request=request)
check_max_token_realm(request=request)
except PolicyError as e:
g.audit_object.log({"success": True,
'action_detail': e})
Expand Down
206 changes: 128 additions & 78 deletions tests/test_api_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,64 +50,62 @@
import re
from . import smtpmock, ldap3mock, radiusmock


PWFILE = "tests/testdata/passwords"
HOSTSFILE = "tests/testdata/hosts"
DICT_FILE="tests/testdata/dictionary"
DICT_FILE = "tests/testdata/dictionary"

LDAPDirectory = [{"dn": "cn=alice,ou=example,o=test",
"attributes": {'cn': 'alice',
"sn": "Cooper",
"givenName": "Alice",
'userPassword': 'alicepw',
'oid': "2",
"homeDirectory": "/home/alice",
"email": "[email protected]",
"memberOf": ["cn=admins,o=test", "cn=users,o=test"],
"accountExpires": 131024988000000000,
"objectGUID": '\xef6\x9b\x03\xc0\xe7\xf3B'
'\x9b\xf9\xcajl\rM1',
'mobile': ["1234", "45678"]}},
{"dn": 'cn=bob,ou=example,o=test',
"attributes": {'cn': 'bob',
"sn": "Marley",
"givenName": "Robert",
"email": "[email protected]",
"memberOf": ["cn=users,o=test"],
"mobile": "123456",
"homeDirectory": "/home/bob",
'userPassword': 'bobpwééé',
"accountExpires": 9223372036854775807,
"objectGUID": '\xef6\x9b\x03\xc0\xe7\xf3B'
'\x9b\xf9\xcajl\rMw',
'oid': "3"}},
{"dn": 'cn=manager,ou=example,o=test',
"attributes": {'cn': 'manager',
"givenName": "Corny",
"sn": "keule",
"email": "ck@o",
"memberOf": ["cn=helpdesk,o=test", "cn=users,o=test"],
"mobile": "123354",
'userPassword': 'ldaptest',
"accountExpires": 9223372036854775807,
"objectGUID": '\xef6\x9b\x03\xc0\xe7\xf3B'
'\x9b\xf9\xcajl\rMT',
'oid': "1"}},
{"dn": 'cn=frank,ou=sales,o=test',
"attributes": {'cn': 'frank',
"givenName": "Frank",
"sn": "Hause",
"email": "fh@o",
"memberOf": ["cn=users,o=test"],
"mobile": "123354",
'userPassword': 'ldaptest',
"accountExpires": 9223372036854775807,
"objectGUID": '\xef7\x9b\x03\xc0\xe7\xf3B'
'\x9b\xf9\xcajl\rMT',
'oid': "5"}}
"attributes": {'cn': 'alice',
"sn": "Cooper",
"givenName": "Alice",
'userPassword': 'alicepw',
'oid': "2",
"homeDirectory": "/home/alice",
"email": "[email protected]",
"memberOf": ["cn=admins,o=test", "cn=users,o=test"],
"accountExpires": 131024988000000000,
"objectGUID": '\xef6\x9b\x03\xc0\xe7\xf3B'
'\x9b\xf9\xcajl\rM1',
'mobile': ["1234", "45678"]}},
{"dn": 'cn=bob,ou=example,o=test',
"attributes": {'cn': 'bob',
"sn": "Marley",
"givenName": "Robert",
"email": "[email protected]",
"memberOf": ["cn=users,o=test"],
"mobile": "123456",
"homeDirectory": "/home/bob",
'userPassword': 'bobpwééé',
"accountExpires": 9223372036854775807,
"objectGUID": '\xef6\x9b\x03\xc0\xe7\xf3B'
'\x9b\xf9\xcajl\rMw',
'oid': "3"}},
{"dn": 'cn=manager,ou=example,o=test',
"attributes": {'cn': 'manager',
"givenName": "Corny",
"sn": "keule",
"email": "ck@o",
"memberOf": ["cn=helpdesk,o=test", "cn=users,o=test"],
"mobile": "123354",
'userPassword': 'ldaptest',
"accountExpires": 9223372036854775807,
"objectGUID": '\xef6\x9b\x03\xc0\xe7\xf3B'
'\x9b\xf9\xcajl\rMT',
'oid': "1"}},
{"dn": 'cn=frank,ou=sales,o=test',
"attributes": {'cn': 'frank',
"givenName": "Frank",
"sn": "Hause",
"email": "fh@o",
"memberOf": ["cn=users,o=test"],
"mobile": "123354",
'userPassword': 'ldaptest',
"accountExpires": 9223372036854775807,
"objectGUID": '\xef7\x9b\x03\xc0\xe7\xf3B'
'\x9b\xf9\xcajl\rMT',
'oid': "5"}}
]


OTPs = ["755224",
"287082",
"359152",
Expand All @@ -130,6 +128,7 @@ class AuthorizationPolicyTestCase(MyApiTestCase):
Testcase for issue
https://github.com/privacyidea/privacyidea/issues/543
"""

@ldap3mock.activate
def test_00_create_realm(self):
ldap3mock.setLDAPDirectory(LDAPDirectory)
Expand All @@ -140,11 +139,11 @@ def test_00_create_realm(self):
'LOGINNAMEATTRIBUTE': 'cn',
'LDAPSEARCHFILTER': '(cn=*)',
'USERINFO': '{ "username": "cn",'
'"phone" : "telephoneNumber", '
'"mobile" : "mobile"'
', "email" : "mail", '
'"surname" : "sn", '
'"givenname" : "givenName" }',
'"phone" : "telephoneNumber", '
'"mobile" : "mobile"'
', "email" : "mail", '
'"surname" : "sn", '
'"givenname" : "givenName" }',
'UIDTYPE': 'DN',
"resolver": "catchall",
"type": "ldapresolver"}
Expand Down Expand Up @@ -482,15 +481,15 @@ def test_00_create_realms(self):

def test_01_validate_offline(self):
# create offline app
#tokenobj = get_tokens(self.serials[0])[0]
# tokenobj = get_tokens(self.serials[0])[0]
mr_obj = save_machine_resolver({"name": "testresolver",
"type": "hosts",
"filename": HOSTSFILE,
"type.filename": "string",
"desc.filename": "the filename with the "
"hosts",
"pw": "secret",
"type.pw": "password"})
"type": "hosts",
"filename": HOSTSFILE,
"type.filename": "string",
"desc.filename": "the filename with the "
"hosts",
"pw": "secret",
"type.pw": "password"})
self.assertTrue(mr_obj > 0)
# Attach the offline app to pippin
r = attach_token(self.serials[0], "offline", hostname="pippin",
Expand Down Expand Up @@ -1041,9 +1040,9 @@ def test_10_saml_check(self):
"[email protected]")
self.assertEqual(attributes.get("givenname"), "Cornelius")
self.assertEqual(attributes.get("mobile"), "+491111111")
self.assertEqual(attributes.get("phone"), "+491234566")
self.assertEqual(attributes.get("realm"), "realm1")
self.assertEqual(attributes.get("username"), "cornelius")
self.assertEqual(attributes.get("phone"), "+491234566")
self.assertEqual(attributes.get("realm"), "realm1")
self.assertEqual(attributes.get("username"), "cornelius")

# Return SAML attributes On Fail
with self.app.test_request_context('/validate/samlcheck',
Expand Down Expand Up @@ -2016,7 +2015,7 @@ def test_23_pass_no_user_and_pass_no_token(self):
# Creating a notification event. The non-existing user must
# still be able to pass!
eid = set_event("notify", event=["validate_check"], action="sendmail",
handlermodule="UserNotification", conditions={"token_locked": True})
handlermodule="UserNotification", conditions={"token_locked": True})

with self.app.test_request_context('/validate/check',
method='POST',
Expand Down Expand Up @@ -2797,8 +2796,9 @@ def test_32_secondary_login_attribute(self):
json_response = res.json
self.assertTrue(json_response.get("result").get("status"), res)
self.assertEqual(json_response.get("result").get("value").get("count"), 1)
self.assertTrue("logged in as Cooper." in json_response.get("result").get("value").get("auditdata")[0].get("info"),
json_response.get("result").get("value").get("auditdata"))
self.assertTrue(
"logged in as Cooper." in json_response.get("result").get("value").get("auditdata")[0].get("info"),
json_response.get("result").get("value").get("auditdata"))

self.assertTrue(delete_realm("tr"))
self.assertTrue(delete_resolver("myLDAPres"))
Expand Down Expand Up @@ -3377,7 +3377,6 @@ def test_02_application_specific_password_token(self):


class WebAuthn(MyApiTestCase):

username = "selfservice"
pin = "webauthnpin"
serial = "WAN0001D434"
Expand Down Expand Up @@ -3698,7 +3697,6 @@ def test_20_authenticate_with_token(self):


class MultiChallege(MyApiTestCase):

serial = "hotp1"

"""
Expand Down Expand Up @@ -4163,7 +4161,6 @@ def test_01_push_challenge_tags(self):


class AChallengeResponse(MyApiTestCase):

serial = "hotp1"
serial_email = "email1"
serial_sms = "sms1"
Expand Down Expand Up @@ -4235,7 +4232,8 @@ def test_01_challenge_response_token_deactivate(self):
self.assertFalse(data.get("result").get("value"))
self.assertEqual(data.get("result").get("authentication"), "REJECT")
detail = data.get("detail")
self.assertEqual(detail.get("message"), "Challenge matches, but token is not fit for challenge. Token is disabled")
self.assertEqual(detail.get("message"),
"Challenge matches, but token is not fit for challenge. Token is disabled")

# The token is still disabled. We are checking, if we can do a challenge response
# for a disabled token
Expand Down Expand Up @@ -5917,7 +5915,6 @@ def test_03_fail_to_enroll_EMail(self):
toks = get_tokens(user=User("alice", "ldaprealm"), tokentype="email")
self.assertEqual(0, len(toks))


@ldap3mock.activate
@responses.activate
def test_04_enroll_SMS(self):
Expand Down Expand Up @@ -6085,9 +6082,63 @@ def test_05_enroll_EMail_3rdparty_validator(self):
toks = get_tokens(user=User("alice", "ldaprealm"), tokentype="email")
self.assertEqual(0, len(toks))

def test_06_max_token_policy(self):
"""
There are 5 policies that can limit the number of tokens a user can have, which will deny
enroll_via_multichallenge. 4 policies are checked via check_max_token_user:
- max_token_per_user
- max_active_token_per_user
- {type}_max_token_per_user
- {type_max_active_token_per_user
And 1 via check_max_token_realm:
- max_token_per_realm
Test that each of these functions correctly deny the enrollment of a token.
"""
self.setUp_user_realms()
user = User("hans", "realm1")
spass = "12"
token1 = init_token({"type": "spass", "pin": spass}, user)
self.assertTrue(token1.get_serial())
set_policy("enroll_via_multichallenge", scope=SCOPE.AUTH,
action=f"{ACTION.ENROLL_VIA_MULTICHALLENGE}=hotp")

set_policy("max_token_per_user", scope=SCOPE.ENROLL, action=f"{ACTION.MAXTOKENUSER}=1")
self._authenticate_no_token_enrolled(user, spass)
delete_policy("max_token_per_user")

class ValidateShortPasswordTestCase(MyApiTestCase):
set_policy("max_active_token_per_user", scope=SCOPE.ENROLL, action=f"{ACTION.MAXACTIVETOKENUSER}=1")
self._authenticate_no_token_enrolled(user, spass)
delete_policy("max_active_token_per_user")

set_policy("hotp_max_token_per_user", scope=SCOPE.ENROLL, action=f"{ACTION.MAXTOKENUSER}=0")
self._authenticate_no_token_enrolled(user, spass)
delete_policy("hotp_max_token_per_user")

set_policy("max_token_per_realm", scope=SCOPE.ENROLL, action=f"{ACTION.MAXTOKENREALM}=1")
self._authenticate_no_token_enrolled(user, spass)
delete_policy("max_token_per_realm")

delete_policy("enroll_via_multichallenge")
remove_token(token1.get_serial())

def _authenticate_no_token_enrolled(self, user: User, otp):
with self.app.test_request_context('/validate/check',
method='POST',
data={"user": user.login, "realm": user.realm, "pass": otp}):
res = self.app.full_dispatch_request()
self.assertTrue(res.status_code == 200, res)
data = res.json
self.assertIn("result", data)
self.assertTrue(data.get("result").get("status"))
self.assertTrue(data.get("result").get("value"))
self.assertEqual(data.get("result").get("authentication"), "ACCEPT")
self.assertIn("detail", data)
detail = data.get("detail")
self.assertNotIn("transaction_id", detail)
self.assertNotIn("multi_challenge", detail)


class ValidateShortPasswordTestCase(MyApiTestCase):
yubi_otpkey = "9163508031b20d2fbb1868954e041729"

public_uid = "ecebeeejedecebeg"
Expand Down Expand Up @@ -6154,7 +6205,6 @@ def test_00_setup_tokens(self):


class WebAuthnOfflineTestCase(MyApiTestCase):

"""
This Testcase simulates the enrollment and full authentication with a WebAuthn token.
"""
Expand Down

0 comments on commit 4ed9efd

Please sign in to comment.