diff --git a/README.md b/README.md index 0021a2d5f99245c313ff595821a2d0fec3f2dd96..431b1d786a30ae284f6fcb2d8047f96ed801e041 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,8 @@ After the initial download, it is recommended that you occasionally go through t ### Configure ECCS 1. Configure ECCS properties: - * `vim eccs_properties.py` (and change it upon your needs) + * `cp $HOME/eccs/eccs_properties.py.template $HOME/eccs/eccs_properties.py` + * `vim $HOME/eccs/eccs_properties.py` (and change it upon your needs) 2. Change `PATH` by adding the virtualenv Python `bin` dir: * CentOS: @@ -220,7 +221,7 @@ After the initial download, it is recommended that you occasionally go through t ```bash # set PATH for ECCS - if [ -d "$HOME/eccs" ] ; then + if [ -d "$HOME/eccs" ]; then PATH="$HOME/eccs/eccs-venv/bin:$PATH" fi ``` @@ -233,7 +234,7 @@ After the initial download, it is recommended that you occasionally go through t ```bash # set PATH for ECCS - if [ -d "$HOME/eccs" ] ; then + if [ -d "$HOME/eccs" ]; then PATH="$HOME/eccs/eccs-venv/bin:$PATH" fi ``` diff --git a/api.py b/api.py index cea39c684d772fe1767c83059b7f0c28ee967615..47a6ce302178d32c14c001c48dd4b117bed529dc 100755 --- a/api.py +++ b/api.py @@ -101,8 +101,8 @@ class EccsResults(Resource): simple = True if 'check_result' in request.args: check_result = request.args['check_result'] - if (check_result not in ['OK','Timeout','Invalid-Form','Connection-Error','No-eduGAIN-Metadata','SSL-Error','DISABLED']): - return jsonify(error="Incorrect check_result value provided. It can be 'OK','Timeout','Invalid-Form','Connection-Error','No-eduGAIN-Metadata','SSL-Error' ok 'DISABLED'") + if (check_result not in ['OK','Timeout','Invalid-Form','Connection-Error','No-eduGAIN-Metadata','SSL-Error','IdP-Error','DISABLED']): + return jsonify(error="Incorrect check_result value provided. It can be 'OK','Timeout','Invalid-Form','Connection-Error','No-eduGAIN-Metadata','SSL-Error','IdP-Error' or 'DISABLED'") lines = [] results = [] diff --git a/eccs_properties.py.template b/eccs_properties.py.template new file mode 100644 index 0000000000000000000000000000000000000000..28c9b55450a299407a0edbc8a9963485dcf9c459 --- /dev/null +++ b/eccs_properties.py.template @@ -0,0 +1,85 @@ +import os +from datetime import date + +DAY = date.today().isoformat() + +ECCS_DIR = f"{os.environ['HOME']}/eccs" +PATHCHROMEDRIVER = f"{ECCS_DIR}/chromedriver" +ECCS_PYTHON = f"{ECCS_DIR}/python/bin/python3" + +# Input +ECCS_INPUTDIR = f"{ECCS_DIR}/input" +ECCS_LISTIDPSURL = 'https://technical.edugain.org/api.php?action=list_eccs_idps&format=json' +ECCS_LISTIDPSFILE = f"{ECCS_INPUTDIR}/list_eccs_idps.json" +ECCS_LISTFEDSURL = 'https://technical.edugain.org/api.php?action=list_feds&opt=1&format=json' +ECCS_LISTFEDSFILE = f"{ECCS_INPUTDIR}/list_fed.json" + +# Output +ECCS_OUTPUTDIR = f"{ECCS_DIR}/output" +ECCS_RESULTSLOG = f"eccs_{DAY}.log" +ECCS_HTMLDIR = f"{ECCS_DIR}/html" + +# Selenium +ECCS_SELENIUMDEBUG = False +ECCS_SELENIUMLOGDIR = f"{ECCS_DIR}/selenium-logs" +ECCS_SELENIUMPAGELOADTIMEOUT = 60 #seconds (remind to change timeout seconds also on web/eccs.js) +ECCS_SELENIUMSCRIPTTIMEOUT = 60 #seconds +ECCS_REQUESTSTIMEOUT = 15 #seconds + +# Logs +ECCS_LOGSDIR = f"{ECCS_DIR}/logs" +ECCS_STDOUT = f"{ECCS_LOGSDIR}/stdout_{DAY}.log" +ECCS_STDERR = f"{ECCS_LOGSDIR}/stderr_{DAY}.log" +ECCS_FAILEDCMD = f"{ECCS_LOGSDIR}/failed-cmd.sh" +ECCS_STDOUTIDP = f"{ECCS_LOGSDIR}/stdout_idp_{DAY}.log" +ECCS_STDERRIDP = f"{ECCS_LOGSDIR}/stderr_idp_{DAY}.log" +ECCS_FAILEDCMDIDP = f"{ECCS_LOGSDIR}/failed-cmd-idp.sh" + +# Number of processes to run in parallel +ECCS_NUMPROCESSES = 35 + +# The 2 SPs that will be used to test each IdP +ECCS_SPS = [ + "https://sp-demo.idem.garr.it/Shibboleth.sso/Login?entityID=", + "https://attribute-viewer.aai.switch.ch/interfederation-test/Shibboleth.sso/Login?entityID=" +] + +# ROBOTS.TXT +ROBOTS_USER_AGENT = "ECCS/2.0 (+https://technical.edugain.org/eccs)" + +# PATTERNS +JAVASCRIPT = "x-my-okta-version" +IDPERROR = "error.has.occurred|error.occurred|Error.when.processing.authentication.request|The.system.encountered.an.error|Internal.Server.Error|403.Forbidden|Service.Unavailable|InvalidProfileConfiguration|Unexpected.System.Error|404.not.found|404.-.not.found|OpenAthens:.404|On.tapahtunut.virhe|Unhandled.exception|Bad.Gateway|Page.Not.Found|Δεν.επιτρέπεται.η.πρόσβαση|temporary.error|temporarily.unavailable|License.error|n'est.pas.gérée" +METADATAPATTERN = "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|Unsupported.Request|Not.Authorized|METADATANOTFOUND|Unknown.login.requester|is.unspecified.or.unsupported|Unknown.service.provider|Richiesta.non.supportata|Metadati.non.trovati|untrusted.provider|Unregistered.Service|Unsupported.request|UNHANDLEDEXCEPTION|Metadata.*.expired|Could.not.find.any.*.metadata.*.for|不支持的请求|l'application.n'est.pas.enregistrée|Requisição.não.suportada|トされていないリクエスト|is.not.allowed|Authorization.Failure|Pedido.não.suportado" +PASSWORDPATTERN = '<input[\s]+[^>]*(type=\s*[\'"]password[\'"]|password)[^>]*>' +REFUSEDPATTERN = '(^http)(.*\.png$)|(.*\.css$)|(.*\.js$)|(.*\.gif$)|(.*\.svg$)|(.*\.jpg$)' + +# { 'reg_auth':'reason' } +FEDS_DISABLED_DICT = { + 'http://www.surfconext.nl/':'Federation excluded from check', + 'https://www.wayf.dk':'Federation excluded from check', + 'http://feide.no/':'Federation excluded from check' +} + +# { 'entityid_idp':'reason' } +IDPS_DISABLED_DICT = { + 'https://idp.eie.gr/idp/shibboleth':'Disabled on 2019-04-24 because ECCS cannot check non-standard login page', + 'https://edugain-proxy.igtf.net/simplesaml/saml2/idp/metadata.php':'Disabled on 2017-03-17 on request of federation operator', + 'https://gn-vho.grnet.gr/idp/shibboleth':'Disabled on 2019-04-24 because basic authentication is not supported by ECCS check', + 'https://wtc.tu-chemnitz.de/shibboleth':'Disabled on 2019-02-26 because ECCS cannot check non-standard login page', + 'https://idp.fraunhofer.de/idp/shibboleth':'Disabled on 2017-11-24 on request of federation operator', + 'https://idp.dfn-cert.de/idp/shibboleth':'Disabled on 2018-04-05 on request of federation operator', + 'https://idp.cambria.ac.uk/openathens':'Disabled on 2017-10-27 on request of federation operator', + 'https://login.lstonline.ac.uk/idp/pingfederate':'Disabled on 2017-02-08 on request of federation operator', + 'https://indiid.net/idp/shibboleth':'Disabled on 2017-10-27 on request of federation operator', + 'https://idp.nulc.ac.uk/openathens':'Disabled on 2017-10-27 on request of federation operator', + 'https://lc-idp.lincolncollege.ac.uk/shibboleth':'Disabled on 2015-08-17 because uses HTTP Basic authentication, which cannot be checked reliably', + 'https://idp.wnsc.ac.uk/idp/shibboleth':'Disabled on 2017-10-27 on request of federation operator', +# 'https://idp.strodes.ac.uk/shibboleth':'Disabled on 2015-08-17 because uses HTTP Basic authentication, which cannot be checked reliably', + 'https://idp.uel.ac.uk/shibboleth':'Disabled on 2017-10-27 on request of federation operator', + 'https://idp.ucreative.ac.uk/shibboleth':'Disabled on 2017-10-27 on request of federation operator', + 'https://idp.llandrillo.ac.uk/shibboleth':'Disabled on 2017-10-27 on request of federation operator', + 'https://sso.vu.lt/SSO/saml2/idp/metadata.php':'Disabled on 2018-11-02 because ECCS cannot check non-standard login page', + 'https://ssl.education.lu/saml/saml2/idp/metadata.php':'Disabled on 2018-11-06 ECCS cannot check non-standard login page', + 'https://sso.oktaedu.com/idp/shibboleth':'Disabled on 2021-08-12 because ECCS cannot check non-standard login page', +} diff --git a/utils.py b/utils.py index 9a0ee0889902f5cc8b94e7ac617b33401fe00bb8..385564a928ec6e91af28e0457f2c93a9062236ff 100644 --- a/utils.py +++ b/utils.py @@ -237,10 +237,13 @@ def check_idp_response_selenium(sp,idp,test): # Catch SSL Exceptions and block the ECCS check except requests.exceptions.SSLError as e: - if (test): page_source = f"\nAn SSL Error occurred while opening https://{fqdn_idp}/robots.txt:\n\n{e}\n\nCheck it on SSL Labs: https://www.ssllabs.com/ssltest/analyze.html?d={fqdn_idp}" - else: page_source = f"<h1>SSL ERROR</h1><h2>An SSL error occurred for the server {fqdn_idp}:</h2><p>{e}</p><p>Check it on SSL Labs: <a href='https://www.ssllabs.com/ssltest/analyze.html?d={fqdn_idp}'>Click Here</a></p>" - store_page_source(page_source,idp,sp,test) - return (idp['entityID'],wayfless_url,check_time,"SSL-Error",webdriver_error) + if ('unable to get local issuer certificate' not in str(e)): + if (test): page_source = f"\nAn SSL Error occurred while opening https://{fqdn_idp}/robots.txt:\n\n{e}\n\nCheck it on SSL Labs: https://www.ssllabs.com/ssltest/analyze.html?d={fqdn_idp}" + else: page_source = f"<h1>SSL ERROR</h1><h2>An SSL error occurred for the server {fqdn_idp}:</h2><p>{e}</p><p>Check it on SSL Labs: <a href='https://www.ssllabs.com/ssltest/analyze.html?d={fqdn_idp}'>Click Here</a></p>" + store_page_source(page_source,idp,sp,test) + return (idp['entityID'],wayfless_url,check_time,"SSL-Error",webdriver_error) + else: + pass # Do not consider any other Exception except: @@ -271,10 +274,16 @@ def check_idp_response_selenium(sp,idp,test): check_time = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') + 'Z' driver.get(wayfless_url) - metadata_not_found = re.search(e_p.METADATAPATTERN,driver.page_source, re.I) - - idp_error = re.search(e_p.IDPERROR,driver.page_source, re.I) + # Support HTTP Basic Authentication + unauthorized = re.search('401.(\D.|\s.)?Unauthorized', driver.page_source, re.I) + if (unauthorized): + if (test): pgsrc = f"\n[PAGE_SOURCE]\n{driver.page_source}\n[WAYFLESS URL]{wayfless_url} - JAVASCRIPT FOUND" + else: pgsrc = driver.page_source + stored = store_page_source(pgsrc,idp,sp,test) + if (stored): + return (idp['entityID'],wayfless_url,check_time,"OK",webdriver_error) + metadata_not_found = re.search(e_p.METADATAPATTERN,driver.page_source, re.I) if (metadata_not_found): if (test): pgsrc = f"\n[PAGE_SOURCE]\n{driver.page_source}\n[WAYFLESS URL]{wayfless_url} - METADATA NOT FOUND" else: pgsrc = driver.page_source @@ -282,6 +291,7 @@ def check_idp_response_selenium(sp,idp,test): if (stored): return (idp['entityID'],wayfless_url,check_time,"No-eduGAIN-Metadata",webdriver_error) + idp_error = re.search(e_p.IDPERROR,driver.page_source, re.I) if (idp_error): if (test): pgsrc = f"\n[PAGE_SOURCE]\n{driver.page_source}\n[WAYFLESS URL]{wayfless_url} - IDP ERROR" else: pgsrc = driver.page_source @@ -293,10 +303,12 @@ def check_idp_response_selenium(sp,idp,test): if ('<iframe' in driver.page_source): follow_all_nested_iframes(driver) - driver.refresh() + load_js = re.search(e_p.JAVASCRIPT, driver.page_source, re.I) + if (load_js): + driver.refresh() WebDriverWait(driver, e_p.ECCS_SELENIUMPAGELOADTIMEOUT).until( - EC.presence_of_element_located((By.XPATH,'//input[@type="password"]')) + EC.presence_of_element_located((By.XPATH,'//input[@type="password"]|//input[@type="Password"]')) ) if (test): pgsrc = f"\n[WAYFLESS_URL]\n{wayfless_url} - OK" @@ -310,7 +322,7 @@ def check_idp_response_selenium(sp,idp,test): metadata_not_found = re.search(e_p.METADATAPATTERN,driver.page_source, re.I) try: - input_password_found = driver.find_element(By.XPATH,'//input[@type="password"]') + input_password_found = driver.find_element(By.XPATH,'//input[@type="password"]|//input[@type="Password"]') except NoSuchElementException as e: # This IF is for those IdP that doesn't consuming the eduGAIN metadata and reaching Timeout