"Fossies" - the Fresh Open Source Software Archive

Member "LinOTP-release-2.10.5.2/linotpd/src/linotp/tests/functional/test_policy2/test_randompin.py" (13 May 2019, 19440 Bytes) of package /linux/misc/LinOTP-release-2.10.5.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 # -*- coding: utf-8 -*-
    2 #
    3 #    LinOTP - the open source solution for two factor authentication
    4 #    Copyright (C) 2010 - 2019 KeyIdentity GmbH
    5 #
    6 #    This file is part of LinOTP server.
    7 #
    8 #    This program is free software: you can redistribute it and/or
    9 #    modify it under the terms of the GNU Affero General Public
   10 #    License, version 3, as published by the Free Software Foundation.
   11 #
   12 #    This program is distributed in the hope that it will be useful,
   13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
   14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   15 #    GNU Affero General Public License for more details.
   16 #
   17 #    You should have received a copy of the
   18 #               GNU Affero General Public License
   19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
   20 #
   21 #
   22 #    E-mail: linotp@keyidentity.com
   23 #    Contact: www.linotp.org
   24 #    Support: www.keyidentity.com
   25 #
   26 
   27 
   28 """
   29 Test the otp_pin_random policy
   30 """
   31 
   32 from collections import deque
   33 from copy import deepcopy
   34 
   35 from linotp.tests import TestController
   36 
   37 import logging
   38 log = logging.getLogger(__name__)
   39 
   40 
   41 
   42 
   43 class TestRandompinController(TestController):
   44     """
   45     Test the otp_pin_random policy
   46     """
   47 
   48     # Don't mutate this data in test functions because it will be shared by all
   49     # tests. Instead copy it and then use it.
   50     tokens = [
   51         {
   52             'key': '3132333435363738393031323334353637383930',
   53             'type': 'hmac',
   54             'serial': None,
   55             'otplen': 6,
   56             'otps': deque(['755224', '287082', '359152', '969429', '338314',
   57                            '254676', '287922', '162583', '399871', '520489']),
   58             }
   59         ]
   60     # set up in setUp
   61     policies_for_deletion = None
   62     token_for_deletion = None
   63 
   64     def setUp(self):
   65         TestController.setUp(self)
   66         self.create_common_resolvers()
   67         self.create_common_realms()
   68         self.token_for_deletion = set()
   69         self.policies_for_deletion = set()
   70 
   71         return
   72 
   73     def tearDown(self):
   74         # Delete policies
   75         for policy in self.policies_for_deletion:
   76             self.delete_policy(policy)
   77         # Delete token
   78         for token in self.token_for_deletion:
   79             self.delete_token(token)
   80 
   81         self.delete_all_realms()
   82         self.delete_all_resolvers()
   83         TestController.tearDown(self)
   84         return
   85 
   86     def test_simple_enroll(self):
   87         """
   88         After normal enroll just OTP is enough. With otp_pin_random policy not.
   89 
   90         After a normal enroll you can authenticate successfully with only OTP
   91         (because no PIN is set). If otp_pin_random is set this is no longer the
   92         case (because PIN has been set to an unknown value).
   93         """
   94         # Enroll token
   95         user = u'aἰσχύλος'  # realm myDefRealm
   96         token = deepcopy(self.tokens[0])
   97         self._enroll_token(token, user=user)
   98 
   99         # Login with only OTP succeeds
  100         self._validate(
  101             user,
  102             token['otps'].popleft(),
  103             )
  104 
  105         self._create_randompin_policy('myDefRealm')
  106 
  107         # Enroll new token
  108         token2 = deepcopy(self.tokens[0])
  109         self._enroll_token(token2, user=user)
  110 
  111         # Login with only OTP fails
  112         self._validate(
  113             user,
  114             token2['otps'].popleft(),
  115             expected='value-false'
  116             )
  117         return
  118 
  119     def test_simple_assign(self):
  120         """
  121         Same as 'test_simple_enroll' but with assign after enroll
  122 
  123         Verify the behaviour is the same if the token is first enrolled and
  124         then assigned to a user, instead of directly enrolling for the user
  125         as in test_simple_enroll.
  126         """
  127         # Enroll token
  128         user = u'aἰσχύλος'  # realm myDefRealm
  129         token = deepcopy(self.tokens[0])
  130         self._enroll_token(token)
  131 
  132         # Login with only OTP succeeds
  133         self._validate_check_s(
  134             token['serial'],
  135             token['otps'].popleft(),
  136             )
  137 
  138         self._assign(token['serial'], user)
  139 
  140         # Login with only OTP succeeds
  141         self._validate(
  142             user,
  143             token['otps'].popleft(),
  144             )
  145 
  146         self._create_randompin_policy('myDefRealm')
  147 
  148         # Enroll token
  149         user = u'aἰσχύλος'  # realm myDefRealm
  150         token2 = deepcopy(self.tokens[0])
  151         self._enroll_token(token2)
  152 
  153         # Login with only OTP fails (PIN unknown)
  154         self._validate_check_s(
  155             token2['serial'],
  156             token2['otps'].popleft(),
  157             expected='value-false'
  158             )
  159 
  160         self._assign(token2['serial'], user)
  161 
  162         # Login with only OTP fails (PIN unknown)
  163         self._validate(
  164             user,
  165             token2['otps'].popleft(),
  166             expected='value-false'
  167             )
  168         return
  169 
  170     def test_selfservice(self):
  171         """
  172         User logs into selfservice and sets PIN then authenticates with PIN+OTP
  173 
  174         After enrolling the PIN is unknown and the token can't be used. The
  175         user can log into the selfservice and set a PIN for his token. Then
  176         he can authenticate with PIN+OTP.
  177 
  178         This test will fail with WebTest 1.2.1 (Debian Squeeze) because of a
  179         bug that caused cookies to be quoted twice. The bug is fixed in 1.2.2.
  180         https://github.com/Pylons/webtest/
  181                             commit/8471db1c2dc505c633bca2d39d5713dba0c51a42
  182         """
  183 
  184         self._create_randompin_policy('myDefRealm')
  185         self._create_selfservice_policy('myDefRealm')
  186 
  187         # Enroll token
  188         user = u'aἰσχύλος'  # realm myDefRealm
  189         token = deepcopy(self.tokens[0])
  190         self._enroll_token(token, user=user)
  191 
  192         # Login with only OTP fails (because PIN is unknown)
  193         self._validate(
  194             user,
  195             token['otps'].popleft(),
  196             expected='value-false'
  197             )
  198 
  199         # User logs into selfservice and sets PIN
  200         pwd = u'Πέρσαι'
  201         pin = 'mytokenpin'
  202         self._set_pin_in_selfservice(user, pwd, token['serial'], pin)
  203 
  204         # authenticate successfully with PIN+OTP
  205         self._validate(
  206             user,
  207             pin + token['otps'].popleft(),
  208             )
  209         return
  210 
  211     def test_admin_setpin(self):
  212         """
  213         Admin can set the PIN, even after the user has set it in selfservice
  214 
  215         This test will fail with WebTest 1.2.1 (Debian Squeeze) because of a
  216         bug that caused cookies to be quoted twice. The bug is fixed in 1.2.2.
  217         https://github.com/Pylons/webtest/
  218                                 commit/8471db1c2dc505c633bca2d39d5713dba0c51a42
  219         """
  220 
  221         self._create_randompin_policy('myDefRealm')
  222         self._create_selfservice_policy('myDefRealm')
  223 
  224         # Enroll token
  225         user = u'aἰσχύλος'  # realm myDefRealm
  226         token = deepcopy(self.tokens[0])
  227         self._enroll_token(token, user=user)
  228 
  229         # Login with only OTP fails (because PIN is unknown)
  230         self._validate(
  231             user,
  232             token['otps'].popleft(),
  233             expected='value-false'
  234             )
  235 
  236         # Admin sets PIN
  237         self._set_pin(token['serial'], 'admin-set-pin')
  238         # authenticate successfully with PIN+OTP
  239         self._validate(
  240             user,
  241             'admin-set-pin' + token['otps'].popleft(),
  242             )
  243 
  244         # User logs into selfservice and sets PIN
  245         pwd = u'Πέρσαι'
  246         pin = 'mytokenpin'
  247         self._set_pin_in_selfservice(user, pwd, token['serial'], pin)
  248         # authenticate successfully with PIN+OTP
  249         self._validate(
  250             user,
  251             pin + token['otps'].popleft(),
  252             )
  253 
  254         # Admin sets PIN again
  255         self._set_pin(token['serial'], 'second-admin-set-pin')
  256         # authenticate successfully with PIN+OTP
  257         self._validate(
  258             user,
  259             'second-admin-set-pin' + token['otps'].popleft(),
  260             )
  261         return
  262 
  263     def test_assign_other_user(self):
  264         """
  265         Verify PIN is overwritten when assigning token to a different user
  266 
  267         Test both the case where the user is in the same realm (where the
  268         policy is defined) and in another realm without opt_pin_random policy.
  269 
  270         This test will fail with WebTest 1.2.1 (Debian Squeeze) because of a
  271         bug that caused cookies to be quoted twice. The bug is fixed in 1.2.2.
  272         https://github.com/Pylons/webtest/
  273                                 commit/8471db1c2dc505c633bca2d39d5713dba0c51a42
  274         """
  275         self._create_randompin_policy('myDefRealm')
  276         self._create_selfservice_policy('myDefRealm')
  277 
  278         # Enroll token
  279         user = u'aἰσχύλος'  # realm myDefRealm
  280         token = deepcopy(self.tokens[0])
  281         self._enroll_token(token, user=user)
  282 
  283         # Login with only OTP fails (because PIN is unknown)
  284         self._validate(
  285             user,
  286             token['otps'].popleft(),
  287             expected='value-false'
  288             )
  289 
  290         # User logs into selfservice and sets PIN
  291         pwd = u'Πέρσαι'
  292         pin = 'mytokenpin'
  293         self._set_pin_in_selfservice(user, pwd, token['serial'], pin)
  294         # authenticate successfully with PIN+OTP
  295         self._validate(
  296             user,
  297             pin + token['otps'].popleft(),
  298             )
  299 
  300         # Assign token to new user
  301         new_user = 'beckett'
  302         self._assign(token['serial'], new_user)
  303 
  304         # authenticate fails because old PIN is no longer valid (i.e. was
  305         # overwritten with a random value during assignment)
  306         self._validate(
  307             new_user,
  308             pin + token['otps'].popleft(),
  309             expected='value-false',
  310             )
  311 
  312         # Admin sets the PIN
  313         self._set_pin(token['serial'], 'admin-set-pin')
  314 
  315         # Now assign the token to a user in a realm without otp_pin_random
  316         # policy
  317         user3 = 'shakespeare@mymixrealm'
  318         self._assign(token['serial'], user3)
  319 
  320         # authenticate succeeds because PIN is NOT overwritten (in a real
  321         # scenario it is assumed the new user does not know the PIN of the
  322         # previous one)
  323         self._validate(
  324             user3,
  325             'admin-set-pin' + token['otps'].popleft(),
  326             )
  327         return
  328 
  329     def test_randompin_with_autoassignment(self):
  330         """
  331         Enroll with randompin and then autoassign token -> PIN is user password
  332         """
  333         self._create_randompin_policy('myDefRealm')
  334 
  335         token = deepcopy(self.tokens[0])
  336         self._enroll_token(token)
  337 
  338         # Login with only OTP fails (because PIN is unknown)
  339         self._validate_check_s(
  340             token['serial'],
  341             token['otps'].popleft(),
  342             expected='value-false'
  343             )
  344 
  345         # Create autoassignment policy
  346         self._create_autoassignment_policy('myDefRealm')
  347         # Set token realm for autoassignment to work
  348         self._set_token_realm(token['serial'], 'myDefRealm')
  349 
  350         # autoassign the token
  351         user = u'aἰσχύλος'
  352         pwd = u'Πέρσαι'
  353         self._validate(
  354             user,
  355             pwd + token['otps'].popleft(),
  356             )
  357 
  358         # The user password is set as PIN
  359         for _ in range(3):
  360             self._validate(
  361                 user,
  362                 pwd + token['otps'].popleft(),
  363                 )
  364         return
  365 
  366     # -------- Private helper methods ----- --
  367     def _create_randompin_policy(self, realm):
  368         """
  369         Creates an otp_pin_random policy for 'realm'. Schedules the policy for
  370         deletion on tearDown.
  371         """
  372         policy_name = 'randompin'
  373         params = {
  374             'name': policy_name,
  375             'scope': 'enrollment',
  376             'action': 'otp_pin_random=12',
  377             'realm': realm,
  378             }
  379         self.create_policy(params)
  380         self.policies_for_deletion.add(policy_name)
  381         return
  382 
  383     def _create_selfservice_policy(self, realm):
  384         """
  385         Creates a selfservice policy for 'realm'. Schedules the policy for
  386         deletion on tearDown.
  387         """
  388         policy_name = 'selfservice'
  389         params = {
  390             'name': policy_name,
  391             'scope': 'selfservice',
  392             'action': 'setOTPPIN',
  393             'realm': realm,
  394             }
  395         self.create_policy(params)
  396         self.policies_for_deletion.add(policy_name)
  397         return
  398 
  399     def _create_autoassignment_policy(self, realm):
  400         """
  401         Creates an autoassignment policy for 'realm'. Schedules the policy for
  402         deletion on tearDown.
  403         """
  404         policy_name = 'autoassignment'
  405         params = {
  406             'name': policy_name,
  407             'scope': 'enrollment',
  408             'action': 'autoassignment',
  409             'realm': realm,
  410             }
  411         self.create_policy(params)
  412         self.policies_for_deletion.add(policy_name)
  413         return
  414 
  415     def _enroll_token(self, token, user=None):
  416         """
  417         Enroll token for 'user'.
  418 
  419         :param token: A dictionary with token information. This dictionary is
  420             augmented with 'serial' after enrolling the token.
  421         :param user: The name of the user to assign the token to. If None then
  422             the token is not assigned.
  423         """
  424         # enroll token
  425         params = {
  426             "otpkey": token['key'],
  427             "type": token['type'],
  428             "otplen": token['otplen'],
  429             }
  430         if user:
  431             params['user'] = user.encode('utf-8')
  432         response = self.make_admin_request('init', params=params)
  433         content = TestController.get_json_body(response)
  434         self.assertTrue(content['result']['status'])
  435         self.assertTrue(content['result']['value'])
  436         token['serial'] = content['detail']['serial']
  437         self.token_for_deletion.add(token['serial'])
  438         return
  439 
  440     def _validate(self, user, pwd, expected='success', err_msg=None):
  441         """
  442         runs a validate/check request and verifies the response is as 'expected'
  443 
  444         :param user: Username or username@realm
  445         :param pwd: Password (e.g. PIN+OTP)
  446         :param expected: One of 'success', 'value-false', 'status-false' or
  447                         'both-false'
  448         :param err_msg: An error message to display if assert fails
  449         :return: The content (JSON object)
  450         """
  451         params = {
  452             'user': user.encode('utf-8'),
  453             'pass': pwd.encode('utf-8')
  454             }
  455         return self._validate_base(
  456             params,
  457             action='check',
  458             expected=expected,
  459             err_msg=err_msg,
  460             )
  461 
  462     def _validate_check_s(self, serial, pwd, expected='success', err_msg=None):
  463         """
  464         Makes a validate/check_s request and verifies the response is as
  465         'expected'
  466 
  467         :param serial: Token serial
  468         :param pwd: Password (e.g. PIN+OTP)
  469         :param expected: One of 'success', 'value-false', 'status-false' or
  470             'both-false'
  471         :param err_msg: An error message to display if assert fails
  472         :return: The content (JSON object)
  473         """
  474         params = {
  475             'serial': serial,
  476             'pass': pwd.encode('utf-8')
  477             }
  478         return self._validate_base(
  479             params,
  480             action='check_s',
  481             expected=expected,
  482             err_msg=err_msg,
  483             )
  484 
  485     def _validate_base(self, params, action='check', expected='success',
  486                        err_msg=None):
  487         """
  488         Base method for /validate/<action> requests
  489 
  490         Don't call this method directly but use _validate() or
  491         _validate_check_s() instead.
  492 
  493         :param params: Request parameters
  494         :param expected: One of 'success', 'value-false', 'status-false' or
  495             'both-false'
  496         :param err_msg: An error message to display if the assert fails
  497         :return: The content (JSON object)
  498         """
  499         response = self.make_validate_request(action, params=params)
  500         content = TestController.get_json_body(response)
  501         if not err_msg:
  502             err_msg = "validate/%s failed for %r. Response: %r" % (
  503                 action,
  504                 params,
  505                 content
  506                 )
  507         if expected == 'success':
  508             self.assertTrue(content['result']['status'], err_msg)
  509             self.assertTrue(content['result']['value'], err_msg)
  510         elif expected == 'value-false':
  511             self.assertTrue(content['result']['status'], err_msg)
  512             self.assertFalse(content['result']['value'], err_msg)
  513         elif expected == 'status-false':
  514             self.assertFalse(content['result']['status'], err_msg)
  515             self.assertTrue(content['result']['value'], err_msg)
  516         elif expected == 'both-false':
  517             self.assertFalse(content['result']['status'], err_msg)
  518             self.assertFalse(content['result']['value'], err_msg)
  519         else:
  520             self.fail("Unknown 'expected' %s" % expected)
  521         return content
  522 
  523     def _assign(self, serial, user):
  524         """
  525         Assign token defined by 'serial' to 'user'
  526 
  527         :param serial: Token serial number
  528         :param user: User (e.g. username@realm)
  529         :return: None
  530         """
  531         params = {
  532             'serial': serial,
  533             'user': user.encode('utf-8'),
  534             }
  535         response = self.make_admin_request('assign', params=params)
  536         content = TestController.get_json_body(response)
  537         self.assertTrue(content['result']['status'])
  538         self.assertTrue(content['result']['value'])
  539         return
  540 
  541     def _set_pin_in_selfservice(self, user, pwd, serial, pin):
  542         """
  543         Log into selfservice and set PIN
  544 
  545         :param user: username or username@realm
  546         :param pwd: User password
  547         :param serial: Token serial
  548         :param pin: The PIN to be set
  549         """
  550         params = {
  551             'serial': serial,
  552             'userpin': pin,
  553             }
  554         login = user.encode('utf-8')
  555         password = pwd.encode('utf-8')
  556         response = self.make_userservice_request('setpin', params,
  557                                                  auth_user=(login, password))
  558 
  559         content = TestController.get_json_body(response)
  560         self.assertTrue(content['result']['status'])
  561         expected = {
  562             "set userpin": 1
  563             }
  564         self.assertDictEqual(expected, content['result']['value'])
  565         return
  566 
  567     def _set_pin(self, serial, pin):
  568         """
  569         Set the token PIN 'pin' for the token identified by 'serial'
  570         """
  571         params = {
  572             'serial': serial,
  573             'pin': pin,
  574             }
  575         response = self.make_admin_request('set', params=params)
  576         content = TestController.get_json_body(response)
  577         self.assertTrue(content['result']['status'])
  578         self.assertTrue(content['result']['value'])
  579         return
  580 
  581     def _set_token_realm(self, serial, realm):
  582         """
  583         Set the token realm 'realm' for the token identified by 'serial'
  584         """
  585         assert serial and realm, "Both 'serial' and 'realm' required"
  586         params = {
  587             'serial': serial,
  588             'realms': realm,
  589         }
  590         response = self.make_admin_request('tokenrealm', params=params)
  591         content = TestController.get_json_body(response)
  592         self.assertTrue(content['result']['status'])
  593         self.assertEqual(1, content['result']['value'])
  594         return