def __init__(self): self._user_manager = UserManager() self._config = dict() # AuthHandler and ChatController get dynamically loaded in startup() self._auth_handler = None self._chat_controller = None self._last_device = None self._last_session = None self._last_request_eap_pwd = False
def _get_mgr_with(self, testuser, data): mgr = UserManager() # add the user first mgr.add_request(testuser) testuser.user_data = data mgr.update(testuser) mgr.finish_request() self.assertListEqual(list(mgr.list_users()), [testuser]) self.assertFalse(mgr.is_request_pending()) return mgr
def test_reject_user_request(self): mgr = UserManager() # finish the request without update testuser = UserIdentifier('foo', 'bar') mgr.add_request(testuser) mgr.finish_request() self.assertListEqual(list(mgr.list_users()), []) self.assertFalse(mgr.is_request_pending())
def test_add_request_duplicate(self): mgr = UserManager() testuser = UserIdentifier('foo', 'bar') self.assertFalse(mgr.is_request_pending()) res1 = mgr.add_request(testuser) res2 = mgr.add_request(UserIdentifier('test', '2')) self.assertTrue(res1) self.assertFalse(res2) self.assertTrue(mgr.is_request_pending()) self.assertEqual(testuser, mgr.get_request()) self.assertListEqual(list(mgr.list_users()), [])
def test_add_request(self): mgr = UserManager() testuser = UserIdentifier('foo', 'bar') self.assertFalse(mgr.is_request_pending()) res = mgr.add_request(testuser) self.assertTrue(res) self.assertTrue(mgr.is_request_pending()) self.assertEqual(testuser, mgr.get_request()) self.assertListEqual(list(mgr.list_users()), [])
def test_add_request_user_has_password(self): mgr = UserManager() testuser = UserIdentifier('foo', 'bar') pw = mgr.generate_password() mgr.add_request(testuser) requested = mgr.get_request() self.assertEqual(pw, requested.password)
def test_initial_state(self): mgr = UserManager() self.assertFalse(mgr.is_request_pending()) self.assertListEqual(list(mgr.list_users()), []) self.assert_state_new(mgr.may_join(UserIdentifier('some test', '')))
def test_generate_password(self): mgr = UserManager() pw = mgr.generate_password() self.assertIsInstance(pw, str) self.assertNotEqual(pw, '')
def _get_mgr_with_two_users(self): mgr = UserManager() testuser1 = UserIdentifier('foo', 'bar') testuser2 = UserIdentifier('john doe', 'phone') mgr.add_request(testuser1) mgr.update(testuser1) mgr.finish_request() mgr.add_request(testuser2) mgr.update(testuser2) mgr.finish_request() return mgr, testuser1, testuser2
def test_update_request(self): mgr = UserManager() testuser = UserIdentifier('foo', 'bar') pw = mgr.generate_password() mgr.add_request(testuser) testdata = UserData() testuser.user_data = testdata testuser.password = pw mgr.update(testuser) mgr.finish_request() # generate another password, which should not affect the stored one mgr.generate_password() found = mgr.find('foo') self.assertEqual(found.name, testuser.name) self.assertEqual(found.device_id, testuser.device_id) self.assertEqual(found.password, pw) self.assertEqual(found.user_data, testdata)
def test_add_two_users(self): mgr = UserManager() testuser1 = UserIdentifier('foo', 'bar') testuser2 = UserIdentifier('john doe', 'phone') res1 = mgr.add_request(testuser1) mgr.update(testuser1) mgr.finish_request() res2 = mgr.add_request(testuser2) mgr.update(testuser2) mgr.finish_request() self.assertTrue(res1) self.assertTrue(res2) self.assertFalse(mgr.is_request_pending()) userlist = mgr.list_users() self.assertEqual(len(userlist), 2) self.assertIn(testuser1, userlist) self.assertIn(testuser2, userlist)
def test_add_request_invalid_data(self): mgr = UserManager() self.assertFalse(mgr.add_request('invalid data')) self.assertFalse(mgr.is_request_pending()) self.assertIsNone(mgr.get_request())
class GuestAuthCore(object): def __init__(self): self._user_manager = UserManager() self._config = dict() # AuthHandler and ChatController get dynamically loaded in startup() self._auth_handler = None self._chat_controller = None self._last_device = None self._last_session = None self._last_request_eap_pwd = False @staticmethod def get_eap_type(items_dict): """ Returns the EAP-Message's type field as integer, or -1 if unavailable """ msg = items_dict.get('EAP-Message', '') # message string starts with 0x, then followed by 4 octets for code, # identifier and length (8 hex digits). The type is 1 octet, so in # total we need 12 digits. # See RFC 3748, section 4. result = -1 if len(msg) >= 12: try: result = int(msg[10:12], 16) except ValueError: pass return result @staticmethod def skip_eap_message(items_dict): """ Indicates whether to ignore the EAP message stored in the respective field of items_dict. :returns: True if the message should be skipped """ eap_type = GuestAuthCore.get_eap_type(items_dict) # Skip all messages except the supported authentication methods. # If there is no EAP Message, it should not be skipped (-1). return eap_type not in [ -1, EAP_TYPE_PEAP, EAP_TYPE_MS_EAP_AUTH, EAP_TYPE_MSCHAP_2, EAP_TYPE_PWD ] def _add_post_auth_session_timeout(self, user_id, post_auth_dict): """ Adds the Session-Timeout attribute to a dict prepared by the AuthHandler in post_auth if the user is known and has valid_until set. :param user_id: The UserIdentifier containing the post_auth call attributes :param post_auth_dict: A dict to be returned in post_auth, usually prepared by the AuthHandler. Can also be None. :returns: None if post_auth_dict was none, otherwise the given dict with the timeout attribute added. """ stored_user = self._user_manager.find(user_id.name) if (stored_user == user_id and stored_user.user_data and stored_user.user_data.valid_until): user_valid = stored_user.user_data.valid_until # add 10 seconds to ensure the timeout is expired when # disconnecting session_timeout = user_valid - time.time() + 10 if session_timeout > 0: logger.debug('Setting post_auth timeout %s for %s' % (session_timeout, user_id.name)) if not post_auth_dict: post_auth_dict = dict() post_auth_dict['reply:Session-Timeout'] = session_timeout return post_auth_dict def startup(self, config): self._config = config # Dynamically load AuthHandler auth_loader = ImplLoader(auth.AuthHandler, DefaultAuthHandler) auth_impl = auth_loader.load(config.get('auth_handler', 'Default')) self._auth_handler = auth_impl() self._auth_handler.start(self._config) # Initialize ChatController self._chat_controller = ChatController(self._user_manager, self._auth_handler) self._chat_controller.start(self._config) logger.info('radguestauth core started.') def authorize(self, items): username = items.get('User-Name') calling_id = items.get('Calling-Station-Id') acct_session = items.get('Acct-Session-Id', '') keys = items.keys() # remember attributes for EAP-PWD requests, as the inner tunnel value # has only the username set. if GuestAuthCore.get_eap_type(items) == EAP_TYPE_PWD: # EAP-PWD: set flag for following inner request self._last_request_eap_pwd = True self._last_device = calling_id self._last_session = acct_session elif self._last_request_eap_pwd and not calling_id: # EAP-PWD inner request: restore attributes from previous request calling_id = self._last_device acct_session = self._last_session self._last_device = None self._last_session = None if 'EAP-Message' in keys and 'FreeRADIUS-Proxied-To' not in keys: # skip outer EAP requests return (auth.NO_OP, None) if ('FreeRADIUS-Proxied-To' in keys and GuestAuthCore.skip_eap_message(items)): # EAP methods may use multiple inner messages. # Skip all messages except the ones specifying the authentication # method (such as PEAP or MSCHAPv2). return (auth.NO_OP, None) if username and calling_id: # reset EAP-PWD flag self._last_request_eap_pwd = False user_id = UserIdentifier(username, calling_id) state = self._user_manager.may_join(user_id) if state == UserData.JOIN_STATE_ALLOWED: # look up full user object with data user_id = self._user_manager.find(username) logger.debug('authorize called for user %s (ALLOWED)' % username) elif state == UserData.JOIN_STATE_WAITING: # The user is the request user. Load the request to get the # password user_id = self._user_manager.get_request() elif state == UserData.JOIN_STATE_NEW: # always reject if another request is pending if self._user_manager.is_request_pending(): logger.info( 'Rejecting new user %s due to pending request' % username) return (auth.REJECT, None) # add request and notify host if successful if self._user_manager.add_request(user_id): self._chat_controller.notify_join(user_id) # load request user object with password attribute set user_id = self._user_manager.get_request() # state changes to WAITING now. state = UserData.JOIN_STATE_WAITING else: logger.warn('Failed to add request for %s', username) # reject user without calling handler if something went # wrong. return (auth.REJECT, None) return self._auth_handler.handle_user_state( user_id, state, acct_session) return (auth.REJECT, None) def post_auth(self, items): username = items.get('User-Name') calling_id = items.get('Calling-Station-Id') if username and calling_id: user_id = UserIdentifier(username, calling_id) acct_session = items.get('Acct-Session-Id', '') data = self._auth_handler.on_post_auth(user_id, acct_session) return self._add_post_auth_session_timeout(user_id, data) return None def drop_expired_users(self): """ This is intended to be called e.g. via a nightly cron job. Usually, users get disconnected via session timeout or if the login number is exceeded. However, the entries in the UserManager and possible whitelist entries in AuthHandler can be present after a timeout if the user doesn't join again. Even though these users won't get connectivity if they join again at a later time, removing old entries regularly makes it easier for the host to keep track of current guests. To avoid long-standing requests, the request also gets removed. """ expired = self._user_manager.get_expired_users() for user in expired: # call AuthHandler such that the user gets disconnected if needed self._auth_handler.on_host_deny(user) self._user_manager.remove(user) logger.info('Dropped expired user %s' % user.name) if self._user_manager.is_request_pending(): self._auth_handler.on_host_deny(self._user_manager.get_request()) self._user_manager.finish_request() logger.info('Removed ongoing request during drop_expired_users') def shutdown(self): try: self._chat_controller.stop() self._auth_handler.shutdown() except AttributeError as exc: logger.error('Failed to shutdown. Likely, this occured because' + ' initialization went wrong. (%s)' % exc)