def test_blocking_all_uris(self): """ test that the uris will be blocked and unblocked after delay """ # -------------------------------------------------------------- -- # setup a local registry for the test DictResourceRegistry.registry = {} # ------------------------------------------------------------------ -- # setup the Resource Scheduler res_sched = ResourceScheduler( tries=1, resource_registry_class=DictResourceRegistry) res_sched.uri_list = string_to_list("uri://1, uri://2, uri://3, ") # ------------------------------------------------------------------ -- # check that all uris are blocked with freeze_time("2012-01-14 12:00:00"): # -------------------------------------------------------------- -- # block all uris for uri in next(res_sched): res_sched.block(uri, delay=30) # -------------------------------------------------------------- -- # verify that all uris are blocked and none is iterated uris = [] for uri in next(res_sched): uris.append(uri) assert len(uris) == 0 # one minute later with freeze_time("2012-01-14 12:01:00"): # -------------------------------------------------------------- -- # verify that all uris are un blocked after the delay uris = [] for uri in next(res_sched): uris.append(uri) assert "uri://1" in uris assert "uri://2" in uris assert "uri://3" in uris return
def test_uris_retry(self): """ test all uris will be n-times tried """ # -------------------------------------------------------------- -- # setup a local registry for the test DictResourceRegistry.registry = {} # -------------------------------------------------------------- -- # setup the Resource Scheduler res_sched = ResourceScheduler( tries=3, resource_registry_class=DictResourceRegistry) res_sched.uri_list = string_to_list( "bluri://1, bluri://2, bluri://3, ") # -------------------------------------------------------------- -- # check that the retry will be run through all uris n-times uris = [] for uri in next(res_sched): uris.append(uri) assert len(uris) == 9 # -------------------------------------------------------------- -- # check that every uri is registered in the global registry for _key, val in list(res_sched.resource_registry.registry.items()): value, b_ind, b_count = val assert value is None assert b_ind == 0 assert b_count == 0 return
def test_uris_retry(self): """ test all uris will be n-times tried """ # -------------------------------------------------------------- -- # setup a local registry for the test DictResourceRegistry.registry = {} # -------------------------------------------------------------- -- # setup the Resource Scheduler res_sched = ResourceScheduler( tries=3, resource_registry_class=DictResourceRegistry) res_sched.uri_list = string_to_list( "bluri://1, bluri://2, bluri://3, ") # -------------------------------------------------------------- -- # check that the retry will be run through all uris n-times uris = [] for uri in res_sched.next(): uris.append(uri) self.assertTrue(len(uris) == 9) # -------------------------------------------------------------- -- # check that every uri is registered in the global registry for _key, value in res_sched.resource_registry.registry.items(): self.assertTrue(value is None) return
def checkOtp(self, anOtpVal, counter, window, options=None): ''' Here we contact the Yubico Cloud server to validate the OtpVal. ''' pparams = {} yubico_url = getFromConfig("yubico.url", FALLBACK_YUBICO_URL) if yubico_url == DEPRECATED_YUBICO_URL: log.warning("Usage of YUBICO_URL %r is deprecated!! ", DEPRECATED_YUBICO_URL) # setups with old YUBICO URLS will be broken on yubico side # after 3th of February 2019 third_feb_2019 = datetime.datetime(year=2019, month=2, day=3) if datetime.datetime.now() >= third_feb_2019: raise Exception("Usage of YUBICO_URL %r is deprecated!! " % DEPRECATED_YUBICO_URL) apiId = getFromConfig("yubico.id") or DEFAULT_CLIENT_ID apiKey = getFromConfig("yubico.secret") or DEFAULT_API_KEY if apiKey == DEFAULT_API_KEY or apiId == DEFAULT_CLIENT_ID: log.warning("Usage of default apiKey or apiId not recomended!!") log.warning("Please register your own apiKey and apiId at " "yubico website !!") log.warning("Configure of apiKey and apiId at the " "linotp manage config menue!!") tokenid = self.getFromTokenInfo("yubico.tokenid") if len(anOtpVal) < 12: log.warning("[checkOtp] The otpval is too short: %r" % anOtpVal) return -1 if anOtpVal[:12] != tokenid: log.warning("[checkOtp] the tokenid in the OTP value does " "not match the assigned token!") return -1 timeout = getFromConfig("yubico.timeout") if timeout: pparams['timeout'] = parse_timeout(timeout) nonce = binascii.hexlify(os.urandom(20)).decode() p = urllib.parse.urlencode({ 'nonce': nonce, 'otp': anOtpVal, 'id': apiId }) yubico_urls = [x.strip() for x in yubico_url.split(',')] res_scheduler = ResourceScheduler(tries=2, uri_list=yubico_urls) for uri in next(res_scheduler): try: URL = "%s?%s" % (uri, p) response = requests.get(URL, **pparams) if response.ok: return self._check_yubico_response( nonce, apiKey, response.content.decode()) log.info("Failed to validate yubico request %r", response) return -1 except (Timeout, ConnectTimeout, ReadTimeout, ConnectionError, TooManyRedirects) as exx: log.exception('resource %r not available!', uri) # mark the url as blocked res_scheduler.block(uri, delay=30) log.error("[checkOtp] Error getting response from " "Yubico Cloud Server (%r)" % uri) except Exception as exx: log.exception('unknown exception for uri %r!', uri) raise exx # ------------------------------------------------------------------ -- # if we reach here, no resource has been availabel log.error('non of the resources %r available!', yubico_urls) raise AllResourcesUnavailable('non of the resources %r available!' % yubico_urls)
def sendSMS(self, message=None, transactionid=None): """ send sms :param message: the sms submit message - could contain placeholders like <otp> or <serial> :type message: string :return: submitted message :rtype: string """ success = None if not message: message = "<otp>" if not SMSPROVIDER_IMPORTED: raise Exception( "The SMSProvider could not be imported. Maybe you " "didn't install the package (Debian " "linotp-smsprovider or PyPI SMSProvider)" ) # we require the token owner to get the phone number and the provider owner = get_token_owner(self) phone = self.get_mobile_number(owner) otp = self.getNextOtp() serial = self.getSerial() if "<otp>" not in message: log.error("Message unconfigured: prepending <otp> to message") if isinstance(message, str): message = "<otp> %s" % message else: message = "<otp> %r" % message message = message.replace("<otp>", otp) message = message.replace("<serial>", serial) if transactionid: message = message.replace("<transactionid>", transactionid) log.debug("[sendSMS] sending SMS to phone number %s ", phone) realm = None realms = self.getRealms() if realms: realm = realms[0] # we require the token owner to get the phone number and the provider owner = get_token_owner(self) if not owner or not owner.login: log.warning("[sendSMS] Missing required token owner") # ------------------------------------------------------------------ -- # load providers for the user providers = get_provider_from_policy("sms", realm=realm, user=owner) # remember if at least one provider could be accessed available = False res_scheduler = ResourceScheduler(tries=1, uri_list=providers) for provider_name in next(res_scheduler): sms_provider = loadProvider("sms", provider_name=provider_name) if not sms_provider: log.error("Unable to load provider %r", provider_name) log.error("Please verify your provider configuration!") raise Exception("unable to load provider") try: success = sms_provider.submitMessage(phone, message) available = True log.info( "SMS successfully submitted by provider: %r", provider_name ) break except ProviderNotAvailable as exx: log.warn("Provider not available %r", provider_name) res_scheduler.block(provider_name, delay=30) if not available: log.error("all providers are not available %r", providers) raise AllResourcesUnavailable( "unable to connect to any SMSProvider %r" % providers ) log.info("[sendSMS] message submitted") # # after submit set validity time self.setValidUntil() return success, message
def test_blocking_counter(self): """ test that if for one entry the blocking counter increments at max of 8 run a connection timeout simulation by rising an exception for a special uri - we have to run many more times as the blocking url is not involved in every run. Thus we count the number when it is involved and terminate after 10 calls. THe max though should be t 8. """ # -------------------------------------------------------------- -- # setup a local registry for the test DictResourceRegistry.registry = {} # -------------------------------------------------------------- -- # setup the Resource Scheduler res_sched = ResourceScheduler( tries=1, resource_registry_class=DictResourceRegistry) res_sched.uri_list = string_to_list( "bluri://1, bluri://2, bluri://3, ") the_blocked_one = res_sched.uri_list[1] # -------------------------------------------------------------- -- # run the loop raise_counter = 0 i = 0 while raise_counter < 10: i += 1 with freeze_time(datetime.datetime.utcnow() + datetime.timedelta(minutes=i)): try: for uri in next(res_sched): if uri == the_blocked_one: raise DummyException() except DummyException: raise_counter += 1 res_sched.block(the_blocked_one, 30, immediately=True) # -------------------------------------------------------------- -- # check the result for key, val in list(res_sched.resource_registry.registry.items()): value, b_ind, b_count = val if b_ind == 1: assert value is not None assert key == the_blocked_one assert b_count == 8 else: assert value is None assert b_count == 0 assert b_ind == 0 return
def test_blocking_one_uri(self): """ test that if one entry is blocked it will not be in the iteration list """ # -------------------------------------------------------------- -- # setup a local registry for the test DictResourceRegistry.registry = {} # -------------------------------------------------------------- -- # setup the Resource Scheduler res_sched = ResourceScheduler( tries=3, resource_registry_class=DictResourceRegistry) res_sched.uri_list = string_to_list( "bluri://1, bluri://2, bluri://3, ") the_blocked_one = res_sched.uri_list[1] with freeze_time("2012-01-14 12:00:00"): # -------------------------------------------------------------- -- # block the second entry res_sched.block(the_blocked_one, 30, immediately=True) # -------------------------------------------------------------- -- # verify that the second entry will not be iterated uris = [] for uri in next(res_sched): uris.append(uri) assert the_blocked_one not in uris # -------------------------------------------------------------- -- # verify that the retry is done 3 times for the other two assert len(uris) == 6 # -------------------------------------------------------------- -- # verify that the blocked one is marked as blocked in the registry for key, val in list(res_sched.resource_registry.registry.items()): value, b_ind, b_count = val if key == the_blocked_one: assert value is not None assert b_ind == 1 assert b_count == 0 else: assert value is None # one minute later with freeze_time("2012-01-14 12:01:00"): uris = [] for uri in next(res_sched): uris.append(uri) # -------------------------------------------------------------- -- # verify that the former blocked one is now not more blocked assert the_blocked_one in uris # -------------------------------------------------------------- -- # verify that the former blocked one is as well unblocked in the # registry for _key, val in list( res_sched.resource_registry.registry.items()): value, _b_ind, _b_count = val assert value is None return
def _http_push(self, challenge, gda, transactionId): """ push the notification over http by calling the requests POST api :param message: the notification message :param gda: the global device identifier :return: tuple with response status and content / reason """ # ----------------------------------------------------------------- -- # Challenge Service expectes the following document # {"challenge": { # "transactionId": "string", # "gda": "string", # "challenge": "string" } # } params = {} params['transactionId'] = transactionId params['gda'] = gda params['challenge'] = challenge json_challenge = {"challenge": params} # # using **args for the timeout parameter # pparams = {} if self.timeout: pparams['timeout'] = self.timeout # submitting the json body requires the correct HTTP headers # with contenttype declaration: headers = { 'Content-type': 'application/json', 'Accept': 'text/plain'} if self.proxy: pparams['proxies'] = self.proxy # # we check if the client certificate exists, which is # referenced as a filename # if self.client_cert and os.path.isfile(self.client_cert): pparams['cert'] = self.client_cert server_cert = self.server_cert if server_cert is not None: # Session.post() doesn't like unicode values in Session.verify if isinstance(server_cert, unicode): server_cert = server_cert.encode('utf-8') pparams['verify'] = server_cert # ------------------------------------------------------------------ -- # schedule all resources res_scheduler = ResourceScheduler( tries=2, uri_list=self.push_server_urls) # ------------------------------------------------------------------ -- # location to preserve the last exception, so we can report this if # every resource access failed last_exception = None # ------------------------------------------------------------------ -- # iterate through all resources for uri in res_scheduler.next(): try: response = requests.post( uri, json=json_challenge, headers=headers, **pparams) if not response.ok: result = response.reason else: result = response.content return response.ok, result except (Timeout, ConnectTimeout, ReadTimeout, ConnectionError, TooManyRedirects) as exx: log.exception('resource %r not available!', uri) # mark the url as blocked res_scheduler.block(uri, delay=30) # and preserve the exception, so that we are able to raise this # when no resources are available at all last_exception = exx # ------------------------------------------------------------------ -- # if we reach here, no resource has been availabel log.error('non of the resources %r available!', self.push_server_urls) if last_exception: log.error("Last Exception was %r", exx) raise last_exception raise AllResourcesUnavailable('non of the resources %r available!' % self.push_server_urls)
def checkOtp(self, anOtpVal, counter, window, options=None): """ Here we contact the Yubico Cloud server to validate the OtpVal. """ pparams = {} yubico_url = getFromConfig("yubico.url", FALLBACK_YUBICO_URL) if yubico_url == DEPRECATED_YUBICO_URL: log.warning( "Usage of YUBICO_URL %r is deprecated!! ", DEPRECATED_YUBICO_URL, ) # setups with old YUBICO URLS will be broken on yubico side # after 3th of February 2019 third_feb_2019 = datetime.datetime(year=2019, month=2, day=3) if datetime.datetime.now() >= third_feb_2019: raise Exception("Usage of YUBICO_URL %r is deprecated!! " % DEPRECATED_YUBICO_URL) apiId = getFromConfig("yubico.id") apiKey = getFromConfig("yubico.secret") if not apiKey or not apiId: log.error(APIKEY_UNCONFIGURED_ERROR) raise YubicoApikeyException( "Yubico apiKey or apiId not configured!") tokenid = self.getFromTokenInfo("yubico.tokenid") if len(anOtpVal) < 12: log.warning("[checkOtp] The otpval is too short: %r", anOtpVal) return -1 if anOtpVal[:12] != tokenid: log.warning("[checkOtp] the tokenid in the OTP value does " "not match the assigned token!") return -1 timeout = getFromConfig("yubico.timeout") if timeout: pparams["timeout"] = parse_timeout(timeout) nonce = binascii.hexlify(os.urandom(20)).decode() p = urllib.parse.urlencode({ "nonce": nonce, "otp": anOtpVal, "id": apiId }) yubico_urls = [x.strip() for x in yubico_url.split(",")] res_scheduler = ResourceScheduler(tries=2, uri_list=yubico_urls) for uri in next(res_scheduler): try: URL = "%s?%s" % (uri, p) response = requests.get(URL, **pparams) if response.ok: return self._check_yubico_response( nonce, apiKey, response.content.decode()) log.info("Failed to validate yubico request %r", response) return -1 except ( Timeout, ConnectTimeout, ReadTimeout, ConnectionError, TooManyRedirects, ) as exx: log.error("resource %r not available!", uri) # mark the url as blocked res_scheduler.block(uri, delay=30) log.error( "[checkOtp] Error getting response from " "Yubico Cloud Server (%r)", uri, ) except Exception as exx: log.error("unknown exception for uri %r!", uri) raise exx # ------------------------------------------------------------------ -- # if we reach here, no resource has been availabel log.error("non of the resources %r available!", yubico_urls) raise AllResourcesUnavailable("non of the resources %r available!" % yubico_urls)