.. _token_development: ====== Tokens ====== If you decide to implement new token classes, you need to change the parameter ``linotpTokenModules`` in ``/etc/linotp2/linotp.ini`` in the ``DEFAULT`` section. This parameter contains a list of token modules LinOTP should use. .. note:: The module ``linotp.lib.tokenclass`` will always be used. Starting with LinOTP 2.5.2 it is even simpler to add new token types to LinOTP. The complete token type handling is now done dynamically. This means you can * add a new token module * new policies that are necessary for this token * new management GUI (enrollment and token settings) and * new selfservice enrollment GUI for this new token without having to adapt anything in the LinOTP core. This is done by creating two files **new_token.py** This file contains all the program logic, the calculation and verification of the OTP values and the handling of the policies. **new_token.mako** This file contains the GUI parts for management and selfservice. The existing tokens are located in the module ``linotp.lib.tokens``. Both files describe the new token and need to be located in the same directory i.e. the same python module. The GUI file ------------ The **new_token.mako** file is written in the MAKO templating language. More information on how MAKO works can be found on the project website [#mako_website]_. .. [#mako_website] http://www.makotemplates.org/ .. note:: Please take a look at the example file ``dyntoken.mako``. To make the file handling easier there is only this single file providing the GUI for * selfservice * token configurations * token enrollment. Therefore there is a top level switch in this file that evaluates the variable ``c.scope``. The scope config ~~~~~~~~~~~~~~~~ **config** The scope ``config`` is used for the generic token configuration information. E.g. for a time based HMAC Token (TOTP) you can use this scope to configure the default time window and the default OTP length. The scope ``config`` contains the complete HTML code of this config tab. **config.title** This is the header text of the config tab. To read and write the token configuration to the LinOTP configuration database you need to define two javascript functions:: function dyn_get_config_val(){ var ret = {}; ret['DynTokenTimeOut'] = 'dyn_challenge_timeout'; ret['DynTokenMaxChallemges'] = 'dyn_max_challenge'; return ret; } function dyn_get_config_params(){ var ret ={} ret['DynTokenTimeOut'] = $('#dyn_challenge_timeout').val(); ret['DynTokenMaxChallemges'] = $('#dyn_max_challenge').val(); return ret } The name of the function starts with the token type name of the token. You already know tokens like ``hmac``, ``totp``, ``spass``. So the function names concatenate like ``_get_config_val`` and ``_get_config_params()``. The example above are the functions of the token type ``dyn``. In the config tab for the token, you might have several HTML object. The function maps the identifier of the HTML objects to the configuration keys stored in the LinOTP configuration database. The function ``_get_config_val`` is called, when the config tab is opened. It tells LinOTP to get the config entry with the key ``DynTokenTimeOut`` from the LinOTP configuration and put it into the HTML object with the ``id=dyn_challenge_timeout``. The function ``_get_config_params`` is called, when the config tab is saved. The values of the HTML objects are written to the corresponding LinOTP config database entries. .. warning:: All config entries are stored in one config table. So you need to review third party token modules and assure, that a foreign token modules does not interfere with your other tokens or break your LinOTP. Best practice is to let your token config key start with the token type as prefix. The scope enroll ~~~~~~~~~~~~~~~~ **enroll** This is the HTML GUI for the enrollment of a token by the administrator. Here all necessary data input is displayed. **enroll.title** This is the header text for the enrollment. In the enrollment process an administrator needs to add additional information, like the RADIUS server in case of a RADIUS Token, the secret key or whatever. These data need to be passed to LinOTP, therefore you need to define a javascript function ``_get_enroll_params``. The function has to return a hash of all the parameters, that will be passed to the ``admin/init`` function:: function dyn_get_enroll_params(){ var url = {} url['type'] = 'dyn'; url['otppin'] = $('#dyn_pin1').val(); url['hashlib'] = $('#dyn_algorith').val(); url['otplen'] = $('#dyn_otplen').val(); url['serial'] = create_serial('DYN'); if ( $('#dyn_key_cb').attr('checked') ) { url['genkey']='1';} else { url['otpkey'] = $('#dyn_key').val();} return url; } The scope selfservice ~~~~~~~~~~~~~~~~~~~~~ **selfservice.enroll** This is the HTML GUI for the user self enrollment in the Selfservice Portal. **selfservice.title.enroll** This is the header text of the selfservice tab. In the self enrollment process several information needs to be entered by the user and needs to be passed to LinOTP. Three javascript functions are used to read the data from the HTML GUI and pass it to LinOTP. The first function ``self__get_param`` function is used to retrieve the hashed parameters that needs to be passed to the ``selfservice/userinit`` function:: function self_dyn_get_param() { var urlparam = {}; urlparam['type'] = 'dyn'; urlparam['description'] = "self enrolled" ; urlparam['otpkey'] = $('#motp_secret').val(); urlparam['otppin'] = $('#motp_s_pin1').val(); urlparam['serial'] = generate_serial("LSMO"); return urlparam; } .. note:: The ``generate_serial()`` function generates a new serial number with the passed prefix. The function ``self__clear`` is used to clear the input fields:: function self_dyn_clear() { $('#motp_s_pin1').val(''); $('#motp_secret').val(''); $('#motp_s_pin2').val(''); } Finally there is a function ``self__submit`` that is called when the token is enrolled. This function can also be used to do a input validation:: function self_dyn_submit(){ var ret = false if ($('#form_registermotp').valid()) { var params = self_motp_get_param(); enroll_token( params ); //self_motp_clear(); ret = true; } else { alert("Form data not valid."); } return ret; } .. note:: When doing input validation the corresponding ``jQuery.validator`` needs to be defined. The token file -------------- The token file ``dyntoken.py`` itself contains the token definition as a python class that inherits from ``TokenClass`` which is defined in ``linotp.lib.tokenclass``. The token class needs to provide a class method ``getClassInfo``, that tells LinOTP, which scopes to use in which situation. Therefore it returns a dictionary of the following kind:: { 'type' : 'dyn', 'title' : 'my dyn Token', 'description' : ('my own dynamic otp token'), 'init' : {'page' : {'html' : 'dyntoken.mako', 'scope' : 'enroll',}, 'title' : {'html' : 'dyntoken.mako', 'scope' : 'enroll.tab',}, }, 'config' : { 'page' : {'html' : 'dyntoken.mako', 'scope' : 'config',}, 'title' : {'html' : 'dyntoken.mako', 'scope' : 'config.tab',}, }, 'selfservice' : { 'enroll' : {'page' : {'html' : 'dyntoken.mako', 'scope' : 'selfservice.enroll',}, 'title' : { 'html' : 'dyntoken.mako', 'scope' : 'selfservice.tab.enroll',}, }, }, } Challenge Response ~~~~~~~~~~~~~~~~~~ Starting with LinOTP 2.6 you can easily create tokens capable of doing challenge response. Usually a challenge will be triggered when sending an authentication request to ``/validate/check`` with the additional parameter ``data`` or ``challenge``. The method :py:meth:`~linotp.lib.tokenclass.TokenClass.is_challenge_request` determines, if the given request is a challenge or not. The response will also be sent to the ``/validate/check`` interface. Usually the response has to contain the parameter ``state`` or ``transactionid`` so that the request will be identified as a response to a previous challenge. The method :py:meth:`~linotp.lib.tokenclass.TokenClass.is_challenge_response` determines, if the given request is a response for a previous challenge. Each token type can define it's own challenge handling by overwriting the corresponding base class method. So the methods ``is_challenge_request`` and ``is_challenge_response`` determine in which of the following three branches the request will be verified: ``authenticate`` This is the normal single shot authentication e.g. for push button OTP tokens. No challenge and no response is involved. ``is_challenge_request`` and ``is_challenge_response`` both return false. The authentication workflow will call the :py:meth:`~linotp.lib.tokenclass.TokenClass.authenticate` method. ``challenge`` The method ``is_challenge_request`` returns true and ``is_challenge_response`` returns false. A new challenge will be created. The authentication workflow will call the method :py:meth:`~linotp.lib.tokenclass.TokenClass.initChallenge` and :py:meth:`~linotp.lib.tokenclass.TokenClass.createChallenge`. ``response`` The method ``is_challenge_request`` returns false and ``is_challenge_response`` returns true. The authentication workflow will call the method :py:meth:`~linotp.lib.tokenclass.TokenClass.checkResponse4Challenge`. Please see the base class definition in :ref:`token_base_class`. .. _token_base_class: Base TokenClass ~~~~~~~~~~~~~~~ See documentation for TokenClass class: :py:class:`linotp.lib.tokenclass.TokenClass`