privacyidea / privacyidea

@@ -442,16 +442,15 @@
Loading
442 442
        :param transactionid: the id of this challenge
443 443
        :param options: the request context parameters / data
444 444
        :type options: dict
445 -
        :return: tuple of (bool, message, transactionid, attributes)
445 +
        :return: tuple of (bool, message, transactionid, reply_dict)
446 446
        :rtype: tuple
447 447
448 448
        The return tuple builds up like this:
449 449
        ``bool`` if submit was successful;
450 450
        ``message`` which is displayed in the JSON response;
451 -
        additional ``attributes``, which are displayed in the JSON response.
451 +
        additional challenge ``reply_dict``, which are displayed in the JSON challenges response.
452 452
        """
453 453
        options = options or {}
454 -
        attributes = {}
455 454
        message = ""
456 455
        if type(options.get("data")) == dict:
457 456
            # In the special first chal-resp case we do not have jsonified data, yet. So we need to convert
@@ -478,8 +477,8 @@
Loading
478 477
        db_challenge.save()
479 478
        expiry_date = datetime.datetime.now() + \
480 479
                      datetime.timedelta(seconds=validity)
481 -
        attributes['valid_until'] = "{0!s}".format(expiry_date)
482 -
        return True, message, db_challenge.transaction_id, attributes
480 +
        reply_dict = {'attributes': {'valid_until': "{0!s}".format(expiry_date)}}
481 +
        return True, message, db_challenge.transaction_id, reply_dict
483 482
484 483
    def is_challenge_request(self, passw, user=None, options=None):
485 484
        """

@@ -311,8 +311,7 @@
Loading
311 311
                 bool, if submit was successful
312 312
                 message is submitted to the user
313 313
                 data is preserved in the challenge
314 -
                 attributes - additional attributes, which are displayed in the
315 -
                    output
314 +
                 reply_dict - additional reply_dict, which is added to the response
316 315
        """
317 316
        success = False
318 317
        options = options or {}
@@ -320,7 +319,7 @@
Loading
320 319
                                                        "{0!s}_{1!s}".format(self.get_class_type(),
321 320
                                                                             ACTION.CHALLENGETEXT),
322 321
                                                        options) or _("Enter the OTP from the SMS:")
323 -
        attributes = {'state': transactionid}
322 +
        reply_dict = {'attributes': {'state': transactionid}}
324 323
        validity = self._get_sms_timeout()
325 324
326 325
        if self.is_active() is True:
@@ -359,9 +358,9 @@
Loading
359 358
360 359
        expiry_date = datetime.datetime.now() + \
361 360
                                    datetime.timedelta(seconds=validity)
362 -
        attributes['valid_until'] = "{0!s}".format(expiry_date)
361 +
        reply_dict['attributes']['valid_until'] = "{0!s}".format(expiry_date)
363 362
364 -
        return success, return_message, transactionid, attributes
363 +
        return success, return_message, transactionid, reply_dict
365 364
366 365
    @log_with(log)
367 366
    @check_token_locked

@@ -259,7 +259,7 @@
Loading
259 259
                * success: if submit was successful
260 260
                * message: the text submitted to the user
261 261
                * transactionid: the given or generated transactionid
262 -
                * attributes: additional attributes, which are displayed in the output
262 +
                * reply_dict: additional dictionary, which is added to the response
263 263
        :rtype: tuple(bool, str, str, dict)
264 264
        """
265 265
        success = False
@@ -268,7 +268,7 @@
Loading
268 268
                                                        "{0!s}_{1!s}".format(self.get_class_type(),
269 269
                                                                             ACTION.CHALLENGETEXT),
270 270
                                                        options) or _("Enter the OTP from the Email:")
271 -
        attributes = {'state': transactionid}
271 +
        reply_dict = {'attributes': {'state': transactionid}}
272 272
        validity = int(get_from_config("email.validtime", 120))
273 273
274 274
        if self.is_active() is True:
@@ -314,9 +314,9 @@
Loading
314 314
315 315
        expiry_date = datetime.datetime.now() + \
316 316
                                    datetime.timedelta(seconds=validity)
317 -
        attributes['valid_until'] = "{0!s}".format(expiry_date)
317 +
        reply_dict['attributes']['valid_until'] = "{0!s}".format(expiry_date)
318 318
319 -
        return success, return_message, transactionid, attributes
319 +
        return success, return_message, transactionid, reply_dict
320 320
321 321
    @log_with(log)
322 322
    @check_token_locked

@@ -2090,17 +2090,17 @@
Loading
2090 2090
    for token_obj in token_list:
2091 2091
        # Check if the max auth is succeeded
2092 2092
        if token_obj.check_all(message_list):
2093 -
            r_chal, message, transaction_id, attributes = \
2093 +
            r_chal, message, transaction_id, challenge_info = \
2094 2094
                token_obj.create_challenge(
2095 2095
                    transactionid=transaction_id, options=options)
2096 2096
            # Add the reply to the response
2097 2097
            message_list.append(message)
2098 2098
            if r_chal:
2099 -
                challenge_info = {}
2099 +
                challenge_info = challenge_info or {}
2100 2100
                challenge_info["transaction_id"] = transaction_id
2101 -
                challenge_info["attributes"] = attributes
2102 2101
                challenge_info["serial"] = token_obj.token.serial
2103 2102
                challenge_info["type"] = token_obj.get_tokentype()
2103 +
                challenge_info["client_mode"] = token_obj.client_mode
2104 2104
                challenge_info["message"] = message
2105 2105
                # If exist, add next pin and next password change
2106 2106
                next_pin = token_obj.get_tokeninfo(
@@ -2116,6 +2116,7 @@
Loading
2116 2116
                    challenge_info["password_change"] = \
2117 2117
                        token_obj.is_pin_change(
2118 2118
                            password=True)
2119 +
                # FIXME: This is deprecated and should be remove one day
2119 2120
                reply_dict.update(challenge_info)
2120 2121
                reply_dict["multi_challenge"].append(challenge_info)
2121 2122
    if message_list:

@@ -220,14 +220,14 @@
Loading
220 220
                 bool, if submit was successful
221 221
                 message is submitted to the user
222 222
                 data is preserved in the challenge
223 -
                 attributes - additional attributes, which are displayed in the
223 +
                 reply_dict - additional attributes, which are displayed in the
224 224
                    output
225 225
        """
226 226
        if options is None:
227 227
            options = {}
228 228
        message = options.get('radius_message') or "Enter your RADIUS tokencode:"
229 229
        state = hexlify_and_unicode(options.get('radius_state') or b'')
230 -
        attributes = {'state': transactionid}
230 +
        reply_dict = {'attributes': {'state': transactionid}}
231 231
        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
232 232
233 233
        db_challenge = Challenge(self.token.serial,
@@ -237,7 +237,7 @@
Loading
237 237
                                 validitytime=validity)
238 238
        db_challenge.save()
239 239
        self.challenge_janitor()
240 -
        return True, message, db_challenge.transaction_id, attributes
240 +
        return True, message, db_challenge.transaction_id, reply_dict
241 241
242 242
    @log_with(log)
243 243
    def is_challenge_response(self, passw, user=None, options=None):

@@ -184,13 +184,13 @@
Loading
184 184
        :param transactionid: the id of this challenge
185 185
        :param options: the request context parameters / data
186 186
        :type options: dict
187 -
        :return: tuple of (bool, message, transactionid, attributes)
187 +
        :return: tuple of (bool, message, transactionid, reply_dict)
188 188
        :rtype: tuple
189 189
190 190
        The return tuple builds up like this:
191 191
        ``bool`` if submit was successful;
192 192
        ``message`` which is displayed in the JSON response;
193 -
        additional ``attributes``, which are displayed in the JSON response.
193 +
        additional challenge ``reply_dict``, which are displayed in the JSON challenges response.
194 194
        """
195 195
        options = options or {}
196 196
        message = 'Please answer the challenge'
@@ -238,8 +238,9 @@
Loading
238 238
        db_challenge.save()
239 239
240 240
        attributes["challenge"] = challenge
241 +
        reply_dict = {"attributes": attributes}
241 242
242 -
        return True, message, db_challenge.transaction_id, attributes
243 +
        return True, message, db_challenge.transaction_id, reply_dict
243 244
244 245
    def verify_response(self, passw=None, challenge=None):
245 246
        """

@@ -1292,6 +1292,19 @@
Loading
1292 1292
        details["threadid"] = threading.current_thread().ident
1293 1293
        res["detail"] = details
1294 1294
1295 +
    # Fix for sending an information about challenge response
1296 +
    # TODO: Make this default, when we move from the binary result->value to
1297 +
    #       more states in version 4.0
1298 +
    if rid > 1:
1299 +
        if obj:
1300 +
            r_authentication = "ACCEPT"
1301 +
        if not obj and details.get("multi_challenge"):
1302 +
            # We have a challenge authentication
1303 +
            r_authentication = "CHALLENGE"
1304 +
        else:
1305 +
            r_authentication = "REJECT"
1306 +
        res["result"]["authentication"] = r_authentication
1307 +
1295 1308
    return res
1296 1309
1297 1310

@@ -205,17 +205,16 @@
Loading
205 205
        :param transactionid: the id of this challenge
206 206
        :param options: the request context parameters / data
207 207
        :type options: dict
208 -
        :return: tuple of (bool, message, transactionid, attributes)
208 +
        :return: tuple of (bool, message, transactionid, reply_dict)
209 209
        :rtype: tuple
210 210
211 211
        The return tuple builds up like this:
212 212
        ``bool`` if submit was successful;
213 213
        ``message`` which is displayed in the JSON response;
214 -
        additional ``attributes``, which are displayed in the JSON response.
214 +
        additional challenge ``reply_dict``, which are displayed in the JSON challenges response.
215 215
        """
216 216
        options = options or {}
217 217
        questions = {}
218 -
        attributes = {}
219 218
220 219
        # Get an integer list of the already used questions
221 220
        used_questions = [int(x) for x in options.get("data", "").split(",") if options.get("data")]
@@ -250,8 +249,8 @@
Loading
250 249
        db_challenge.save()
251 250
        expiry_date = datetime.datetime.now() + \
252 251
                      datetime.timedelta(seconds=validity)
253 -
        attributes['valid_until'] = "{0!s}".format(expiry_date)
254 -
        return True, message, db_challenge.transaction_id, attributes
252 +
        reply_dict = {'attributes': {'valid_until': "{0!s}".format(expiry_date)}}
253 +
        return True, message, db_challenge.transaction_id, reply_dict
255 254
256 255
    def check_answer(self, given_answer, challenge_object):
257 256
        """

@@ -188,7 +188,7 @@
Loading
188 188
        The return tuple builds up like this:
189 189
        ``bool`` if submit was successful;
190 190
        ``message`` which is displayed in the JSON response;
191 -
        additional ``attributes``, which are displayed in the JSON response.
191 +
        additional challenge ``reply_dict``, which are displayed in the JSON challenges response.
192 192
        """
193 193
        options = options or {}
194 194
        return_message = get_action_values_from_options(SCOPE.AUTH,
@@ -231,8 +231,9 @@
Loading
231 231
        expiry_date = datetime.datetime.now() + \
232 232
                                    datetime.timedelta(seconds=validity)
233 233
        attributes['valid_until'] = "{0!s}".format(expiry_date)
234 +
        reply_dict = {"attributes": attributes}
234 235
235 -
        return True, return_message, transactionid, attributes
236 +
        return True, return_message, transactionid, reply_dict
236 237
237 238
    @check_token_locked
238 239
    def check_challenge_response(self, user=None, passw=None, options=None):

@@ -33,7 +33,7 @@
Loading
33 33
from privacyidea.lib.decorators import check_token_locked
34 34
from privacyidea.lib.error import ParameterError, RegistrationError, PolicyError
35 35
from privacyidea.lib.token import get_tokens
36 -
from privacyidea.lib.tokenclass import TokenClass, ROLLOUTSTATE
36 +
from privacyidea.lib.tokenclass import TokenClass, CLIENTMODE, ROLLOUTSTATE
37 37
from privacyidea.lib.tokens.u2f import x509name_to_string
38 38
from privacyidea.lib.tokens.webauthn import (COSE_ALGORITHM, webauthn_b64_encode, WebAuthnRegistrationResponse,
39 39
                                             ATTESTATION_REQUIREMENT_LEVEL, webauthn_b64_decode,
@@ -509,6 +509,8 @@
Loading
509 509
    The WebAuthn Token implementation.
510 510
    """
511 511
512 +
    client_mode = CLIENTMODE.WEBAUTHN
513 +
512 514
    @staticmethod
513 515
    def _get_challenge_validity_time():
514 516
        return int(get_from_config(WEBAUTHNCONFIG.CHALLENGE_VALIDITY_TIME,
@@ -1065,7 +1067,7 @@
Loading
1065 1067
        :type transactionid: basestring
1066 1068
        :param options: The request context parameters and data
1067 1069
        :type options: dict
1068 -
        :return: Success status, message, transaction id and response details
1070 +
        :return: Success status, message, transaction id and reply_dict
1069 1071
        :rtype: (bool, basestring, basestring, dict)
1070 1072
        """
1071 1073
@@ -1122,13 +1124,12 @@
Loading
1122 1124
                             required)
1123 1125
        ).assertion_dict
1124 1126
1125 -
        response_details = {
1126 -
            "webAuthnSignRequest": public_key_credential_request_options,
1127 -
            "hideResponseInput": True,
1128 -
            "img": user.icon_url
1129 -
        }
1127 +
        reply_dict = {"attributes": {"webAuthnSignRequest": public_key_credential_request_options,
1128 +
                                     "hideResponseInput": self.client_mode != CLIENTMODE.INTERACTIVE,
1129 +
                                     "img": user.icon_url},
1130 +
                      "image": user.icon_url}
1130 1131
1131 -
        return True, message, db_challenge.transaction_id, response_details
1132 +
        return True, message, db_challenge.transaction_id, reply_dict
1132 1133
    
1133 1134
    @check_token_locked
1134 1135
    def check_otp(self, otpval, counter=None, window=None, options=None):

@@ -125,13 +125,24 @@
Loading
125 125
    VIRTUAL = "virtual"
126 126
127 127
128 -
class TOKENMODE(object):
128 +
class AUTHENTICATIONMODE(object):
129 129
    AUTHENTICATE = 'authenticate'
130 130
    CHALLENGE = 'challenge'
131 131
    # If the challenge is answered out of band
132 132
    OUTOFBAND = 'outofband'
133 133
134 134
135 +
class CLIENTMODE(object):
136 +
    """
137 +
    This informs privacyIDEA clients how to
138 +
    handle challenge-responses
139 +
    """
140 +
    INTERACTIVE = 'interactive'
141 +
    POLL = 'poll'
142 +
    U2F = 'u2f'
143 +
    WEBAUTHN = 'webauthn'
144 +
145 +
    
135 146
class ROLLOUTSTATE(object):
136 147
    CLIENTWAIT = 'clientwait'
137 148
    PENDING = 'pending'
@@ -142,7 +153,8 @@
Loading
142 153
    # Class properties
143 154
    using_pin = True
144 155
    hKeyRequired = False
145 -
    mode = [TOKENMODE.AUTHENTICATE, TOKENMODE.CHALLENGE]
156 +
    mode = [AUTHENTICATIONMODE.AUTHENTICATE, AUTHENTICATIONMODE.CHALLENGE]
157 +
    client_mode = CLIENTMODE.INTERACTIVE
146 158
    # Usually a token will be checked in the lib:check_token_list, even if it is disabled
147 159
    check_if_disabled = True
148 160
@@ -180,7 +192,7 @@
Loading
180 192
181 193
    @classmethod
182 194
    def is_outofband(cls):
183 -
        return TOKENMODE.OUTOFBAND in cls.mode
195 +
        return AUTHENTICATIONMODE.OUTOFBAND in cls.mode
184 196
185 197
    @staticmethod
186 198
    def get_class_type():
@@ -1546,20 +1558,20 @@
Loading
1546 1558
        :param transactionid: the id of this challenge
1547 1559
        :param options: the request context parameters / data
1548 1560
        :type options: dict
1549 -
        :return: tuple of (bool, message, transactionid, attributes)
1561 +
        :return: tuple of (bool, message, transactionid, reply_dict)
1550 1562
        :rtype: tuple
1551 1563
1552 1564
        The return tuple builds up like this:
1553 1565
        ``bool`` if submit was successful;
1554 1566
        ``message`` which is displayed in the JSON response;
1555 -
        additional ``attributes``, which are displayed in the JSON response.
1567 +
        additional challenge ``reply_dict``, which are displayed in the JSON challenges response.
1556 1568
        """
1557 1569
        options = options or {}
1558 1570
        message = get_action_values_from_options(SCOPE.AUTH,
1559 1571
                                                 ACTION.CHALLENGETEXT,
1560 1572
                                                 options) or _('please enter otp: ')
1561 1573
        data = None
1562 -
        attributes = None
1574 +
        reply_dict = {}
1563 1575
1564 1576
        validity = int(get_from_config('DefaultChallengeValidityTime', 120))
1565 1577
        tokentype = self.get_tokentype().lower()
@@ -1576,7 +1588,7 @@
Loading
1576 1588
                                 validitytime=validity)
1577 1589
        db_challenge.save()
1578 1590
        self.challenge_janitor()
1579 -
        return True, message, db_challenge.transaction_id, attributes
1591 +
        return True, message, db_challenge.transaction_id, reply_dict
1580 1592
1581 1593
    def get_as_dict(self):
1582 1594
        """

@@ -46,7 +46,7 @@
Loading
46 46
from privacyidea.lib.log import log_with
47 47
from privacyidea.lib import _
48 48
49 -
from privacyidea.lib.tokenclass import TokenClass, TOKENMODE, ROLLOUTSTATE
49 +
from privacyidea.lib.tokenclass import TokenClass, AUTHENTICATIONMODE, CLIENTMODE, ROLLOUTSTATE
50 50
from privacyidea.models import Challenge, db
51 51
from privacyidea.lib.decorators import check_token_locked
52 52
import logging
@@ -256,7 +256,8 @@
Loading
256 256
    - https://github.com/privacyidea/privacyidea/issues/1342
257 257
    - https://github.com/privacyidea/privacyidea/wiki/concept%3A-PushToken
258 258
    """
259 -
    mode = [TOKENMODE.AUTHENTICATE, TOKENMODE.CHALLENGE, TOKENMODE.OUTOFBAND]
259 +
    mode = [AUTHENTICATIONMODE.AUTHENTICATE, AUTHENTICATIONMODE.CHALLENGE, AUTHENTICATIONMODE.OUTOFBAND]
260 +
    client_mode = CLIENTMODE.POLL
260 261
    # A disabled PUSH token has to be removed from the list of checked tokens.
261 262
    check_if_disabled = False
262 263
@@ -811,14 +812,14 @@
Loading
811 812
        The return tuple builds up like this:
812 813
        ``bool`` if submit was successful;
813 814
        ``message`` which is displayed in the JSON response;
814 -
        additional ``attributes``, which are displayed in the JSON response.
815 +
        additional challenge ``reply_dict``, which are displayed in the JSON challenges response.
815 816
        """
816 817
        options = options or {}
817 818
        message = get_action_values_from_options(SCOPE.AUTH,
818 819
                                                 ACTION.CHALLENGETEXT,
819 820
                                                 options) or DEFAULT_CHALLENGE_TEXT
820 821
821 -
        attributes = None
822 +
        reply_dict = {}
822 823
        data = None
823 824
        # Initially we assume there is no error from Firebase
824 825
        res = True
@@ -871,7 +872,7 @@
Loading
871 872
                                                                 PUSH_ACTION.FIREBASE_CONFIG))
872 873
            raise ValidateError("The token has no tokeninfo. Can not send via Firebase service.")
873 874
874 -
        return True, message, db_challenge.transaction_id, attributes
875 +
        return True, message, db_challenge.transaction_id, reply_dict
875 876
876 877
    @check_token_locked
877 878
    def authenticate(self, passw, user=None, options=None):

@@ -299,15 +299,19 @@
Loading
299 299
                "serial": "PIEM0000AB00",
300 300
                "type": "email",
301 301
                "transaction_id": "12345678901234567890",
302 -
                "multi_challenge": [ {"serial": "PIEM0000AB00",
303 -
                                      "transaction_id":  "12345678901234567890",
304 -
                                      "message": "Please enter otp from your email"},
305 -
                                     {"serial": "PISM12345678",
306 -
                                      "transaction_id": "12345678901234567890",
307 -
                                      "message": "Please enter otp from your SMS"}
302 +
                "multi_challenge: [ {"serial": "PIEM0000AB00",
303 +
                                     "transaction_id":  "12345678901234567890",
304 +
                                     "message": "Please enter otp from your
305 +
                                     email",
306 +
                                     "client_mode": "interactive"},
307 +
                                    {"serial": "PISM12345678",
308 +
                                     "transaction_id": "12345678901234567890",
309 +
                                     "message": "Please enter otp from your
310 +
                                     SMS",
311 +
                                     "client_mode": "interactive"}
308 312
                ]
309 313
              },
310 -
              "id": 1,
314 +
              "id": 2,
311 315
              "jsonrpc": "2.0",
312 316
              "result": {
313 317
                "status": true,
@@ -320,6 +324,11 @@
Loading
320 324
    with an SMS. The application and thus the user has to decide, which one
321 325
    to use. They can use either.
322 326
327 +
    The challenges also contain the information of the "client_mode". This
328 +
    tells the plugin, whether it should display an input field to ask for the
329 +
    OTP value or e.g. to poll for an answered authentication.
330 +
    Read more at :ref:`client_modes`.
331 +
323 332
    .. note:: All challenge response tokens have the same ``transaction_id`` in
324 333
       this case.
325 334
@@ -415,7 +424,7 @@
Loading
415 424
                        "success": success,
416 425
                        "serial": serial or details.get("serial"),
417 426
                        "token_type": details.get("type")})
418 -
    return send_result(result, details=details)
427 +
    return send_result(result, rid=2, details=details)
419 428
420 429
421 430
@validate_blueprint.route('/triggerchallenge', methods=['POST', 'GET'])
@@ -453,23 +462,49 @@
Loading
453 462
    **Example response** for a successful triggering of challenge:
454 463
455 464
       .. sourcecode:: http
465 +
       
466 +
            HTTP/1.1 200 OK
467 +
            Content-Type: application/json
456 468
457 -
           HTTP/1.1 200 OK
458 -
           Content-Type: application/json
459 -
460 -
           {
461 -
             "jsonrpc": "2.0",
462 -
             "signature": "1939...146964",
463 -
             "detail": {"transaction_ids": ["03921966357577766962"],
464 -
                        "messages": ["Enter the OTP from the SMS:"],
465 -
                        "threadid": 140422378276608},
466 -
             "versionnumber": "unknown",
467 -
             "version": "privacyIDEA unknown",
468 -
             "result": {"status": true,
469 -
                        "value": 1},
470 -
             "time": 1482223663.517212,
471 -
             "id": 1
472 -
           }
469 +
            {
470 +
               "detail": {
471 +
                    "client_mode": "interactive",
472 +
                    "message": "please enter otp: , please enter otp: ",
473 +
                    "messages":     [
474 +
                        "please enter otp: ",
475 +
                        "please enter otp: "
476 +
                    ],
477 +
                    "multi_challenge": [
478 +
                        {
479 +
                            "client_mode": "interactive",
480 +
                            "message": "please enter otp: ",
481 +
                            "serial": "TOTP000026CB",
482 +
                            "transaction_id": "11451135673179897001",
483 +
                            "type": "totp"
484 +
                        },
485 +
                        {
486 +
                            "client_mode": "interactive",
487 +
                            "message": "please enter otp: ",
488 +
                            "serial": "OATH0062752C",
489 +
                            "transaction_id": "11451135673179897001",
490 +
                            "type": "hotp"
491 +
                        }
492 +
                    ],
493 +
                    "serial": "OATH0062752C",
494 +
                    "threadid": 140329819764480,
495 +
                    "transaction_id": "11451135673179897001",
496 +
                    "transaction_ids": [
497 +
                        "11451135673179897001",
498 +
                        "11451135673179897001"
499 +
                    ],
500 +
                    "type": "hotp"
501 +
               },
502 +
               "id": 2,
503 +
               "jsonrpc": "2.0",
504 +
               "result": {
505 +
                   "status": true,
506 +
                   "value": 2
507 +
               }
473 508
474 509
    **Example response** for response, if the user has no challenge token:
475 510
@@ -543,7 +578,7 @@
Loading
543 578
        "serial": ",".join(challenge_serials),
544 579
    })
545 580
546 -
    return send_result(result_obj, details=details)
581 +
    return send_result(result_obj, rid=2, details=details)
547 582
548 583
549 584
@validate_blueprint.route('/polltransaction', methods=['GET'])

@@ -84,7 +84,7 @@
Loading
84 84
85 85
from privacyidea.api.lib.utils import getParam
86 86
from privacyidea.lib.config import get_from_config
87 -
from privacyidea.lib.tokenclass import TokenClass, TOKENMODE
87 +
from privacyidea.lib.tokenclass import TokenClass, AUTHENTICATIONMODE, CLIENTMODE
88 88
from privacyidea.lib.log import log_with
89 89
from privacyidea.lib.crypto import generate_otpkey
90 90
from privacyidea.lib.utils import create_img
@@ -120,7 +120,8 @@
Loading
120 120
    """
121 121
    The TiQR Token implementation.
122 122
    """
123 -
    mode = [TOKENMODE.AUTHENTICATE, TOKENMODE.CHALLENGE, TOKENMODE.OUTOFBAND]
123 +
    mode = [AUTHENTICATIONMODE.AUTHENTICATE, AUTHENTICATIONMODE.CHALLENGE, AUTHENTICATIONMODE.OUTOFBAND]
124 +
    client_mode = CLIENTMODE.POLL
124 125
125 126
    @staticmethod
126 127
    def get_class_type():
@@ -384,13 +385,13 @@
Loading
384 385
        :param transactionid: the id of this challenge
385 386
        :param options: the request context parameters / data
386 387
        :type options: dict
387 -
        :return: tuple of (bool, message, transactionid, attributes)
388 +
        :return: tuple of (bool, message, transactionid, reply_dict)
388 389
        :rtype: tuple
389 390
390 391
        The return tuple builds up like this:
391 392
        ``bool`` if submit was successful;
392 393
        ``message`` which is displayed in the JSON response;
393 -
        additional ``attributes``, which are displayed in the JSON response.
394 +
        additional challenge ``reply_dict``, which are displayed in the JSON challenges response.
394 395
        """
395 396
        options = options or {}
396 397
        message = _('Please scan the QR Code')
@@ -433,12 +434,15 @@
Loading
433 434
                                              challenge,
434 435
                                              service_displayname
435 436
                                              )
436 -
        attributes = {"img": create_img(authurl, width=250),
437 +
        image = create_img(authurl, width=250)
438 +
        attributes = {"img": image,
437 439
                      "value": authurl,
438 -
                      "poll": True,
439 -
                      "hideResponseInput": True}
440 +
                      "poll": self.client_mode == CLIENTMODE.POLL,
441 +
                      "hideResponseInput": self.client_mode != CLIENTMODE.INTERACTIVE}
442 +
        reply_dict = {"attributes": attributes,
443 +
                      "image": image}
440 444
441 -
        return True, message, db_challenge.transaction_id, attributes
445 +
        return True, message, db_challenge.transaction_id, reply_dict
442 446
443 447
    @check_token_locked
444 448
    def check_challenge_response(self, user=None, passw=None, options=None):

@@ -29,7 +29,7 @@
Loading
29 29
#
30 30
from privacyidea.api.lib.utils import getParam, attestation_certificate_allowed
31 31
from privacyidea.lib.config import get_from_config
32 -
from privacyidea.lib.tokenclass import TokenClass, ROLLOUTSTATE
32 +
from privacyidea.lib.tokenclass import TokenClass, CLIENTMODE, ROLLOUTSTATE
33 33
from privacyidea.lib.token import get_tokens
34 34
from privacyidea.lib.log import log_with
35 35
import logging
@@ -211,6 +211,8 @@
Loading
211 211
    The U2F Token implementation.
212 212
    """
213 213
214 +
    client_mode = CLIENTMODE.U2F
215 +
214 216
    @staticmethod
215 217
    def get_class_type():
216 218
        """
@@ -432,7 +434,7 @@
Loading
432 434
        The return tuple builds up like this:
433 435
        ``bool`` if submit was successful;
434 436
        ``message`` which is displayed in the JSON response;
435 -
        additional ``attributes``, which are displayed in the JSON response.
437 +
        additional challenge ``reply_dict``, which are displayed in the JSON challenges response.
436 438
        """
437 439
        options = options or {}
438 440
        message = get_action_values_from_options(SCOPE.AUTH,
@@ -481,11 +483,12 @@
Loading
481 483
                            "keyHandle": key_handle_url}
482 484
483 485
        image_url = IMAGES.get(self.token.description.lower().split()[0], "")
484 -
        response_details = {"u2fSignRequest": u2f_sign_request,
485 -
                            "hideResponseInput": True,
486 -
                            "img": image_url}
486 +
        reply_dict = {"attributes": {"u2fSignRequest": u2f_sign_request,
487 +
                                     "hideResponseInput": self.client_mode != CLIENTMODE.INTERACTIVE,
488 +
                                     "img": image_url},
489 +
                      "image": image_url}
487 490
488 -
        return True, message, db_challenge.transaction_id, response_details
491 +
        return True, message, db_challenge.transaction_id, reply_dict
489 492
490 493
    @check_token_locked
491 494
    def check_otp(self, otpval, counter=None, window=None, options=None):
Files Coverage
privacyidea 96.58%
Project Totals (162 files) 96.58%

No yaml found.

Create your codecov.yml to customize your Codecov experience

Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading