.. _administrativ_interfaces: Administrative Interfaces ========================= .. note:: To every management controller you can add the parameter ``httperror`` followed by an HTTP error code. If LinOTP would return HTTP 200/OK with ``status: false`` in the JSON response indicating an internal error then LinOTP will instead return a e.g. HTTP 500 error code. Admin Interface ---------------- Managing tokens can be performed via an HTTPs Interface just like the authentication via /validate/check and /validate/simplecheck. The main API is located at the controller /admin. There you can find functions like this: * /admin/init: Enroll token * /admin/enable: Enable token * /admin/disable: Disable token * /admin/show: List tokens * /admin/delete: Delete tokens Other controllers are * /manage: provides the Web UI. * /system: provides the functions to configure the LinOTP server. * /license: provides the functions to set and get license information. * /audit: provides the functions for the audit trail. Orphanced tokens ~~~~~~~~~~~~~~~~ .. index:: Orphaned tokens Orphaned tokens are tokens, that are still assigned to the user, but where the user object was deleted from the user database. Using the method ``/admin/show`` you can also view such orphaned tokens by calling the method:: /admin/show?user='/:no%20user%20info:/' You can also find tokens that have no users or tokens that are assigned to no realm:: /admin/show?user='' /admin/show?realm='' Admin Controller ---------------- See documentation for AdminController class: :py:class:`linotp.controllers.admin.AdminController` .. _system_controller: System Controller ----------------- See documentation for SystemController class: :py:class:`linotp.controllers.system.SystemController` .. _session_parameter: Session parameter ----------------- The LinOTP administrative API (e.g. */manage*, */admin*, */system*) requires an additional **session** parameter and **admin_session** cookie. This provides CSRF (Cross Site Request Forgery) protection. Then only requirement is that both values match but they should be sufficiently random so that an attacker can not simply guess it. Accessing the API with your Browser ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To access the administrative API in your Browser first open the Manage UI (https://FQDN/manage) and enter your admin user credentials. Then check the value of the **admin_session** cookie. In Firefox this can be done as follows: 1. Open the menu *Tools/Page Info* 2. Open the tab *Security* 3. Click on the *View Cookies* button 4. Select the **admin_session** cookie 5. Copy the content (something like *90dd0f1ac...*). .. tip:: Advanced users can also open a JavaScript console (e.g. Firebug) and execute the *getsession()* function. Then open another browser window, type in the API call you want to make and append **session**. For example: https://FQDN/admin/unassign?serial=LSSP0001F4E0&session=90dd0f1ac... Programatically calling the API ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To call the administrative API from a script you need to use a programming language/library that allows you to make HTTPS requests with *Digest Authentication* and allows you to set cookies. Below is a fully functional Python example using the *requests* library to get a list of all token: .. code-block:: python # -*- coding: utf-8 -*- """ Small example on how to use the LinOTP administrative API. Prints all found token serial numbers. Tested with Python 2.7.9 and requests 2.4.3 """ import os import binascii import requests # Adapt this for your server BASE_URL = 'https://FQDN' ADMIN_USER = 'admin' ADMIN_PWD = 'secret' # 'admin/show' returns information for all token in the system url = BASE_URL + '/admin/show' # Use Digest Authentication (checked by the Apache web server or similar) auth = requests.auth.HTTPDigestAuth(ADMIN_USER, ADMIN_PWD) # Generate a random value for 'session' random_val = binascii.hexlify(os.urandom(64)) # Instead of generating 'random_val' you can also make a request to # https://FQDN/admin/getsession which will set a 'admin_session' cookie on # the response. You can then extract this value and use it in further # requests. # Set that value both as parameter and as cookie params = {'session': random_val} cookies = {'admin_session': random_val} # Send the request r = requests.post( url, auth=auth, params=params, cookies=cookies, verify=False, # Bad idea in production. Always verify your certificates! ) # Verify the response contains the expected data assert r.status_code == 200 assert r.encoding == 'utf-8' result = r.json() assert result['result']['status'] == True token_list = result['result']['value']['data'] # Print a list of all token serial numbers print("**************************\nFound %s token:\n" % len(token_list)) for t in token_list: print(t['LinOtp.TokenSerialnumber']) Technical background ~~~~~~~~~~~~~~~~~~~~ LinOTP does not check user authorization when accessing the administrative API (e.g. */admin*, */system*) but relies on the web server running LinOTP (as a WSGI app) to do this. The most common setup (e.g. our KeyIdentity Smart Virtual Appliance) is Apache web server with `Digest Authentication `_. If the user does not identify himself correctly Apache won't forward the request to LinOTP . If the request reaches LinOTP it is assumed the administrator authenticated correctly and his name will be extracted from a HTTP header set during *Digest Authentication*. Once you authenticated your browser will store your information and will not ask again for username+password typically until the next browser restart. The browser will transparently send special HTTP Digest headers with EVERY request your browser makes to the LinOTP server. This opens the door to an attack known as CSRF (Cross Site Request Forgery). Imagine someone is able to trick your browser into calling the following URL: https://FQDN/admin/delete?serial=LSSP0001F4E0 Since your browser sends the HTTP digest headers in EVERY request (as long as you authenticated once after having started the browser) this request will delete your token. In general the attacker can leverage the full power of the LinOTP administrative API as long as your browser is tricked into sending his requests. How can the browser be tricked into doing this? For example if you open a web page containing some malicious HTML such as: .. code-block:: html Your browser will try to display that image (which is not really an image) and execute a GET request to that URL, causing the token to be deleted. See `CSRF on Wikipedia `_ for more information. .. note:: A common misconception is that CSRF can only be done with GET Requests. That is not true. Limiting your server to POST does not protect against CSRF. LinOTP implements a common solution to this problem by requiring something to be sent that will NOT be automatically sent by the browser. This cannot be a cookie (because they are also sent with every request by the browser). It could be a custom HTTP header, or a custom parameter. This parameter can either contain a value that is decrypted and verified by the server (e.g. containing a timestamp that expired after a certain time) or it can simply be a value UNKNOWN to the attacker but matching a cookie. Since the attacker we are assuming only lured you onto a malicious website but has no control over your browser (and can therefore NOT see the cookies for other domains than his own) both approaches work. LinOTP implements the second approach by using a **session** parameter whose value has to match the **admin_session** cookie. .. note:: That these variables are named **session** and **admin_session** is a little bit unfortunate and confusing (since they have nothing to do with sessions) but changing it would break backward compatibility with 3rd party software that interacts with LinOTP. Something like **csrf_token** would have been clearer. .. _disable_session_protection: Disabling the session parameter ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You should only disable the **session** parameter if you fully understand what you are doing. You open your server to CSRF attacks (see the Technical background above). It can be useful during development though because it is cumbersome to set the values in every request. In ``/etc/linotp2/linotp.ini`` add the following line to the ``DEFAULT`` section:: linotpNoSessionCheck = 10.1.2.3, 192.168.0.0/24 You can add any IP address or subnet in CIDR notation separated by comma.