"Fossies" - the Fresh Open Source Software Archive

Member "LinOTP-release-2.10.5.2/linotpd/src/linotp/lib/auth/request.py" (13 May 2019, 11474 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. For more information about "request.py" see the Fossies "Dox" file reference documentation.

    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 " support redirecting requests to remote systems on base of policy defintion "
   28 
   29 import logging
   30 
   31 # this is needed for the http request
   32 import json
   33 import copy
   34 import httplib2
   35 import urllib
   36 import urlparse
   37 
   38 # this is needed for the radius request
   39 import pyrad.packet
   40 from pyrad.client import Client
   41 from pyrad.dictionary import Dictionary
   42 
   43 from linotp.lib.context import request_context as context
   44 
   45 log = logging.getLogger(__name__)
   46 
   47 
   48 class Request(object):
   49     """
   50     Request is the class to handle the forwarding of request
   51     to external, remote sources and servers. Supported is currently either
   52     requests to a Radius server or requests to an remote LinOTP server via
   53     http. The forwarding server defintion is done in the forward policy, where
   54     a list of servers is defined in the URI format. Parameters in URI defintion
   55     of the policy could contain multiple server defintions, which are separated
   56     via ';'
   57      action = http://localhost:5001/validate/check;\
   58               http://127.0.0.1:5001/validate/check
   59     """
   60 
   61     def __init__(self, servers):
   62         """
   63         build up the request class
   64         - by parsing the server definition
   65 
   66         :param servers: the server description from the policy definition
   67         :return: tuple of status as boolean and reply as dict with detail info
   68         """
   69 
   70         self.sysconfig = context['SystemConfig']
   71         self.config = {}
   72 
   73         # split the servers along the ';'
   74         for server in servers.split(';'):
   75             parsed = urlparse.urlparse(server)
   76             self.config[parsed.hostname] = {'scheme': parsed.scheme,
   77                                             'netloc': parsed.netloc,
   78                                             'port': parsed.port,
   79                                             'hostname': parsed.hostname,
   80                                             'path': parsed.path,
   81                                             'params': parsed.params,
   82                                             'query': parsed.query,
   83                                             'fragment': parsed.fragment,
   84                                             'secret': parsed.password,
   85                                             'url': server}
   86         self.hsm = context['hsm']
   87 
   88     @staticmethod
   89     def get_server_params(server_config):
   90         """
   91         helper method to extract the parameter key, value dict
   92         from the server url parameter
   93 
   94         :param server_config:
   95         """
   96         # parameters in the policy server url may overwrite request params
   97         params = {}
   98         url = server_config.get('url')
   99         if '?' in url:
  100             add_param = urlparse.parse_qs(url.split('?')[1])
  101             for key, value in add_param.items():
  102                 params[key] = value[0]
  103         return params
  104 
  105 
  106 class HttpRequest(Request):
  107     """
  108     HTTP request forwarding handler
  109     """
  110 
  111     def do_request(self, user, password, options=None):
  112         """
  113         run the http request against the remote host
  114 
  115         :param user: the requesting user (required)
  116         :param password: the password which should be checked on the remote
  117                             host
  118         :param options: dict which provides additional request parameter. e.g
  119                         for challenge response
  120 
  121         :return: Tuple of (success, and reply=remote response)
  122         """
  123 
  124         verify_key = "remote.verify_ssl_certificate"
  125         ssl_verify = (str(self.sysconfig.get(verify_key, False))
  126                       .lower().strip() == 'true')
  127 
  128         params = {}
  129         params['pass'] = password.encode("utf-8")
  130         params['user'] = user.login
  131         params['realm'] = user.realm
  132 
  133         for key, value in options.items():
  134             params[key] = value.encode("utf-8")
  135 
  136         for server in self.config.keys():
  137             server_config = self.config[server]
  138 
  139             request_url = "%(scheme)s://%(netloc)s%(path)s" % server_config
  140 
  141             # parameters in the policy server url may overwrite request params
  142             params.update(Request.get_server_params(server_config))
  143 
  144             res = False
  145             reply = {}
  146 
  147             try:
  148                 # prepare the submit and receive headers
  149                 headers = {"Content-type": "application/x-www-form-urlencoded",
  150                            "Accept": "text/plain", 'Connection': 'close'}
  151 
  152                 data = urllib.urlencode(params)
  153                 # submit the request
  154                 try:
  155                     # is httplib compiled with ssl?
  156                     ns = not ssl_verify
  157                     http = httplib2.Http(disable_ssl_certificate_validation=ns)
  158 
  159                 except TypeError as exx:
  160                     # not so on squeeze:
  161                     # TypeError: __init__() got an unexpected keyword argument
  162                     # 'disable_ssl_certificate_validation'
  163 
  164                     log.warning("httplib2 'disable_ssl_certificate_validation'"
  165                                 " attribute error: %r", exx)
  166                     # so we run in fallback mode
  167                     http = httplib2.Http()
  168 
  169                 (resp, content) = http.request(request_url,
  170                                                method="POST",
  171                                                body=data,
  172                                                headers=headers)
  173                 if resp.status not in [200]:
  174                     raise Exception("Http Status not ok (%s)", resp.status)
  175 
  176                 result = json.loads(content)
  177                 status = result['result']['status']
  178                 log.debug("Request to %s returned status %r" %
  179                           (request_url, status))
  180 
  181                 if status is True:
  182                     if result['result']['value'] is True:
  183                         res = True
  184 
  185                 if "detail" in result:
  186                     reply = copy.deepcopy(result["detail"])
  187                     res = False
  188 
  189                 # we break the servers loop if one request ended up here
  190                 break
  191 
  192             except Exception as exx:
  193                 log.exception("Error getting response from remote server "
  194                               "for url %r. Exception was %r", request_url, exx)
  195 
  196         return res, reply
  197 
  198 
  199 class RadiusRequest(Request):
  200     """
  201     Radius request forwarding handler
  202     """
  203 
  204     def do_request(self, user, password, options=None):
  205         """
  206         run the radius request against the remote host
  207 
  208         :param user: the requesting user (required)
  209         :param password: the password which should be checked on the remote
  210                             host
  211         :param options: dict which provides additional request parameter. e.g
  212                         for challenge response
  213 
  214         :return: Tuple of (success, and reply=remote response)
  215         """
  216 
  217         reply = {}
  218         res = False
  219 
  220         for server in self.config.keys():
  221             server_config = self.config[server]
  222             radiusServer = server_config['netloc']
  223             radiusUser = user.login
  224 
  225             # Read the secret!!! - currently in plain text
  226             radiusSecret = server_config['secret']
  227 
  228             # here we also need to check for radius.user
  229             log.debug("Checking OTP len:%s on radius server %s,"
  230                       "User: %s", len(password), radiusServer, radiusUser)
  231 
  232             try:
  233                 # pyrad does not allow to set timeout and retries.
  234                 # it defaults to retries=3, timeout=5
  235 
  236                 server = radiusServer.split(':')
  237                 r_server = server[0]
  238                 r_authport = 1812
  239                 nas_identifier = self.sysconfig.get("radius.nas_identifier",
  240                                                     "LinOTP")
  241                 r_dict = self.sysconfig.get("radius.dictfile",
  242                                             "/etc/linotp2/dictionary")
  243 
  244                 if len(server) >= 2:
  245                     r_authport = int(server[1])
  246                 log.debug("Radius: NAS Identifier: %r, "
  247                           "Dictionary: %r", nas_identifier, r_dict)
  248 
  249                 log.debug("Radius: constructing client object "
  250                           "with server: %r, port: %r, secret: %r",
  251                           r_server, r_authport, radiusSecret)
  252 
  253                 srv = Client(server=r_server,
  254                              authport=r_authport,
  255                              secret=radiusSecret,
  256                              dict=Dictionary(r_dict))
  257 
  258                 req = srv.CreateAuthPacket(code=pyrad.packet.AccessRequest,
  259                                            User_Name=radiusUser.\
  260                                                      encode('ascii'),
  261                                            NAS_Identifier=nas_identifier.\
  262                                                           encode('ascii'))
  263 
  264                 req["User-Password"] = req.PwCrypt(password)
  265                 if "transactionid" in options or 'state' in options:
  266                     req["State"] = str(options.get('transactionid',
  267                                                    options.get('state')))
  268 
  269                 response = srv.SendPacket(req)
  270 
  271                 if response.code == pyrad.packet.AccessChallenge:
  272                     opt = {}
  273                     for attr in response.keys():
  274                         opt[attr] = response[attr]
  275                     res = False
  276                     log.debug("Radius: challenge returned %r ", opt)
  277                     # now we map this to a linotp challenge
  278                     if "State" in opt:
  279                         reply["transactionid"] = opt["State"][0]
  280 
  281                     if "Reply-Message" in opt:
  282                         reply["message"] = opt["Reply-Message"][0]
  283 
  284                 elif response.code == pyrad.packet.AccessAccept:
  285                     log.info("Radius: Server %s granted "
  286                              "access to user %s.", r_server, radiusUser)
  287                     res = True
  288                 else:
  289                     log.warning("Radius: Server %s"
  290                                 "rejected access to user %s.",
  291                                 r_server, radiusUser)
  292                     res = False
  293 
  294                 # we break the servers look if one request ended up here
  295                 break
  296 
  297             except Exception as ex:
  298                 log.exception("Error contacting "
  299                               "radius Server: %r", ex)
  300 
  301         return (res, reply)
  302 
  303 ### eof #######################################################################