Commit 5dacd1f9 authored by Marco Malavolti's avatar Marco Malavolti
Browse files

Refactorized eccs2checks output and WebDriver exceptions

parent d9d06f06
selenium_chromedriver.log
*.swp
*.txt
# HOWTO Install and Configure ECCS-2
# EduGAIN Connectivity Check Service 2
* `sudo apt install python3 python3-pip chromium chromium-l10n git libapache2-mod-wsgi python3-dev`
# Requirements Hardware
* OS: Debian 9,10 (tested)
* HDD: 10 GB
* RAM: 4 GB
* CPU: >= 2 vCPU
# Requirements Software
* Apache Server + WSGI
* Python 3.8
* Selenim + Chromium Web Brower
# HOWTO Install and Configure
* `sudo apt install chromium chromium-l10n git jq`
* `python3 -m pip install --user --upgrade pip virtualenv`
* `python3 -m venv eccs2venv`
* `source eccs2venv/bin/activate` (`deactivate` to exit Virtualenv)
......
......@@ -3,58 +3,17 @@
import logging
import re
from eccs2properties import DAY,ECCS2LOGSDIR
from eccs2properties import DAY,ECCS2LOGSDIR,ECCS2OUTPUTDIR
from flask import Flask, request, jsonify
from flask_restful import Resource, Api
from json import dumps, loads
from logging.handlers import RotatingFileHandler
from pathlib import PurePath
from utils import getLogger, getDriver
app = Flask(__name__)
api = Api(app)
def getLogger(filename,log_level="DEBUG",path="./"):
logger = logging.getLogger(filename)
ch = logging.FileHandler(path+filename,'w','utf-8')
if (log_level == "DEBUG"):
logger.setLevel(logging.DEBUG)
ch.setLevel(logging.DEBUG)
elif (log_level == "INFO"):
logger.setLevel(logging.INFO)
ch.setLevel(logging.INFO)
elif (log_level == "WARN"):
logger.setLevel(logging.WARN)
ch.setLevel(logging.WARN)
elif (log_level == "ERROR"):
logger.setLevel(logging.ERROR)
ch.setLevel(logging.ERROR)
elif (log_level == "CRITICAL"):
logger.setLevel(logging.CRITICAL)
ch.setLevel(logging.CRITICAL)
formatter = logging.Formatter('%(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
return logger
# Setup Chromium Webdriver
def setup():
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
driver = webdriver.Chrome('chromedriver', chrome_options=chrome_options)
# Configure timeouts
driver.set_page_load_timeout(30)
driver.set_script_timeout(30)
return driver
# /eccs2/test
class Test(Resource):
......@@ -67,7 +26,7 @@ class Checks(Resource):
def get(self):
app.logger.info("Request 'Checks'")
file_path = "%s/eccs2checks_%s.log" % (ECCS2LOGSDIR,DAY)
file_path = "%s/eccs2checks_%s.log" % (ECCS2OUTPUTDIR,DAY)
date = PurePath(file_path).parts[-1].split('_')[1].split('.')[0]
pretty = 0
status = None
......@@ -76,7 +35,7 @@ class Checks(Resource):
if 'date' in request.args:
app.logger.info("'date' parameter inserted")
date = request.args['date']
file_path = "%s/eccs2checks_%s.log" % (ECCS2LOGSDIR,date)
file_path = "%s/eccs2checks_%s.log" % (ECCS2OUTPUTDIR,date)
if 'pretty' in request.args:
app.logger.info("'pretty' parameter inserted")
pretty = request.args['pretty']
......@@ -97,41 +56,49 @@ class Checks(Resource):
check_idp = check[0]
check_sp = check[1]
check_status = check[2].rstrip("\n\r")
status_code = check[2]
check_time = check[3]
check_status = check[4].rstrip("\n\r")
if (idp and status):
app.logger.info("Checks for 'idp' and 'status'.")
app.logger.info("Search for 'idp':'%s' and 'status':'%s'." % (idp,status))
if (idp == check_idp and status == check_status):
result.append( { 'sp' : check_sp,
'idp' : check_idp,
'check_time': check_time,
'status_code': status_code,
'status' : check_status,
'date': date
} )
elif (idp):
#app.logger.info(re.search(".*."+idp+".*.", check_idp, re.IGNORECASE))
#app.logger.info(check_idp))
app.logger.info("Checks for Idp '%s'." % idp)
app.logger.info("Search for 'idp':'%s'" % idp)
if (re.search(".*."+idp+".*.", check_idp, re.IGNORECASE)):
result.append( { 'sp' : check_sp,
'idp' : check_idp,
'check_time': check_time,
'status_code': status_code,
'status' : check_status,
'date': date
} )
elif (status):
app.logger.info("Check for the status '%s'." % status)
app.logger.info("Search for 'status':'%s'." % status)
if (status == check_status):
result.append( { 'sp' : check_sp,
'idp' : check_idp,
'check_time': check_time,
'status_code': status_code,
'status' : check_status,
'date': date
} )
else:
app.logger.info("All checks.")
result.append( { 'sp' : check_sp,
'idp' : check_idp,
'check_time': check_time,
'status_code': status_code,
'status' : check_status,
'date': date
} )
else:
app.logger.info("All checks.")
result.append( { 'sp' : check_sp,
'idp' : check_idp,
'status' : check_status,
'date': date
} )
if (pretty):
pp_json = dumps(result, indent=4, sort_keys=True)
......@@ -154,7 +121,7 @@ class EccsResults(Resource):
def get(self):
app.logger.info("Request 'EccsResults'")
file_path = "%s/eccs2_%s.log" % (ECCS2LOGSDIR,DAY)
file_path = "%s/eccs2_%s.log" % (ECCS2OUTPUTDIR,DAY)
date = PurePath(file_path).parts[-1].split('_')[1].split('.')[0]
pretty = 0
status = None
......@@ -163,7 +130,7 @@ class EccsResults(Resource):
if 'date' in request.args:
app.logger.info("'date' parameter inserted")
date = request.args['date']
file_path = "%s/eccs2_%s.log" % (ECCS2LOGSDIR,date)
file_path = "%s/eccs2_%s.log" % (ECCS2OUTPUTDIR,date)
if 'pretty' in request.args:
app.logger.info("'pretty' parameter inserted")
pretty = request.args['pretty']
......@@ -197,23 +164,23 @@ class EccsResults(Resource):
# SP-status-2 check[13]
check = line.split(";")
idp_displayname = check[0].rstrip("\n\r")
idp_entity_id = check[1].rstrip("\n\r")
idp_reg_auth = check[2].rstrip("\n\r")
idp_tech_ctcs = check[3].rstrip("\n\r")
idp_supp_ctcs = check[4].rstrip("\n\r")
idp_checks_status = check[5].rstrip("\n\r")
sp1_entity_id = check[6].rstrip("\n\r")
sp1_check_time = check[7].rstrip("\n\r")
sp1_status_code = check[8].rstrip("\n\r")
sp1_check_status = check[9].rstrip("\n\r")
sp2_entity_id = check[10].rstrip("\n\r")
sp2_check_time = check[11].rstrip("\n\r")
sp2_status_code = check[12].rstrip("\n\r")
idp_displayname = check[0]
idp_entity_id = check[1]
idp_reg_auth = check[2]
idp_tech_ctcs = check[3]
idp_supp_ctcs = check[4]
idp_checks_status = check[5]
sp1_entity_id = check[6]
sp1_check_time = check[7]
sp1_status_code = check[8]
sp1_check_status = check[9]
sp2_entity_id = check[10]
sp2_check_time = check[11]
sp2_status_code = check[12]
sp2_check_status = check[13].rstrip("\n\r")
if (idp and status):
app.logger.info("Results for the idp '%s' with status '%s'" % (idp, status))
app.logger.info("eccsresults: check for 'idp':'%s' with 'status':'%s'" % (idp, status))
if (idp == idp_entity_id and status == idp_checks_status):
result.append(
{
......@@ -228,21 +195,21 @@ class EccsResults(Resource):
'sp1' : {
'entityID' : sp1_entity_id,
'checkTime' : sp1_check_time,
'status' : sp1_check_status,
'statusCode' : sp1_status_code
'statusCode' : sp1_status_code,
'status' : sp1_check_status
},
'sp2' : {
'entityID' : sp2_entity_id,
'checkTime' : sp2_check_time,
'status' : sp2_check_status,
'statusCode' : sp2_status_code
'statusCode' : sp2_status_code,
'status' : sp2_check_status
},
'status' : idp_checks_status
} )
elif (idp):
#app.logger.info(re.search(".*."+idp+".*.", idp_entity_id, re.IGNORECASE))
#app.logger.info(idp_entity_id))
app.logger.info("Results for IdP '%s'." % idp)
app.logger.info("eccsresults: results for IdP:'%s'." % idp)
if (re.search(".*."+idp+".*.", idp_entity_id, re.IGNORECASE)):
result.append(
{
......@@ -257,18 +224,19 @@ class EccsResults(Resource):
'sp1' : {
'entityID' : sp1_entity_id,
'checkTime' : sp1_check_time,
'status' : sp1_check_status,
'statusCode' : sp1_status_code
'statusCode' : sp1_status_code,
'status' : sp1_check_status
},
'sp2' : {
'entityID' : sp2_entity_id,
'checkTime' : sp2_check_time,
'status' : sp2_check_status,
'statusCode' : sp2_status_code
'statusCode' : sp2_status_code,
'status' : sp2_check_status
},
'status' : idp_checks_status
} )
elif (status):
app.logger.info("eccsresults: Search for 'status':'%s'." % status)
if (status == idp_checks_status):
result.append(
{
......@@ -283,14 +251,14 @@ class EccsResults(Resource):
'sp1' : {
'entityID' : sp1_entity_id,
'checkTime' : sp1_check_time,
'status' : sp1_check_status,
'statusCode' : sp1_status_code
'statusCode' : sp1_status_code,
'status' : sp1_check_status
},
'sp2' : {
'entityID' : sp2_entity_id,
'checkTime' : sp2_check_time,
'status' : sp2_check_status,
'statusCode' : sp2_status_code
'statusCode' : sp2_status_code,
'status' : sp2_check_status
},
'status' : idp_checks_status
} )
......@@ -308,14 +276,14 @@ class EccsResults(Resource):
'sp1' : {
'entityID' : sp1_entity_id,
'checkTime' : sp1_check_time,
'status' : sp1_check_status,
'statusCode' : sp1_status_code
'statusCode' : sp1_status_code,
'status' : sp1_check_status
},
'sp2' : {
'entityID' : sp2_entity_id,
'checkTime' : sp2_check_time,
'status' : sp2_check_status,
'statusCode' : sp2_status_code
'statusCode' : sp2_status_code,
'status' : sp2_check_status
},
'status' : idp_checks_status
} )
......@@ -339,5 +307,5 @@ api.add_resource(EccsResults, '/eccs/eccsresults') # Route_3
if __name__ == '__main__':
app.config['JSON_AS_ASCII'] = False
app.logger = getLogger("eccs2api.log", "INFO", ECCS2LOGSDIR)
app.logger = getLogger("eccs2api.log", ECCS2LOGSDIR, "w", "INFO")
app.run(port='5002')
#!/bin/bash
# Remove old IdP and Fed List
rm -f /opt/eccs2/input/*.json
# Run ECCS2
/opt/eccs2/runEccs2.py
......@@ -7,7 +7,8 @@ import re
import requests
import time
from eccs2properties import ECCS2LOGSDIR, ECCS2RESULTSLOG, ECCS2CHECKSLOG, FEDS_BLACKLIST, IDPS_BLACKLIST, ECCS2SPS, ECCS2SELENIUMDEBUG
from eccs2properties import DAY, ECCS2HTMLDIR, ECCS2LOGSDIR, ECCS2OUTPUTDIR, ECCS2RESULTSLOG, ECCS2CHECKSLOG, FEDS_BLACKLIST, IDPS_BLACKLIST, ECCS2SPS, ECCS2SELENIUMDEBUG
from pathlib import Path
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import Select, WebDriverWait
......@@ -17,13 +18,14 @@ from urllib3.exceptions import MaxRetryError
from urllib3.util import parse_url
from utils import getLogger, getIdPContacts, getDriver
"""
This script use Selenium and Chromium to select the IdP to check from a Shibboleth SP with the Shibboleth Embedded Discovery Service installed and configured to answer to all eduGAIN IdPs.
The SPs used to check an IdP will be SP24(IDEM) and Attribute Viewer (SWITCH).
The check will be passed when both SPs will return the authentication page of the IdP checked.
"""
def checkIdP(sp,idp,logger):
def checkIdP(sp,idp):
# Chromedriver MUST be instanced here to avoid problems with SESSION
# Disable SSL requests warning messages
......@@ -33,19 +35,23 @@ def checkIdP(sp,idp,logger):
fqdn_idp = parse_url(idp['entityID'])[2]
driver = getDriver(fqdn_idp,debugSelenium)
# Exception of WebDriver raises
if (driver == None):
return None
# Configure Blacklists
federation_blacklist = FEDS_BLACKLIST
entities_blacklist = IDPS_BLACKLIST
if (idp['registrationAuthority'] in federation_blacklist):
check_time = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') + 'Z'
logger.info("%s;%s;%s;NULL;Federation excluded from checks" % (idp['entityID'],sp,check_time))
return (sp,check_time,"NULL","DISABLED")
#logger.info("%s;%s;%s;NULL;Federation excluded from checks" % (idp['entityID'],sp,check_time))
return (idp['entityID'],sp,check_time,"NULL","DISABLED")
if (idp['entityID'] in entities_blacklist):
check_time = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') + 'Z'
logger.info("%s;%s;%s;NULL;IdP excluded from checks" % (idp['entityID'],sp,check_time))
return (sp,check_time,"NULL","DISABLED")
#logger.info("%s;%s;%s;NULL;IdP excluded from checks" % (idp['entityID'],sp,check_time))
return (idp['entityID'],sp,check_time,"NULL","DISABLED")
# Open SP, select the IDP from the EDS and press 'Enter' to reach the IdP login page to check
try:
......@@ -56,9 +62,16 @@ def checkIdP(sp,idp,logger):
page_source = driver.page_source
samlrequest_url = driver.current_url
# Put the code of the page into an HTML file
Path("%s/%s" % (ECCS2HTMLDIR,DAY)).mkdir(parents=True, exist_ok=True)
fqdn_idp = parse_url(idp['entityID'])[2]
fqdn_sp = parse_url(sp)[2]
with open("%s/%s/%s---%s.html" % (ECCS2HTMLDIR,DAY,fqdn_idp,fqdn_sp),"w") as html:
html.write(page_source)
except TimeoutException as e:
logger.info("%s;%s;%s;999;Timeout" % (idp['entityID'],sp,check_time))
return (sp,check_time,"999","Timeout")
#logger.info("%s;%s;999;%s;Timeout" % (idp['entityID'],sp,check_time))
return (idp['entityID'],sp,check_time,"999","Timeout")
except NoSuchElementException as e:
# The input of the bootstrap tables are provided by "eccs2" and "eccs2checks" log.
......@@ -69,8 +82,8 @@ def checkIdP(sp,idp,logger):
return None
except UnexpectedAlertPresentException as e:
logger.info("%s;%s;%s;888;UnexpectedAlertPresent" % (idp['entityID'],sp,check_time))
return (sp,check_time,"888","ERROR")
#logger.info("%s;%s;888;%s;UnexpectedAlertPresent" % (idp['entityID'],sp,check_time))
return (idp['entityID'],sp,check_time,"888","ERROR")
except WebDriverException as e:
print("!!! WEB DRIVER EXCEPTION - RUN AGAIN THE COMMAND!!!")
......@@ -87,6 +100,7 @@ def checkIdP(sp,idp,logger):
finally:
driver.quit()
pattern_metadata = "Unable.to.locate(\sissuer.in|).metadata(\sfor|)|no.metadata.found|profile.is.not.configured.for.relying.party|Cannot.locate.entity|fail.to.load.unknown.provider|does.not.recognise.the.service|unable.to.load.provider|Nous.n'avons.pas.pu.(charg|charger).le.fournisseur.de service|Metadata.not.found|application.you.have.accessed.is.not.registered.for.use.with.this.service|Message.did.not.meet.security.requirements"
pattern_username = '<input[\s]+[^>]*((type=\s*[\'"](text|email)[\'"]|user)|(name=\s*[\'"](name)[\'"]))[^>]*>';
......@@ -98,7 +112,7 @@ def checkIdP(sp,idp,logger):
try:
headers = {'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36'}
status_code = requests.get(samlrequest_url, headers=headers, verify=False, timeout=30).status_code
status_code = str(requests.get(samlrequest_url, headers=headers, verify=False, timeout=30).status_code)
except requests.exceptions.ConnectionError as e:
#print("!!! REQUESTS STATUS CODE CONNECTION ERROR EXCEPTION !!!")
......@@ -132,88 +146,93 @@ def checkIdP(sp,idp,logger):
if(metadata_not_found):
logger.info("%s;%s;%s;%s;No-eduGAIN-Metadata" % (idp['entityID'],sp,status_code,check_time))
return (sp,check_time,status_code,"No-eduGAIN-Metadata")
#logger.info("%s;%s;%s;%s;No-eduGAIN-Metadata" % (idp['entityID'],sp,status_code,check_time))
return (idp['entityID'],sp,check_time,status_code,"No-eduGAIN-Metadata")
elif not username_found or not password_found:
logger.info("%s;%s;%s;%s;Invalid-Form" % (idp['entityID'],sp,status_code,check_time))
return (sp,check_time,status_code,"Invalid-Form")
#logger.info("%s;%s;%s;%s;Invalid-Form" % (idp['entityID'],sp,status_code,check_time))
return (idp['entityID'],sp,check_time,status_code,"Invalid-Form")
else:
logger.info("%s;%s;%s;%s;OK" % (idp['entityID'],sp,status_code,check_time))
return (sp,check_time,status_code,"OK")
#logger.info("%s;%s;%s;%s;OK" % (idp['entityID'],sp,status_code,check_time))
return (idp['entityID'],sp,check_time,status_code,"OK")
def check(idp,sps,eccs2log,eccs2checksLog):
result = []
def check(idp,sps,eccs2log):
results = []
for sp in sps:
resultCheck = checkIdP(sp,idp,eccs2checksLog)
result.append(resultCheck)
listTechContacts = getIdPContacts(idp,'technical')
listSuppContacts = getIdPContacts(idp,'support')
strTechContacts = ','.join(listTechContacts)
strSuppContacts = ','.join(listSuppContacts)
# If all checks are 'OK', than the IdP consuming correctly eduGAIN Metadata.
if (result[0][3] == result[1][3] == "OK"):
# IdP-DisplayName;IdP-entityID;IdP-RegAuth;IdP-tech-ctc-1,IdP-tech-ctc-2;IdP-supp-ctc-1,IdP-supp-ctc-2;Status;SP-entityID-1;SP-check-time-1;SP-status-code-1;SP-result-1;SP-entityID-2;SP-check-time-2;SP-status-code-2;SP-result-2
eccs2log.info("%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s" % (
idp['displayname'].replace("&apos;","'").split(';')[1].split('==')[0],
idp['entityID'],
idp['registrationAuthority'],
strTechContacts,
strSuppContacts,
'OK',
result[0][0],
result[0][1],
result[0][2],
result[0][3],
result[1][0],
result[1][1],
result[1][2],
result[1][3]))
elif (result[0][3] == result[1][3] == "DISABLED"):
eccs2log.info("%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s" % (
idp['displayname'].replace("&apos;","'").split(';')[1].split('==')[0],
idp['entityID'],
idp['registrationAuthority'],
strTechContacts,
strSuppContacts,
'DISABLE',
result[0][0],
result[0][1],
result[0][2],
result[0][3],
result[1][0],
result[1][1],
result[1][2],
result[1][3]))
elif (result[0][3] == None or result[1][3] == None):
# Do nothing
return
else:
eccs2log.info("%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s" % (
idp['displayname'].replace("&apos;","'").split(';')[1].split('==')[0],
idp['entityID'],
idp['registrationAuthority'],
strTechContacts,
strSuppContacts,
'ERROR',
result[0][0],
result[0][1],
result[0][2],
result[0][3],
result[1][0],
result[1][1],
result[1][2],
result[1][3]))
resultCheck = checkIdP(sp,idp)
# Se il checkIdP ha successo, aggiungo alla lista dei check
# altrimenti no.
if resultCheck is not None:
results.append(resultCheck)
if len(results) == 2:
with open("%s/%s" % (ECCS2OUTPUTDIR,ECCS2CHECKSLOG), 'a') as f:
for elem in results:
f.write(";".join(elem))
f.write("\n")
listTechContacts = getIdPContacts(idp,'technical')
listSuppContacts = getIdPContacts(idp,'support')
strTechContacts = ','.join(listTechContacts)
strSuppContacts = ','.join(listSuppContacts)
# If all checks are 'OK', than the IdP consuming correctly eduGAIN Metadata.
if (results[0][4] == results[1][4] == "OK"):
# IdP-DisplayName;IdP-entityID;IdP-RegAuth;IdP-tech-ctc-1,IdP-tech-ctc-2;IdP-supp-ctc-1,IdP-supp-ctc-2;Status;SP-entityID-1;SP-check-time-1;SP-status-code-1;SP-result-1;SP-entityID-2;SP-check-time-2;SP-status-code-2;SP-result-2
eccs2log.info("%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s" % (
idp['displayname'].replace("&apos;","'").split(';')[1].split('==')[0],
idp['entityID'],
idp['registrationAuthority'],
strTechContacts,
strSuppContacts,
'OK',
results[0][1], # SP-entityID-1
results[0][2], # SP-check-time-1
results[0][3], # SP-status-code-1
results[0][4], # SP-result-1
results[1][1], # SP-entityID-2
results[1][2], # SP-check-time-2
results[1][3], # SP-status-code-2
results[1][4])) # SP-result-2
elif (results[0][4] == results[1][4] == "DISABLED"):
eccs2log.info("%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s" % (
idp['displayname'].replace("&apos;","'").split(';')[1].split('==')[0],
idp['entityID'],
idp['registrationAuthority'],
strTechContacts,
strSuppContacts,
'DISABLE',
results[0][1],
results[0][2],
results[0][3],
results[0][4],
results[1][1],
results[1][2],
results[1][3],
results[1][4]))
else:
eccs2log.info("%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s;%s" % (
idp['displayname'].replace("&apos;","'").split(';')[1].split('==')[0],
idp['entityID'],
idp['registrationAuthority'],
strTechContacts,
strSuppContacts,
'ERROR',
results[0][1],
results[0][2],
results[0][3],
results[0][4],
results[1][1],
results[1][2],
results[1][3],
results[1][4]))
# MAIN
if __name__=="__main__":
eccs2log = getLogger(ECCS2RESULTSLOG, ECCS2LOGSDIR, 'a', "INFO")
eccs2checksLog = getLogger(ECCS2CHECKSLOG, ECCS2LOGSDIR, 'a', "INFO")
eccs2log = getLogger(ECCS2RESULTSLOG, ECCS2OUTPUTDIR, 'a', "INFO")
sps = ECCS2SPS
......@@ -224,4 +243,4 @@ if __name__=="__main__":
idp = json.loads(args.idpJson[0])
check(idp,sps,eccs2log,eccs2checksLog)
check(idp,sps,eccs2log)
......@@ -5,31 +5,34 @@ from datetime import date
DAY = date.today().isoformat()
ECCS2DIR = "/opt/eccs2"
ECCS2LOGSDIR = "%s/logs" % ECCS2DIR
ECCS2INPUTDIR = "%s/input" % ECCS2DIR
# Input
ECCS2INPUTDIR = "%s/input" % ECCS2DIR
ECCS2LISTIDPSURL = 'https://technical.edugain.org/api.php?action=list_eccs_idps&format=json'
ECCS2LISTIDPSFILE = "%s/list_eccs_idps.json" % ECCS2INPUTDIR
ECCS2LISTFEDSURL = 'https://technical.edugain.org/api.php?action=list_feds&opt=1&format=json'
ECCS2LISTFEDSFILE = "%s/list_fed.json" % ECCS2INPUTDIR
# Output
ECCS2OUTPUTDIR = "%s/output" % ECCS2DIR
ECCS2RESULTSLOG = "eccs2_%s.log" % DAY
ECCS2CHECKSLOG = "eccs2checks_%s.log" % DAY
ECCS2SELENIUMLOGDIR = "%s/selenium-logs" % ECCS2DIR