<commit_before>###############################################################################
##
##  Copyright (C) 2014 Tavendo GmbH
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU Affero General Public License, version 3,
##  as published by the Free Software Foundation.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
##  GNU Affero General Public License for more details.
##
##  You should have received a copy of the GNU Affero General Public License
##  along with this program. If not, see <http://www.gnu.org/licenses/>.
##
###############################################################################

from __future__ import absolute_import

__all__ = (
   'CrossbarRouterSessionFactory',
   'CrossbarRouterFactory',
   'CrossbarRouterServiceSession'
)

import json
import datetime
import traceback
from pytrie import StringTrie
from collections import namedtuple

from six.moves import urllib

from twisted.python import log
from twisted.python.failure import Failure
from twisted.internet.defer import Deferred, inlineCallbacks

from autobahn import util
from autobahn.websocket import http
from autobahn.websocket.compress import *

from autobahn import wamp
from autobahn.wamp import types
from autobahn.wamp import message
from autobahn.wamp.exception import ApplicationError
from autobahn.twisted.wamp import ApplicationSession

from crossbar.router.router import Router, RouterFactory, RouterSession, RouterSessionFactory

import crossbar

from crossbar.router.types import RouterOptions
from crossbar.router.interfaces import IRouter

from crossbar.router.auth import PendingAuthPersona, \
                                 PendingAuthWampCra, \
                                 PendingAuthTicket



class CrossbarRouterSession(RouterSession):
   """
   Router-side of (non-embedded) Crossbar.io WAMP sessions.
   """

   def onOpen(self, transport):
      """
      Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onOpen`
      """
      RouterSession.onOpen(self, transport)

      if hasattr(self._transport, 'factory') and hasattr(self._transport.factory, '_config'):
         self._transport_config = self._transport.factory._config
      else:
         self._transport_config = {}

      self._pending_auth = None
      self._session_details = None
      self._service_session = None


   def onHello(self, realm, details):

      try:

         ## check if the realm the session wants to join actually exists
         ##
         if realm not in self._router_factory:
            return types.Deny(ApplicationError.NO_SUCH_REALM, message = "no realm '{}' exists on this router".format(realm))

         ## perform authentication
         ##
         if self._transport._authid is not None:

            ## already authenticated .. e.g. via cookie

            ## check if role still exists on realm
            ##
            allow = self._router_factory[realm].has_role(self._transport._authrole)

            if allow:
               return types.Accept(authid = self._transport._authid,
                                   authrole = self._transport._authrole,
                                   authmethod = self._transport._authmethod,
                                   authprovider = 'transport')
            else:
               return types.Deny(ApplicationError.NO_SUCH_ROLE, message = "session was previously authenticated (via transport), but role '{}' no longer exists on realm '{}'".format(self._transport._authrole, realm))

         else:
            ## if authentication is enabled on the transport ..
            ##
            if "auth" in self._transport_config:

               ## iterate over authentication methods announced by client ..
               ##
               for authmethod in details.authmethods or ["anonymous"]:

                  ## .. and if the configuration has an entry for the authmethod
                  ## announced, process ..
                  if authmethod in self._transport_config["auth"]:

                     ## "WAMP-Challenge-Response" authentication
                     ##
                     if authmethod == u"wampcra":
                        cfg = self._transport_config['auth']['wampcra']

                        if cfg['type'] == 'static':
                           if details.authid in cfg.get('users', {}):
                              user = cfg['users'][details.authid]

                              self._pending_auth = PendingAuthWampCra(details.pending_session, details.authid, user['role'], u'static', user['secret'].encode('utf8'))

                              ## send challenge to client
                              ##
                              extra = {
                                 u'challenge': self._pending_auth.challenge
                              }

                              ## when using salted passwords, provide the client with
                              ## the salt and then PBKDF2 parameters used
                              if 'salt' in user:
                                 extra[u'salt'] = user['salt']
                                 extra[u'iterations'] = user.get('iterations', 1000)
                                 extra[u'keylen'] = user.get('keylen', 32)

                              return types.Challenge(u'wampcra', extra)

                           else:
                              return types.Deny(message = "no user with authid '{}' in user database".format(details.authid))

                        elif cfg['type'] == 'dynamic':

                           ## call the configured dynamic authenticator procedure
                           ## via the router's service session
                           ##
                           service_session = self._router_factory.get(realm)._realm.session
                           session_details = {
                              ## forward transport level details of the WAMP session that
                              ## wishes to authenticate
                              'transport': self._transport._transport_info,

                              ## the following WAMP session ID will be assigned to the session
                              ## if (and only if) the subsequent authentication succeeds.
                              'session': self._pending_session_id
                           }
                           d = service_session.call(cfg['authenticator'], realm, details.authid, session_details)

                           def on_authenticate_ok(user):

                              ## construct a pending WAMP-CRA authentication
                              ##
                              self._pending_auth = PendingAuthWampCra(details.pending_session, details.authid, user['role'], u'dynamic', user['secret'].encode('utf8'))

                              ## send challenge to client
                              ##
                              extra = {
                                 u'challenge': self._pending_auth.challenge
                              }

                              ## when using salted passwords, provide the client with
                              ## the salt and the PBKDF2 parameters used
                              ##
                              if 'salt' in user:
                                 extra[u'salt'] = user['salt']
                                 extra[u'iterations'] = user.get('iterations', 1000)
                                 extra[u'keylen'] = user.get('keylen', 32)

                              return types.Challenge(u'wampcra', extra)

                           def on_authenticate_error(err):
                              error = None
                              message = "dynamic WAMP-CRA credential getter failed: {}".format(err)

                              if isinstance(err.value, ApplicationError):
                                 error = err.value.error
                                 if err.value.args and len(err.value.args):
                                    message = err.value.args[0]

                              return types.Deny(error, message)


                           d.addCallbacks(on_authenticate_ok, on_authenticate_error)

                           return d

                        else:

                           return types.Deny(message = "illegal WAMP-CRA authentication config (type '{0}' is unknown)".format(cfg['type']))


                     ## WAMP-Ticket authentication
                     ##
                     elif authmethod == u"ticket":
                        cfg = self._transport_config['auth']['ticket']

                        ## use static principal database from configuration
                        ##
                        if cfg['type'] == 'static':
<commit_msg>Fixed an triggered assertion resulting in no reply to the client on special occasions<commit_after>###############################################################################
##
##  Copyright (C) 2014 Tavendo GmbH
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU Affero General Public License, version 3,
##  as published by the Free Software Foundation.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
##  GNU Affero General Public License for more details.
##
##  You should have received a copy of the GNU Affero General Public License
##  along with this program. If not, see <http://www.gnu.org/licenses/>.
##
###############################################################################

from __future__ import absolute_import

__all__ = (
   'CrossbarRouterSessionFactory',
   'CrossbarRouterFactory',
   'CrossbarRouterServiceSession'
)

import json
import datetime
import traceback
from pytrie import StringTrie
from collections import namedtuple

from six.moves import urllib

from twisted.python import log
from twisted.python.failure import Failure
from twisted.internet.defer import Deferred, inlineCallbacks

from autobahn import util
from autobahn.websocket import http
from autobahn.websocket.compress import *

from autobahn import wamp
from autobahn.wamp import types
from autobahn.wamp import message
from autobahn.wamp.exception import ApplicationError
from autobahn.twisted.wamp import ApplicationSession

from crossbar.router.router import Router, RouterFactory, RouterSession, RouterSessionFactory

import crossbar

from crossbar.router.types import RouterOptions
from crossbar.router.interfaces import IRouter

from crossbar.router.auth import PendingAuthPersona, \
                                 PendingAuthWampCra, \
                                 PendingAuthTicket



class CrossbarRouterSession(RouterSession):
   """
   Router-side of (non-embedded) Crossbar.io WAMP sessions.
   """

   def onOpen(self, transport):
      """
      Implements :func:`autobahn.wamp.interfaces.ITransportHandler.onOpen`
      """
      RouterSession.onOpen(self, transport)

      if hasattr(self._transport, 'factory') and hasattr(self._transport.factory, '_config'):
         self._transport_config = self._transport.factory._config
      else:
         self._transport_config = {}

      self._pending_auth = None
      self._session_details = None
      self._service_session = None


   def onHello(self, realm, details):

      try:

         ## check if the realm the session wants to join actually exists
         ##
         if realm not in self._router_factory:
            return types.Deny(ApplicationError.NO_SUCH_REALM, message = "no realm '{}' exists on this router".format(realm))

         ## perform authentication
         ##
         if self._transport._authid is not None:

            ## already authenticated .. e.g. via cookie

            ## check if role still exists on realm
            ##
            allow = self._router_factory[realm].has_role(self._transport._authrole)

            if allow:
               return types.Accept(authid = self._transport._authid,
                                   authrole = self._transport._authrole,
                                   authmethod = self._transport._authmethod,
                                   authprovider = 'transport')
            else:
               return types.Deny(ApplicationError.NO_SUCH_ROLE, message = "session was previously authenticated (via transport), but role '{}' no longer exists on realm '{}'".format(self._transport._authrole, realm))

         else:
            ## if authentication is enabled on the transport ..
            ##
            if "auth" in self._transport_config:

               ## iterate over authentication methods announced by client ..
               ##
               for authmethod in details.authmethods or ["anonymous"]:

                  ## .. and if the configuration has an entry for the authmethod
                  ## announced, process ..
                  if authmethod in self._transport_config["auth"]:

                     ## "WAMP-Challenge-Response" authentication
                     ##
                     if authmethod == u"wampcra":
                        cfg = self._transport_config['auth']['wampcra']

                        if cfg['type'] == 'static':
                           if details.authid in cfg.get('users', {}):
                              user = cfg['users'][details.authid]

                              self._pending_auth = PendingAuthWampCra(details.pending_session, details.authid, user['role'], u'static', user['secret'].encode('utf8'))

                              ## send challenge to client
                              ##
                              extra = {
                                 u'challenge': self._pending_auth.challenge
                              }

                              ## when using salted passwords, provide the client with
                              ## the salt and then PBKDF2 parameters used
                              if 'salt' in user:
                                 extra[u'salt'] = user['salt']
                                 extra[u'iterations'] = user.get('iterations', 1000)
                                 extra[u'keylen'] = user.get('keylen', 32)

                              return types.Challenge(u'wampcra', extra)

                           else:
                              return types.Deny(message = "no user with authid '{}' in user database".format(details.authid))

                        elif cfg['type'] == 'dynamic':

                           ## call the configured dynamic authenticator procedure
                           ## via the router's service session
                           ##
                           service_session = self._router_factory.get(realm)._realm.session
                           session_details = {
                              ## forward transport level details of the WAMP session that
                              ## wishes to authenticate
                              'transport': self._transport._transport_info,

                              ## the following WAMP session ID will be assigned to the session
                              ## if (and only if) the subsequent authentication succeeds.
                              'session': self._pending_session_id
                           }
                           d = service_session.call(cfg['authenticator'], realm, details.authid, session_details)

                           def on_authenticate_ok(user):

                              ## construct a pending WAMP-CRA authentication
                              ##
                              self._pending_auth = PendingAuthWampCra(details.pending_session, details.authid, user['role'], u'dynamic', user['secret'].encode('utf8'))

                              ## send challenge to client
                              ##
                              extra = {
                                 u'challenge': self._pending_auth.challenge
                              }

                              ## when using salted passwords, provide the client with
                              ## the salt and the PBKDF2 parameters used
                              ##
                              if 'salt' in user:
                                 extra[u'salt'] = user['salt']
                                 extra[u'iterations'] = user.get('iterations', 1000)
                                 extra[u'keylen'] = user.get('keylen', 32)

                              return types.Challenge(u'wampcra', extra)

                           def on_authenticate_error(err):
                           
                              error = None
                              message = "dynamic WAMP-CRA credential getter failed: {}".format(err)

                              if isinstance(err.value, ApplicationError):
                                 error = err.value.error
                                 if err.value.args and len(err.value.args):
                                    message = str(err.value.args[0]) # exception does not need to contain a string

                              return types.Deny(error, message)


                           d.addCallbacks(on_authenticate_ok, on_authenticate_error)

                           return d

                        else:

                           return types.Deny(message = "illegal WAMP-CRA authentication config (type '{0}' is unknown)".format(cfg['type']))


                     ## WAMP-Ticket authentication
                     ##
                     elif authmethod == u"ticket":
                        cfg = self._transport_config['auth']['ticket']

                        ## use static principal database from configuration
                        ##
                        if cfg['type'] == 'static':

{'fraction_non_alphanumeric': 0.09447297222682348, 'fraction_numerical': 0.002539892882778422, 'mean_word_length': 1.537050007003782, 'pattern_counts': {'":': 4, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 62, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6420165', 'n_tokens_mistral': 4227, 'n_tokens_neox': 3965, 'n_words': 1171}