Esempio n. 1
0
class WebHandler(tornado.web.RequestHandler):

    __disetConfig = ThreadConfig()
    __log = False

    #Auth requirements
    AUTH_PROPS = None
    #Location of the handler in the URL
    LOCATION = ""
    #URL Schema with holders to generate handler urls
    URLSCHEMA = ""
    #RE to extract group and setup
    PATH_RE = ""

    #Helper function to create threaded gen.Tasks with automatic callback and execption handling
    def threadTask(self, method, *args, **kwargs):
        """
    Helper method to generate a gen.Task and automatically call the callback when the real
    method ends. THIS IS SPARTAAAAAAAAAA. SPARTA has improved using futures ;)
    """
        #Save the task to access the runner
        genTask = False

        #This runs in the separate thread, calls the callback on finish and takes into account exceptions
        def cbMethod(*cargs, **ckwargs):
            cb = ckwargs.pop('callback')
            method = cargs[0]
            disetConf = cargs[1]
            cargs = cargs[2]
            self.__disetConfig.reset()
            self.__disetConfig.load(disetConf)
            ioloop = tornado.ioloop.IOLoop.instance()
            try:
                result = method(*cargs, **ckwargs)
                ioloop.add_callback(functools.partial(cb, result))
            except Exception as excp:
                gLogger.error("Following exception occured %s" % excp)
                exc_info = sys.exc_info()
                genTask.set_exc_info(exc_info)
                ioloop.add_callback(lambda: genTask.exception())

        #Put the task in the thread :)
        def threadJob(tmethod, *targs, **tkwargs):
            tkwargs['callback'] = tornado.stack_context.wrap(
                tkwargs['callback'])
            targs = (tmethod, self.__disetDump, targs)
            gThreadPool.submit(cbMethod, *targs, **tkwargs)

        #Return a YieldPoint
        genTask = tornado.gen.Task(threadJob, method, *args, **kwargs)
        return genTask

    def __disetBlockDecor(self, func):
        def wrapper(*args, **kwargs):
            raise RuntimeError(
                "All DISET calls must be made from inside a Threaded Task! Bad boy!"
            )

        return wrapper

    def __init__(self, *args, **kwargs):
        """
    Initialize the handler
    """
        super(WebHandler, self).__init__(*args, **kwargs)
        if not WebHandler.__log:
            WebHandler.__log = gLogger.getSubLogger(self.__class__.__name__)
        self.__credDict = {}
        self.__setup = Conf.setup()
        self.__processCredentials()
        self.__disetConfig.reset()
        self.__disetConfig.setDecorator(self.__disetBlockDecor)
        self.__disetDump = self.__disetConfig.dump()
        match = self.PATH_RE.match(self.request.path)
        self._pathResult = self.__checkPath(*match.groups())
        self.__sessionData = SessionData(self.__credDict, self.__setup)

    def __processCredentials(self):
        """
    Extract the user credentials based on the certificate or what comes from the balancer
    """
        #NGINX
        if Conf.balancer() == "nginx":
            headers = self.request.headers
            if headers['X-Scheme'] == "https" and headers[
                    'X-Ssl_client_verify'] == 'SUCCESS':
                DN = headers['X-Ssl_client_s_dn']
                self.__credDict['DN'] = DN
                self.__credDict['issuer'] = headers['X-Ssl_client_i_dn']
                result = Registry.getUsernameForDN(DN)
                if not result['OK']:
                    self.__credDict['validDN'] = False
                else:
                    self.__credDict['validDN'] = True
                    self.__credDict['username'] = result['Value']
            return
        #TORNADO
        if not self.request.protocol == "https":
            return
        derCert = self.request.get_ssl_certificate(binary_form=True)
        if not derCert:
            return
        pemCert = ssl.DER_cert_to_PEM_cert(derCert)
        chain = X509Chain()
        chain.loadChainFromString(pemCert)
        result = chain.getCredentials()
        if not result['OK']:
            self.log.error("Could not get client credentials %s" %
                           result['Message'])
            return
        self.__credDict = result['Value']
        #Hack. Data coming from OSSL directly and DISET difer in DN/subject
        try:
            self.__credDict['DN'] = self.__credDict['subject']
        except KeyError:
            pass

    def _request_summary(self):
        """
    Return a string returning the summary of the request
    """
        summ = super(WebHandler, self)._request_summary()
        cl = []
        if self.__credDict.get('validDN', False):
            cl.append(self.__credDict['username'])
            if self.__credDict.get('validGroup', False):
                cl.append("@%s" % self.__credDict['group'])
            cl.append(" (%s)" % self.__credDict['DN'])
        summ = "%s %s" % (summ, "".join(cl))
        return summ

    @property
    def log(self):
        return self.__log

    @classmethod
    def getLog(cls):
        return cls.__log

    def getUserDN(self):
        return self.__credDict.get('DN', '')

    def getUserName(self):
        return self.__credDict.get('username', '')

    def getUserGroup(self):
        return self.__credDict.get('group', '')

    def getUserSetup(self):
        return self.__setup

    def getUserProperties(self):
        return self.__sessionData.getData().properties

    def isRegisteredUser(self):
        return self.__credDict.get('validDN', "") and self.__credDict.get(
            'validGroup', "")

    def getSessionData(self):
        return self.__sessionData.getData()

    def actionURL(self, action=""):
        """
    Given an action name for the handler, return the URL
    """
        if action == "index":
            action = ""
        group = self.getUserGroup()
        if group:
            group = "/g:%s" % group
        setup = self.getUserSetup()
        if setup:
            setup = "/s:%s" % setup
        location = self.LOCATION
        if location:
            location = "/%s" % location
        ats = dict(action=action, group=group, setup=setup, location=location)
        return self.URLSCHEMA % ats

    def __auth(self, handlerRoute, group):
        """
    Authenticate request
    """
        userDN = self.getUserDN()
        if group:
            self.__credDict['group'] = group
        else:
            if userDN:
                result = Registry.findDefaultGroupForDN(userDN)
                if result['OK']:
                    self.__credDict['group'] = result['Value']
        self.__credDict['validGroup'] = False

        if type(self.AUTH_PROPS) not in (types.ListType, types.TupleType):
            self.AUTH_PROPS = [
                p.strip() for p in self.AUTH_PROPS.split(",") if p.strip()
            ]
        allAllowed = False
        for p in self.AUTH_PROPS:
            if p.lower() in ('all', 'any'):
                allAllowed = True

        auth = AuthManager(Conf.getAuthSectionForHandler(handlerRoute))
        ok = auth.authQuery("", self.__credDict, self.AUTH_PROPS)
        if ok:
            if userDN:
                self.__credDict['validGroup'] = True
                self.log.info("AUTH OK: %s by %s@%s (%s)" %
                              (handlerRoute, self.__credDict['username'],
                               self.__credDict['group'], userDN))
            else:
                self.__credDict['validDN'] = False
                self.log.info("AUTH OK: %s by visitor" % (handlerRoute))
        elif allAllowed:
            self.log.info("AUTH ALL: %s by %s" % (handlerRoute, userDN))
            ok = True
        else:
            self.log.info("AUTH KO: %s by %s@%s" %
                          (handlerRoute, userDN, group))
        return ok

    def __checkPath(self, setup, group, route):
        """
    Check the request, auth, credentials and DISET config
    """
        if route[-1] == "/":
            methodName = "index"
            handlerRoute = route
        else:
            iP = route.rfind("/")
            methodName = route[iP + 1:]
            handlerRoute = route[:iP]
        if setup:
            self.__setup = setup
        if not self.__auth(handlerRoute, group):
            return WErr(401, "Unauthorized, bad boy!")

        DN = self.getUserDN()
        if DN:
            self.__disetConfig.setDN(DN)
        group = self.getUserGroup()
        if group:
            self.__disetConfig.setGroup(group)
        self.__disetConfig.setSetup(setup)
        self.__disetDump = self.__disetConfig.dump()

        return WOK(methodName)

    def get(self, setup, group, route):
        if not self._pathResult.ok:
            raise self._pathResult
        methodName = "web_%s" % self._pathResult.data
        try:
            mObj = getattr(self, methodName)
        except AttributeError as e:
            self.log.fatal("This should not happen!! %s" % e)
            raise tornado.web.HTTPError(404)
        return mObj()

    def post(self, *args, **kwargs):
        return self.get(*args, **kwargs)

    def write_error(self, status_code, **kwargs):
        self.set_status(status_code)
        cType = "text/plain"
        data = self._reason
        if 'exc_info' in kwargs:
            ex = kwargs['exc_info'][1]
            trace = traceback.format_exception(*kwargs["exc_info"])
            if not isinstance(ex, WErr):
                data += "\n".join(trace)
            else:
                if self.settings.get("debug"):
                    self.log.error("Request ended in error:\n  %s" %
                                   "\n  ".join(trace))
                data = ex.msg
                if type(data) == types.DictType:
                    cType = "application/json"
                    data = json.dumps(data)
        self.set_header('Content-Type', cType)
        self.finish(data)
Esempio n. 2
0
class WebHandler( tornado.web.RequestHandler ):

  __disetConfig = ThreadConfig()
  __log = False

  #Auth requirements
  AUTH_PROPS = None
  #Location of the handler in the URL
  LOCATION = ""
  #URL Schema with holders to generate handler urls
  URLSCHEMA = ""
  #RE to extract group and setup
  PATH_RE = ""

  #Helper function to create threaded gen.Tasks with automatic callback and execption handling
  def threadTask( self, method, *args, **kwargs ):
    """
    Helper method to generate a gen.Task and automatically call the callback when the real
    method ends. THIS IS SPARTAAAAAAAAAA. SPARTA has improved using futures ;)
    """
    #Save the task to access the runner
    genTask = False

    #This runs in the separate thread, calls the callback on finish and takes into account exceptions
    def cbMethod( *cargs, **ckwargs ):
      cb = ckwargs.pop( 'callback' )
      method = cargs[0]
      disetConf = cargs[1]
      cargs = cargs[2]
      self.__disetConfig.reset()
      self.__disetConfig.load( disetConf )
      ioloop = tornado.ioloop.IOLoop.instance()
      try:
        result = method( *cargs, **ckwargs )
        ioloop.add_callback( functools.partial( cb, result ) )
      except Exception as excp:
        gLogger.error( "Following exception occured %s" % excp )
        exc_info = sys.exc_info()
        genTask.set_exc_info( exc_info )
        ioloop.add_callback( lambda : genTask.exception() )
        
    #Put the task in the thread :)
    def threadJob( tmethod, *targs, **tkwargs ):
      tkwargs[ 'callback' ] = tornado.stack_context.wrap( tkwargs[ 'callback' ] )
      targs = ( tmethod, self.__disetDump, targs )
      gThreadPool.submit( cbMethod, *targs, **tkwargs )
      
    #Return a YieldPoint
    genTask = tornado.gen.Task( threadJob, method, *args, **kwargs )
    return genTask

  def __disetBlockDecor( self, func ):
    def wrapper( *args, **kwargs ):
      raise RuntimeError( "All DISET calls must be made from inside a Threaded Task! Bad boy!" )
    return wrapper


  def __init__( self, *args, **kwargs ):
    """
    Initialize the handler
    """
    super( WebHandler, self ).__init__( *args, **kwargs )
    if not WebHandler.__log:
      WebHandler.__log = gLogger.getSubLogger( self.__class__.__name__ )
    self.__credDict = {}
    self.__setup = Conf.setup()
    self.__processCredentials()
    self.__disetConfig.reset()
    self.__disetConfig.setDecorator( self.__disetBlockDecor )
    self.__disetDump = self.__disetConfig.dump()
    match = self.PATH_RE.match( self.request.path )
    self._pathResult = self.__checkPath( *match.groups() )
    self.__sessionData = SessionData( self.__credDict, self.__setup )

  def __processCredentials( self ):
    """
    Extract the user credentials based on the certificate or what comes from the balancer
    """
    #NGINX
    if Conf.balancer() == "nginx":
      headers = self.request.headers
      if headers[ 'X-Scheme' ] == "https" and headers[ 'X-Ssl_client_verify' ] == 'SUCCESS':
        DN = headers[ 'X-Ssl_client_s_dn' ]
        self.__credDict[ 'DN' ] = DN
        self.__credDict[ 'issuer' ] = headers[ 'X-Ssl_client_i_dn' ]
        result = Registry.getUsernameForDN( DN )
        if not result[ 'OK' ]:
          self.__credDict[ 'validDN' ] = False
        else:
          self.__credDict[ 'validDN' ] = True
          self.__credDict[ 'username' ] = result[ 'Value' ]
      return
    #TORNADO
    if not self.request.protocol == "https":
      return
    derCert = self.request.get_ssl_certificate( binary_form = True )
    if not derCert:
      return
    pemCert = ssl.DER_cert_to_PEM_cert( derCert )
    chain = X509Chain()
    chain.loadChainFromString( pemCert )
    result = chain.getCredentials()
    if not result[ 'OK' ]:
      self.log.error( "Could not get client credentials %s" % result[ 'Message' ] )
      return
    self.__credDict = result[ 'Value' ]
    #Hack. Data coming from OSSL directly and DISET difer in DN/subject
    try:
      self.__credDict[ 'DN' ] = self.__credDict[ 'subject' ]
    except KeyError:
      pass

  def _request_summary( self ):
    """
    Return a string returning the summary of the request
    """
    summ = super( WebHandler, self )._request_summary()
    cl = []
    if self.__credDict.get( 'validDN', False ):
      cl.append( self.__credDict[ 'username' ] )
      if self.__credDict.get( 'validGroup', False ):
        cl.append( "@%s" % self.__credDict[ 'group' ] )
      cl.append( " (%s)" % self.__credDict[ 'DN' ] )
    summ = "%s %s" % ( summ, "".join( cl ) )
    return summ

  @property
  def log( self ):
    return self.__log

  @classmethod
  def getLog( cls ):
    return cls.__log

  def getUserDN( self ):
    return self.__credDict.get( 'DN', '' )

  def getUserName( self ):
    return self.__credDict.get( 'username', '' )

  def getUserGroup( self ):
    return self.__credDict.get( 'group', '' )

  def getUserSetup( self ):
    return self.__setup

  def getUserProperties( self ):
    return self.__sessionData.getData().properties

  def isRegisteredUser( self ):
    return self.__credDict.get( 'validDN', "" ) and self.__credDict.get( 'validGroup', "" )

  def getSessionData( self ):
    return self.__sessionData.getData()
  
  def actionURL( self, action = "" ):
    """
    Given an action name for the handler, return the URL
    """
    if action == "index":
      action = ""
    group = self.getUserGroup()
    if group:
      group = "/g:%s" % group
    setup = self.getUserSetup()
    if setup:
      setup = "/s:%s" % setup
    location = self.LOCATION
    if location:
      location = "/%s" % location
    ats = dict( action = action, group = group, setup = setup, location = location )
    return self.URLSCHEMA % ats

  def __auth( self, handlerRoute, group ):
    """
    Authenticate request
    """
    userDN = self.getUserDN()
    if group:
      self.__credDict[ 'group' ] = group
    else:
      if userDN:
        result = Registry.findDefaultGroupForDN( userDN )
        if result[ 'OK' ]:
          self.__credDict[ 'group' ] = result[ 'Value' ]
    self.__credDict[ 'validGroup' ] = False

    if type( self.AUTH_PROPS ) not in ( types.ListType, types.TupleType ):
      self.AUTH_PROPS = [ p.strip() for p in self.AUTH_PROPS.split( "," ) if p.strip() ]
    allAllowed = False
    for p in self.AUTH_PROPS:
      if p.lower() in ( 'all', 'any' ):
        allAllowed = True

    auth = AuthManager( Conf.getAuthSectionForHandler( handlerRoute ) )
    ok = auth.authQuery( "", self.__credDict, self.AUTH_PROPS )
    if ok:
      if userDN:
        self.__credDict[ 'validGroup' ] = True
        self.log.info( "AUTH OK: %s by %s@%s (%s)" % ( handlerRoute, self.__credDict[ 'username' ], self.__credDict[ 'group' ], userDN ) )
      else:
        self.__credDict[ 'validDN' ] = False
        self.log.info( "AUTH OK: %s by visitor" % ( handlerRoute ) )
    elif allAllowed:
      self.log.info( "AUTH ALL: %s by %s" % ( handlerRoute, userDN ) )
      ok = True
    else:
      self.log.info( "AUTH KO: %s by %s@%s" % ( handlerRoute, userDN, group ) )
    return ok

  def __checkPath( self, setup, group, route ):
    """
    Check the request, auth, credentials and DISET config
    """
    if route[-1] == "/":
      methodName = "index"
      handlerRoute = route
    else:
      iP = route.rfind( "/" )
      methodName = route[ iP + 1: ]
      handlerRoute = route[ :iP ]
    if setup:
      self.__setup = setup
    if not self.__auth( handlerRoute, group ):
      return WErr( 401, "Unauthorized, bad boy!" )

    DN = self.getUserDN()
    if DN:
      self.__disetConfig.setDN( DN )
    group = self.getUserGroup()
    if group:
      self.__disetConfig.setGroup( group )
    self.__disetConfig.setSetup( setup )
    self.__disetDump = self.__disetConfig.dump()

    return WOK( methodName )

  def get( self, setup, group, route ):
    if not self._pathResult.ok:
      raise self._pathResult
    methodName = "web_%s" % self._pathResult.data
    try:
      mObj = getattr( self, methodName )
    except AttributeError as e:
      self.log.fatal( "This should not happen!! %s" % e )
      raise tornado.web.HTTPError( 404 )
    return mObj()

  def post( self, *args, **kwargs ):
    return self.get( *args, **kwargs )


  def write_error( self, status_code, **kwargs ):
    self.set_status( status_code )
    cType = "text/plain"
    data = self._reason
    if 'exc_info' in kwargs:
      ex = kwargs[ 'exc_info' ][1]
      trace = traceback.format_exception( *kwargs["exc_info"] )
      if not isinstance( ex, WErr ):
        data += "\n".join( trace )
      else:
        if self.settings.get("debug"):
          self.log.error( "Request ended in error:\n  %s" % "\n  ".join( trace ) )
        data = ex.msg
        if type( data ) == types.DictType:
          cType = "application/json"
          data = json.dumps( data )
    self.set_header( 'Content-Type', cType )
    self.finish( data )
Esempio n. 3
0
class WebHandler(tornado.web.RequestHandler):

    __disetConfig = ThreadConfig()
    __log = False

    # Auth requirements
    AUTH_PROPS = None
    # Location of the handler in the URL
    LOCATION = ""
    # URL Schema with holders to generate handler urls
    URLSCHEMA = ""
    # RE to extract group and setup
    PATH_RE = ""

    def threadTask(self, method, *args, **kwargs):
        if tornado.version < '5.0.0':
            return self.threadTaskOld(method, *args, **kwargs)
        else:
            return self.threadTaskExecutor(method, *args, **kwargs)

    # Helper function to create threaded gen.Tasks with automatic callback and execption handling
    @deprecated("Only for Tornado 4.x.x and DIRAC v6r20")
    def threadTaskOld(self, method, *args, **kwargs):
        """
    Helper method to generate a gen.Task and automatically call the callback when the real
    method ends. THIS IS SPARTAAAAAAAAAA. SPARTA has improved using futures ;)
    """
        # Save the task to access the runner
        genTask = False

        # This runs in the separate thread, calls the callback on finish and takes into account exceptions
        def cbMethod(*cargs, **ckwargs):
            cb = ckwargs.pop('callback')
            method = cargs[0]
            disetConf = cargs[1]
            cargs = cargs[2]
            self.__disetConfig.reset()
            self.__disetConfig.load(disetConf)
            ioloop = tornado.ioloop.IOLoop.instance()
            try:
                result = method(*cargs, **ckwargs)
                ioloop.add_callback(functools.partial(cb, result))
            except Exception as excp:
                gLogger.error("Following exception occured %s" % excp)
                exc_info = sys.exc_info()
                genTask.set_exc_info(exc_info)
                ioloop.add_callback(lambda: genTask.exception())

        # Put the task in the thread :)
        def threadJob(tmethod, *targs, **tkwargs):
            tkwargs['callback'] = tornado.stack_context.wrap(
                tkwargs['callback'])
            targs = (tmethod, self.__disetDump, targs)
            gThreadPool.submit(cbMethod, *targs, **tkwargs)

        # Return a YieldPoint
        genTask = tornado.gen.Task(threadJob, method, *args, **kwargs)
        return genTask

    def threadTaskExecutor(self, method, *args, **kwargs):
        def threadJob(*targs, **tkwargs):
            args = targs[0]
            disetConf = targs[1]
            self.__disetConfig.reset()
            self.__disetConfig.load(disetConf)
            return method(*args, **tkwargs)

        targs = (args, self.__disetDump)
        return tornado.ioloop.IOLoop.current().run_in_executor(
            gThreadPool, functools.partial(threadJob, *targs, **kwargs))

    def __disetBlockDecor(self, func):
        def wrapper(*args, **kwargs):
            raise RuntimeError(
                "All DISET calls must be made from inside a Threaded Task!")

        return wrapper

    def __init__(self, *args, **kwargs):
        """
    Initialize the handler
    """
        super(WebHandler, self).__init__(*args, **kwargs)
        if not WebHandler.__log:
            WebHandler.__log = gLogger.getSubLogger(self.__class__.__name__)
        self.__credDict = {}
        self.__setup = Conf.setup()
        self.__processCredentials()
        self.__disetConfig.reset()
        self.__disetConfig.setDecorator(self.__disetBlockDecor)
        self.__disetDump = self.__disetConfig.dump()
        match = self.PATH_RE.match(self.request.path)
        self._pathResult = self.__checkPath(*match.groups())
        self.__sessionData = SessionData(self.__credDict, self.__setup)

    def __processCredentials(self):
        """
    Extract the user credentials based on the certificate or what comes from the balancer
    """

        if not self.request.protocol == "https":
            return

        # OIDC auth method
        def oAuth2():
            if self.get_secure_cookie("AccessToken"):
                access_token = self.get_secure_cookie("AccessToken")
                url = Conf.getCSValue(
                    "TypeAuths/%s/authority" % typeAuth) + '/userinfo'
                heads = {
                    'Authorization': 'Bearer ' + access_token,
                    'Content-Type': 'application/json'
                }
                if 'error' in requests.get(url, headers=heads,
                                           verify=False).json():
                    self.log.error('OIDC request error: %s' % requests.get(
                        url, headers=heads, verify=False).json()['error'])
                    return
                ID = requests.get(url, headers=heads,
                                  verify=False).json()['sub']
                result = getUsernameForID(ID)
                if result['OK']:
                    self.__credDict['username'] = result['Value']
                result = getDNForUsername(self.__credDict['username'])
                if result['OK']:
                    self.__credDict['validDN'] = True
                    self.__credDict['DN'] = result['Value'][0]
                result = getCAForUsername(self.__credDict['username'])
                if result['OK']:
                    self.__credDict['issuer'] = result['Value'][0]
                return

        # Type of Auth
        if not self.get_secure_cookie("TypeAuth"):
            self.set_secure_cookie("TypeAuth", 'Certificate')
        typeAuth = self.get_secure_cookie("TypeAuth")
        self.log.info("Type authentication: %s" % str(typeAuth))
        if typeAuth == "Visitor":
            return
        retVal = Conf.getCSSections("TypeAuths")
        if retVal['OK']:
            if typeAuth in retVal.get("Value"):
                method = Conf.getCSValue("TypeAuths/%s/method" % typeAuth,
                                         'default')
                if method == "oAuth2":
                    oAuth2()

        # NGINX
        if Conf.balancer() == "nginx":
            headers = self.request.headers
            if headers['X-Scheme'] == "https" and headers[
                    'X-Ssl_client_verify'] == 'SUCCESS':
                DN = headers['X-Ssl_client_s_dn']
                if not DN.startswith('/'):
                    items = DN.split(',')
                    items.reverse()
                    DN = '/' + '/'.join(items)
                self.__credDict['DN'] = DN
                self.__credDict['issuer'] = headers['X-Ssl_client_i_dn']
                result = Registry.getUsernameForDN(DN)
                if not result['OK']:
                    self.__credDict['validDN'] = False
                else:
                    self.__credDict['validDN'] = True
                    self.__credDict['username'] = result['Value']
            return

        # TORNADO
        derCert = self.request.get_ssl_certificate(binary_form=True)
        if not derCert:
            return
        pemCert = ssl.DER_cert_to_PEM_cert(derCert)
        chain = X509Chain()
        chain.loadChainFromString(pemCert)
        result = chain.getCredentials()
        if not result['OK']:
            self.log.error("Could not get client credentials %s" %
                           result['Message'])
            return
        self.__credDict = result['Value']
        # Hack. Data coming from OSSL directly and DISET difer in DN/subject
        try:
            self.__credDict['DN'] = self.__credDict['subject']
        except KeyError:
            pass

    def _request_summary(self):
        """
    Return a string returning the summary of the request
    """
        summ = super(WebHandler, self)._request_summary()
        cl = []
        if self.__credDict.get('validDN', False):
            cl.append(self.__credDict['username'])
            if self.__credDict.get('validGroup', False):
                cl.append("@%s" % self.__credDict['group'])
            cl.append(" (%s)" % self.__credDict['DN'])
        summ = "%s %s" % (summ, "".join(cl))
        return summ

    @property
    def log(self):
        return self.__log

    @classmethod
    def getLog(cls):
        return cls.__log

    def getUserDN(self):
        return self.__credDict.get('DN', '')

    def getUserName(self):
        return self.__credDict.get('username', '')

    def getUserGroup(self):
        return self.__credDict.get('group', '')

    def getUserSetup(self):
        return self.__setup

    def getUserProperties(self):
        return self.__sessionData.getData().properties

    def isRegisteredUser(self):
        return self.__credDict.get('validDN', "") and self.__credDict.get(
            'validGroup', "")

    def getSessionData(self):
        return self.__sessionData.getData()

    def actionURL(self, action=""):
        """
    Given an action name for the handler, return the URL
    """
        if action == "index":
            action = ""
        group = self.getUserGroup()
        if group:
            group = "/g:%s" % group
        setup = self.getUserSetup()
        if setup:
            setup = "/s:%s" % setup
        location = self.LOCATION
        if location:
            location = "/%s" % location
        ats = dict(action=action, group=group, setup=setup, location=location)
        return self.URLSCHEMA % ats

    def __auth(self, handlerRoute, group, method):
        """
    Authenticate request
    :param str handlerRoute: the name of the handler
    :param str group: DIRAC group
    :param str method: the name of the method
    :return: bool
    """
        userDN = self.getUserDN()
        if group:
            self.__credDict['group'] = group
        else:
            if userDN:
                result = Registry.findDefaultGroupForDN(userDN)
                if result['OK']:
                    self.__credDict['group'] = result['Value']
        self.__credDict['validGroup'] = False

        if type(self.AUTH_PROPS) not in (types.ListType, types.TupleType):
            self.AUTH_PROPS = [
                p.strip() for p in self.AUTH_PROPS.split(",") if p.strip()
            ]

        auth = AuthManager(Conf.getAuthSectionForHandler(handlerRoute))
        ok = auth.authQuery(method, self.__credDict, self.AUTH_PROPS)
        if ok:
            if userDN:
                self.__credDict['validGroup'] = True
                self.log.info("AUTH OK: %s by %s@%s (%s)" %
                              (handlerRoute, self.__credDict['username'],
                               self.__credDict['group'], userDN))
            else:
                self.__credDict['validDN'] = False
                self.log.info("AUTH OK: %s by visitor" % (handlerRoute))
        elif self.isTrustedHost(self.__credDict.get('DN')):
            self.log.info("Request is coming from Trusted host")
            return True
        else:
            self.log.info("AUTH KO: %s by %s@%s" %
                          (handlerRoute, userDN, group))
        return ok

    def isTrustedHost(self, dn):
        """
    Check if the request coming from a TrustedHost
    :param str dn: certificate DN
    :return: bool if the host is Trusrted it return true otherwise false
    """
        retVal = CS.getHostnameForDN(dn)
        if retVal['OK']:
            hostname = retVal['Value']
            if Properties.TRUSTED_HOST in CS.getPropertiesForHost(
                    hostname, []):
                return True
        return False

    def __checkPath(self, setup, group, route):
        """
    Check the request, auth, credentials and DISET config
    """
        if route[-1] == "/":
            methodName = "index"
            handlerRoute = route
        else:
            iP = route.rfind("/")
            methodName = route[iP + 1:]
            handlerRoute = route[:iP]
        if setup:
            self.__setup = setup
        if not self.__auth(handlerRoute, group, methodName):
            return WErr(401, "Unauthorized.")

        DN = self.getUserDN()
        if DN:
            self.__disetConfig.setDN(DN)
        group = self.getUserGroup()
        if group:
            self.__disetConfig.setGroup(group)
        self.__disetConfig.setSetup(setup)
        self.__disetDump = self.__disetConfig.dump()

        return WOK(methodName)

    def get(self, setup, group, route):
        if not self._pathResult.ok:
            raise self._pathResult
        methodName = "web_%s" % self._pathResult.data
        try:
            mObj = getattr(self, methodName)
        except AttributeError as e:
            self.log.fatal("This should not happen!! %s" % e)
            raise tornado.web.HTTPError(404)
        return mObj()

    def post(self, *args, **kwargs):
        return self.get(*args, **kwargs)

    def write_error(self, status_code, **kwargs):
        self.set_status(status_code)
        cType = "text/plain"
        data = self._reason
        if 'exc_info' in kwargs:
            ex = kwargs['exc_info'][1]
            trace = traceback.format_exception(*kwargs["exc_info"])
            if not isinstance(ex, WErr):
                data += "\n".join(trace)
            else:
                if self.settings.get("debug"):
                    self.log.error("Request ended in error:\n  %s" %
                                   "\n  ".join(trace))
                data = ex.msg
                if isinstance(data, dict):
                    cType = "application/json"
                    data = json.dumps(data)
        self.set_header('Content-Type', cType)
        self.finish(data)
Esempio n. 4
0
class WebHandler(tornado.web.RequestHandler):

  __disetConfig = ThreadConfig()
  __log = False

  # Auth requirements
  AUTH_PROPS = None
  # Location of the handler in the URL
  LOCATION = ""
  # URL Schema with holders to generate handler urls
  URLSCHEMA = ""
  # RE to extract group and setup
  PATH_RE = ""

  def threadTask(self, method, *args, **kwargs):
    if tornado.version < '5.0.0':
      return self.threadTaskOld(method, *args, **kwargs)
    else:
      return self.threadTaskExecutor(method, *args, **kwargs)

  # Helper function to create threaded gen.Tasks with automatic callback and execption handling
  @deprecated("Only for Tornado 4.x.x and DIRAC v6r20")
  def threadTaskOld(self, method, *args, **kwargs):
    """
    Helper method to generate a gen.Task and automatically call the callback when the real
    method ends. THIS IS SPARTAAAAAAAAAA. SPARTA has improved using futures ;)
    """
    # Save the task to access the runner
    genTask = False

    # This runs in the separate thread, calls the callback on finish and takes into account exceptions
    def cbMethod(*cargs, **ckwargs):
      cb = ckwargs.pop('callback')
      method = cargs[0]
      disetConf = cargs[1]
      cargs = cargs[2]
      self.__disetConfig.reset()
      self.__disetConfig.load(disetConf)
      ioloop = tornado.ioloop.IOLoop.instance()
      try:
        result = method(*cargs, **ckwargs)
        ioloop.add_callback(functools.partial(cb, result))
      except Exception as excp:
        gLogger.error("Following exception occured %s" % excp)
        exc_info = sys.exc_info()
        genTask.set_exc_info(exc_info)
        ioloop.add_callback(lambda: genTask.exception())

    # Put the task in the thread :)
    def threadJob(tmethod, *targs, **tkwargs):
      tkwargs['callback'] = tornado.stack_context.wrap(tkwargs['callback'])
      targs = (tmethod, self.__disetDump, targs)
      gThreadPool.submit(cbMethod, *targs, **tkwargs)

    # Return a YieldPoint
    genTask = tornado.gen.Task(threadJob, method, *args, **kwargs)
    return genTask

  def threadTaskExecutor(self, method, *args, **kwargs):
    def threadJob(*targs, **tkwargs):
      args = targs[0]
      disetConf = targs[1]
      self.__disetConfig.reset()
      self.__disetConfig.load(disetConf)
      return method(*args, **tkwargs)
    targs = (args, self.__disetDump)
    return tornado.ioloop.IOLoop.current().run_in_executor(gThreadPool, functools.partial(threadJob, *targs, **kwargs))

  def __disetBlockDecor(self, func):
    def wrapper(*args, **kwargs):
      raise RuntimeError("All DISET calls must be made from inside a Threaded Task!")
    return wrapper

  def __init__(self, *args, **kwargs):
    """
    Initialize the handler
    """
    super(WebHandler, self).__init__(*args, **kwargs)
    if not WebHandler.__log:
      WebHandler.__log = gLogger.getSubLogger(self.__class__.__name__)
    self.__credDict = {}
    self.__setup = Conf.setup()
    self.__processCredentials()
    self.__disetConfig.reset()
    self.__disetConfig.setDecorator(self.__disetBlockDecor)
    self.__disetDump = self.__disetConfig.dump()
    match = self.PATH_RE.match(self.request.path)
    self._pathResult = self.__checkPath(*match.groups())
    self.__sessionData = SessionData(self.__credDict, self.__setup)

  def __processCredentials(self):
    """
    Extract the user credentials based on the certificate or what comes from the balancer
    """

    if not self.request.protocol == "https":
      return

    # OIDC auth method
    def oAuth2():
      if self.get_secure_cookie("AccessToken"):
        access_token = self.get_secure_cookie("AccessToken")
        url = Conf.getCSValue("TypeAuths/%s/authority" % typeAuth) + '/userinfo'
        heads = {'Authorization': 'Bearer ' + access_token, 'Content-Type': 'application/json'}
        if 'error' in requests.get(url, headers=heads, verify=False).json():
          self.log.error('OIDC request error: %s' % requests.get(url, headers=heads, verify=False).json()['error'])
          return
        ID = requests.get(url, headers=heads, verify=False).json()['sub']
        result = getUsernameForID(ID)
        if result['OK']:
          self.__credDict['username'] = result['Value']
        result = getDNForUsername(self.__credDict['username'])
        if result['OK']:
          self.__credDict['validDN'] = True
          self.__credDict['DN'] = result['Value'][0]
        result = getCAForUsername(self.__credDict['username'])
        if result['OK']:
          self.__credDict['issuer'] = result['Value'][0]
        return

    # Type of Auth
    if not self.get_secure_cookie("TypeAuth"):
      self.set_secure_cookie("TypeAuth", 'Certificate')
    typeAuth = self.get_secure_cookie("TypeAuth")
    self.log.info("Type authentication: %s" % str(typeAuth))
    if typeAuth == "Visitor":
      return
    retVal = Conf.getCSSections("TypeAuths")
    if retVal['OK']:
      if typeAuth in retVal.get("Value"):
        method = Conf.getCSValue("TypeAuths/%s/method" % typeAuth, 'default')
        if method == "oAuth2":
          oAuth2()

    # NGINX
    if Conf.balancer() == "nginx":
      headers = self.request.headers
      if headers['X-Scheme'] == "https" and headers['X-Ssl_client_verify'] == 'SUCCESS':
        DN = headers['X-Ssl_client_s_dn']
        if not DN.startswith('/'):
          items = DN.split(',')
          items.reverse()
          DN = '/' + '/'.join(items)
        self.__credDict['DN'] = DN
        self.__credDict['issuer'] = headers['X-Ssl_client_i_dn']
        result = Registry.getUsernameForDN(DN)
        if not result['OK']:
          self.__credDict['validDN'] = False
        else:
          self.__credDict['validDN'] = True
          self.__credDict['username'] = result['Value']
      return

    # TORNADO
    derCert = self.request.get_ssl_certificate(binary_form=True)
    if not derCert:
      return
    pemCert = ssl.DER_cert_to_PEM_cert(derCert)
    chain = X509Chain()
    chain.loadChainFromString(pemCert)
    result = chain.getCredentials()
    if not result['OK']:
      self.log.error("Could not get client credentials %s" % result['Message'])
      return
    self.__credDict = result['Value']
    # Hack. Data coming from OSSL directly and DISET difer in DN/subject
    try:
      self.__credDict['DN'] = self.__credDict['subject']
    except KeyError:
      pass

  def _request_summary(self):
    """
    Return a string returning the summary of the request
    """
    summ = super(WebHandler, self)._request_summary()
    cl = []
    if self.__credDict.get('validDN', False):
      cl.append(self.__credDict['username'])
      if self.__credDict.get('validGroup', False):
        cl.append("@%s" % self.__credDict['group'])
      cl.append(" (%s)" % self.__credDict['DN'])
    summ = "%s %s" % (summ, "".join(cl))
    return summ

  @property
  def log(self):
    return self.__log

  @classmethod
  def getLog(cls):
    return cls.__log

  def getUserDN(self):
    return self.__credDict.get('DN', '')

  def getUserName(self):
    return self.__credDict.get('username', '')

  def getUserGroup(self):
    return self.__credDict.get('group', '')

  def getUserSetup(self):
    return self.__setup

  def getUserProperties(self):
    return self.__sessionData.getData().properties

  def isRegisteredUser(self):
    return self.__credDict.get('validDN', "") and self.__credDict.get('validGroup', "")

  def getSessionData(self):
    return self.__sessionData.getData()

  def actionURL(self, action=""):
    """
    Given an action name for the handler, return the URL
    """
    if action == "index":
      action = ""
    group = self.getUserGroup()
    if group:
      group = "/g:%s" % group
    setup = self.getUserSetup()
    if setup:
      setup = "/s:%s" % setup
    location = self.LOCATION
    if location:
      location = "/%s" % location
    ats = dict(action=action, group=group, setup=setup, location=location)
    return self.URLSCHEMA % ats

  def __auth(self, handlerRoute, group, method):
    """
    Authenticate request
    :param str handlerRoute: the name of the handler
    :param str group: DIRAC group
    :param str method: the name of the method
    :return: bool
    """
    userDN = self.getUserDN()
    if group:
      self.__credDict['group'] = group
    else:
      if userDN:
        result = Registry.findDefaultGroupForDN(userDN)
        if result['OK']:
          self.__credDict['group'] = result['Value']
    self.__credDict['validGroup'] = False

    if type(self.AUTH_PROPS) not in (types.ListType, types.TupleType):
      self.AUTH_PROPS = [p.strip() for p in self.AUTH_PROPS.split(",") if p.strip()]

    auth = AuthManager(Conf.getAuthSectionForHandler(handlerRoute))
    ok = auth.authQuery(method, self.__credDict, self.AUTH_PROPS)
    if ok:
      if userDN:
        self.__credDict['validGroup'] = True
        self.log.info("AUTH OK: %s by %s@%s (%s)" %
                      (handlerRoute, self.__credDict['username'], self.__credDict['group'], userDN))
      else:
        self.__credDict['validDN'] = False
        self.log.info("AUTH OK: %s by visitor" % (handlerRoute))
    elif self.isTrustedHost(self.__credDict.get('DN')):
      self.log.info("Request is coming from Trusted host")
      return True
    else:
      self.log.info("AUTH KO: %s by %s@%s" % (handlerRoute, userDN, group))
    return ok

  def isTrustedHost(self, dn):
    """
    Check if the request coming from a TrustedHost
    :param str dn: certificate DN
    :return: bool if the host is Trusrted it return true otherwise false
    """
    retVal = CS.getHostnameForDN(dn)
    if retVal['OK']:
      hostname = retVal['Value']
      if Properties.TRUSTED_HOST in CS.getPropertiesForHost(hostname, []):
        return True
    return False

  def __checkPath(self, setup, group, route):
    """
    Check the request, auth, credentials and DISET config
    """
    if route[-1] == "/":
      methodName = "index"
      handlerRoute = route
    else:
      iP = route.rfind("/")
      methodName = route[iP + 1:]
      handlerRoute = route[:iP]
    if setup:
      self.__setup = setup
    if not self.__auth(handlerRoute, group, methodName):
      return WErr(401, "Unauthorized.")

    DN = self.getUserDN()
    if DN:
      self.__disetConfig.setDN(DN)
    group = self.getUserGroup()
    if group:
      self.__disetConfig.setGroup(group)
    self.__disetConfig.setSetup(setup)
    self.__disetDump = self.__disetConfig.dump()

    return WOK(methodName)

  def get(self, setup, group, route):
    if not self._pathResult.ok:
      raise self._pathResult
    methodName = "web_%s" % self._pathResult.data
    try:
      mObj = getattr(self, methodName)
    except AttributeError as e:
      self.log.fatal("This should not happen!! %s" % e)
      raise tornado.web.HTTPError(404)
    return mObj()

  def post(self, *args, **kwargs):
    return self.get(*args, **kwargs)

  def write_error(self, status_code, **kwargs):
    self.set_status(status_code)
    cType = "text/plain"
    data = self._reason
    if 'exc_info' in kwargs:
      ex = kwargs['exc_info'][1]
      trace = traceback.format_exception(*kwargs["exc_info"])
      if not isinstance(ex, WErr):
        data += "\n".join(trace)
      else:
        if self.settings.get("debug"):
          self.log.error("Request ended in error:\n  %s" % "\n  ".join(trace))
        data = ex.msg
        if isinstance(data, dict):
          cType = "application/json"
          data = json.dumps(data)
    self.set_header('Content-Type', cType)
    self.finish(data)