diff --git a/composer.json b/composer.json
index 8d8c008cb4ddd308241835b6880b1bbf260b3ee2..391d7de4984c1c6598578cc7a218ae10a41faef8 100644
--- a/composer.json
+++ b/composer.json
@@ -1,5 +1,7 @@
 {
     "require": {
-        "twig/twig": "^3.0"
+        "twig/twig": "^3.0",
+        "spomky-labs/otphp": "^10.0",
+        "chillerlan/php-qrcode": "^4.3"
     }
 }
diff --git a/database/edugain_schema.sql b/database/edugain_schema.sql
index 47f951cf6048246711e11fdf77632eae2b47b667..9d88b3fd9799c619b1507c723b8d54a2acd38e96 100644
--- a/database/edugain_schema.sql
+++ b/database/edugain_schema.sql
@@ -1,3 +1,9 @@
+-- MySQL dump 10.14  Distrib 5.5.68-MariaDB, for Linux (x86_64)
+--
+-- Host: localhost    Database: edugain_twoln
+-- ------------------------------------------------------
+-- Server version	5.5.68-MariaDB
+
 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
 /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
 /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
@@ -25,7 +31,7 @@ CREATE TABLE `attribute` (
   `description` longtext COLLATE utf8_unicode_ci,
   PRIMARY KEY (`id`),
   UNIQUE KEY `UNIQ_FA7AEFFB5E237E06` (`name`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+) ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -41,10 +47,11 @@ CREATE TABLE `cert` (
   `sha1` char(255) COLLATE utf8_unicode_ci NOT NULL,
   `sha256` char(255) COLLATE utf8_unicode_ci NOT NULL,
   `code` char(20) COLLATE utf8_unicode_ci DEFAULT NULL,
+  `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
   PRIMARY KEY (`id`),
   KEY `sha1` (`sha1`),
   KEY `sha256` (`sha256`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+) ENGINE=MyISAM AUTO_INCREMENT=250 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -97,7 +104,7 @@ CREATE TABLE `entities` (
   PRIMARY KEY (`id`),
   UNIQUE KEY `entityid` (`entityid`),
   KEY `regauth` (`regauth`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+) ENGINE=MyISAM AUTO_INCREMENT=39362 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -124,12 +131,12 @@ CREATE TABLE `entities_status` (
   `sirtfi_idp_cnt` smallint(6) DEFAULT NULL,
   `sirtfi_sp_cnt` smallint(6) DEFAULT NULL,
   `sirtfi_cnt` smallint(6) DEFAULT NULL,
-  `sirtfi2_idp_cnt` smallint(6) DEFAULT NULL,
-  `sirtfi2_sp_cnt` smallint(6) DEFAULT NULL,
-  `sirtfi2_cnt` smallint(6) DEFAULT NULL,
   `hfd_cnt` smallint(6) DEFAULT NULL,
   `coco_cnt` smallint(6) DEFAULT NULL,
-  `rands_cnt` smallint(6) DEFAULT NULL
+  `rands_cnt` smallint(6) DEFAULT NULL,
+  `sirtfi2_idp_cnt` smallint(6) DEFAULT NULL,
+  `sirtfi2_sp_cnt` smallint(6) DEFAULT NULL,
+  `sirtfi2_cnt` smallint(6) DEFAULT NULL
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
@@ -181,7 +188,7 @@ CREATE TABLE `entity_contactperson` (
   `subtypeschema` char(128) COLLATE utf8_unicode_ci DEFAULT NULL,
   `subtype` char(64) COLLATE utf8_unicode_ci DEFAULT NULL,
   PRIMARY KEY (`id`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+) ENGINE=MyISAM AUTO_INCREMENT=105013 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -393,7 +400,7 @@ CREATE TABLE `entity_roles` (
   PRIMARY KEY (`id`),
   UNIQUE KEY `entity_id` (`entity_id`,`roledesc`),
   KEY `roledesc` (`roledesc`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+) ENGINE=MyISAM AUTO_INCREMENT=57738 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -457,7 +464,7 @@ CREATE TABLE `entityattributes_dict` (
   `short` char(30) COLLATE utf8_unicode_ci DEFAULT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `name` (`name`,`value`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+) ENGINE=MyISAM AUTO_INCREMENT=205 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -472,7 +479,7 @@ CREATE TABLE `entitywarnings_dict` (
   `message` char(255) COLLATE utf8_unicode_ci NOT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `message` (`message`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+) ENGINE=MyISAM AUTO_INCREMENT=61 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -577,6 +584,7 @@ CREATE TABLE `federation_status` (
   `last_expire_notification` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
   `new_profile_status` tinyint(1) DEFAULT NULL,
   `new_profile_modified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `sign_cert_sha1` char(255) COLLATE utf8_unicode_ci DEFAULT NULL,
   UNIQUE KEY `code` (`code`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
@@ -701,7 +709,7 @@ CREATE TABLE `person` (
   `role` char(15) COLLATE utf8_unicode_ci DEFAULT NULL,
   `code` char(20) COLLATE utf8_unicode_ci DEFAULT NULL,
   PRIMARY KEY (`id`)
-) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
+) ENGINE=MyISAM AUTO_INCREMENT=1991 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
@@ -796,7 +804,7 @@ CREATE TABLE `security_contact` (
 /*!50001 SET character_set_results     = utf8 */;
 /*!50001 SET collation_connection      = utf8_general_ci */;
 /*!50001 CREATE ALGORITHM=UNDEFINED */
-/*!50013 SQL SECURITY DEFINER */
+/*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */
 /*!50001 VIEW `federation_v` AS select `federation`.`code` AS `code`,`mds_data`.`reg_auth` AS `reg_auth`,`mds_data`.`metadata_url` AS `metadata_url`,`mds_data`.`status` AS `status`,`federation`.`fed_id` AS `fed_id`,`federation`.`europe` AS `europe`,`federation`.`contact_email` AS `contact_email`,`federation`.`url` AS `url`,`federation`.`policy_english` AS `policy_english`,`federation`.`policy_hardcopy` AS `policy_hardcopy`,`federation`.`policy_date` AS `policy_date`,`federation`.`membership_date` AS `membership_date`,`federation`.`production_date` AS `production_date`,`federation`.`name` AS `name`,`federation`.`policy_version` AS `policy_version`,`federation_status`.`valid_until` AS `valid_until`,`federation_status`.`last_notification` AS `last_notification`,`federation_status`.`feed_problem` AS `feed_problem`,`federation_status`.`new_profile_status` AS `new_profile_status`,`federation_status`.`new_profile_modified` AS `new_profile_modified` from ((`federation` left join `mds_data` on((`mds_data`.`code` = `federation`.`code`))) left join `federation_status` on((`federation`.`code` = `federation_status`.`code`))) */;
 /*!50001 SET character_set_client      = @saved_cs_client */;
 /*!50001 SET character_set_results     = @saved_cs_results */;
@@ -810,3 +818,5 @@ CREATE TABLE `security_contact` (
 /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
 /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
 /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+
+-- Dump completed on 2023-07-25 21:24:58
diff --git a/edugain.config/admin_template.php b/edugain.config/admin_template.php
new file mode 100644
index 0000000000000000000000000000000000000000..91120cb6a3cdcc059d8fe896128f4bdb8ce2058e
--- /dev/null
+++ b/edugain.config/admin_template.php
@@ -0,0 +1,26 @@
+<?php
+$admins= [
+'twoln'=>[
+   'hash' => '$2y$10$BSxcjTwsiycJKIlrpcUnmuLYwO15Zove3ota/FLPXYkUtyZNi60jC',
+   'mail'=>'twoln@umk.pl',
+//    'mail'=>'tw2529@gmail.com',
+    'groups'=>[EDUGAIN_OT, EDUGAIN_SEC],
+    'admin' => true,
+],
+'mgw'=>[
+    'hash'=>'$2y$10$CH259054ypVfnmUDcU14BuisV4/lnr.4EP.kjPTEzW6YjQ1xo4DpC',
+    'mail'=>'mgw@umk.pl',
+    'admin' => true,
+],
+'zbyszek'=>[
+    'hash'=>'$2y$10$blPXmNWOmkgqsrszyroTXOUXSqaG5jz5Mn8GgVzaaXquyE0dyWoO6',
+    'mail'=>'zbigniew.oltuszyk@man.poznan.pl',
+    'admin' => true,
+],
+    'secretariat'=> [
+    'hash' => '$2y$10$Rlikn5XeMwwWqPlwPdCSHexkOzaLSyoYMs.P/TOQM9Haly8ZrgRE2',
+    'admin' => false,
+    ]
+];
+
+
diff --git a/edugain.config/database_policy_template.php b/edugain.config/database_policy_template.php
new file mode 100644
index 0000000000000000000000000000000000000000..82be5b917c84bb239be4fff62b621efa956944b1
--- /dev/null
+++ b/edugain.config/database_policy_template.php
@@ -0,0 +1,3 @@
+<?php
+define('USER', 'policy');
+define('PASSWORD', 'xxxxxx');
diff --git a/edugain.config/database_ro_template.php b/edugain.config/database_ro_template.php
new file mode 100644
index 0000000000000000000000000000000000000000..f44ac2fbe6023ef929a700be45ee304003af509f
--- /dev/null
+++ b/edugain.config/database_ro_template.php
@@ -0,0 +1,3 @@
+<?php
+define('USER', 'edugain_ro');
+define('PASSWORD', 'xxxxxx');
diff --git a/edugain.config/database_rw_template.php b/edugain.config/database_rw_template.php
new file mode 100644
index 0000000000000000000000000000000000000000..16817fe0e3bb719b13d7e3b4f2d741b244fb8bbe
--- /dev/null
+++ b/edugain.config/database_rw_template.php
@@ -0,0 +1,3 @@
+<?php
+define('USER', 'edugain');
+define('PASSWORD', 'xxxxxx');
diff --git a/external_scripts/dbhost/cert.py b/external_scripts/dbhost/cert.py
new file mode 100755
index 0000000000000000000000000000000000000000..ab6e4eb760511d8c9fbc0cf99be36c177a427bdb
--- /dev/null
+++ b/external_scripts/dbhost/cert.py
@@ -0,0 +1,131 @@
+#!/usr/bin/python
+"""
+This script will add a federation signing certificate to the database.
+One required argument is the identifier of the federation. The certificate
+to be added must be in the PEM format must be named upstreamCert_IDENIFIER.cer
+and must be in the Certs subdirecory of your current directory.
+
+The database access details are below as "db, host, user", we suggest that
+it is safest to use localhost as host and have the "user" access right only
+from the localhost. The script will prompt for password of the user (again
+for security reasons this password is not stored in the script).
+
+If a given federation already has a certificate in the database, the script
+will ask if we want to remove or keep the other certificate. It is possible
+to have more certificates than one and they will all be tried during the
+validation process. This feature is meant for smooth transition form one
+signing key to another.  The old key should be removed after the transition
+has been finalised. 
+
+At the moment there is no script for removing the certificate, this needs
+to be done manually accessing the database.  Looking at the createtime field
+may be helpful in selecting the right certificate to remove.
+"""
+import sys,re,ssl,base64,urllib2
+import MySQLdb
+import os.path
+import getpass
+from M2Crypto import X509
+from OpenSSL import crypto
+
+import sys
+
+db = 'edugain'
+host = 'localhost'
+user = 'cert'
+passwd = getpass.getpass()
+selectcodes = "select code from federation"
+select = "select code from federation where code='%s'"
+exists = "select id from cert where code='%s'"
+delete = "delete from cert where id='%s'"
+insert = "insert into cert (code,data,sha1, sha256) values (%(code)s, %(cert)s, %(sha1)s, %(sha256)s)"
+update = "update federation_status set feed_etag=null, feed_last_modified='000-00-00 00:00:00'  where code='%(code)s'"
+
+mys = MySQLdb.connect(host, user, passwd, db)
+mys.autocommit(True)
+curs = mys.cursor()
+curd = mys.cursor()
+codes = []
+
+def yn_choice(msg, default='y'):
+    choices = 'Y/n' if default.lower() in ('y', 'yes') else 'y/N'
+    choice = raw_input("%s (%s) " % (msg, choices))
+    values = ('y', 'yes', '') if choices == 'Y/n' else ('y', 'yes')
+    return choice.strip().lower() in values
+
+if len(sys.argv) == 1:
+ print selectcodes
+ sys.exit(0)
+ cnt = curs.execute(selectcodes)
+ if cnt>0:
+  nextrow = curs.fetchone()
+  while nextrow:
+   code = None
+   row = nextrow
+   nextrow = curs.fetchone()
+   codes.append(row[0])
+else:
+ for i in range(1, len(sys.argv)):
+  codes.append(sys.argv[i])
+for code in codes:
+ try:
+  cnt = curs.execute(select % code)
+  if cnt==1:
+   nextrow = curs.fetchone()
+   while nextrow:
+     code = None
+     row = nextrow
+     nextrow = curs.fetchone()
+     if row is None:
+      continue
+     if row[0]:
+      code = row[0]
+   print 'looking for ', code, 'Certs/upstreamCert_'+code+'.cer'
+   if code and (os.path.isfile('Certs/upstreamCert_'+code+'.cer')):
+     f = open ('Certs/upstreamCert_'+code+'.cer', 'r')
+     cert = f.read()
+     cert_der =  ssl.PEM_cert_to_DER_cert(cert)
+     x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
+     fpsha256 = x509.digest('sha256').decode().replace(':', '')
+     fpsha1 = x509.digest('sha1').decode().replace(':', '')
+     #x509 = X509.load_cert_string(cert, X509.FORMAT_PEM)
+     #fpsha1 = x509.get_fingerprint('sha1')
+     #fpsha256 = x509.get_fingerprint('sha256')
+     c_is = curd.execute(exists % code)
+     if c_is == 2:
+         print 'It is the next certificate for ' + code + \
+               '\nWe can not register more than two certificates per federation!'
+         continue
+     if c_is:
+         choice = yn_choice('There is a certificate registered for ' +
+                            code +
+                            "\nDo you want to delete this certificate? ")
+         if choice:
+             c_nrow = curd.fetchone()
+             while c_nrow:
+                 c_id = None
+                 c_row = c_nrow
+                 c_nrow = curd.fetchone()          
+                 if c_row is None:
+                     continue
+                 if c_row[0]:
+                     print(delete % c_row[0])
+                     curd.execute(delete % c_row[0])
+         else:
+             choice = yn_choice('Do you want to register this certificate and ' +
+                                'have temporary two certificates for ' + code + '?')
+             if not choice:
+                 print code + " skipped, nothing happened"
+                 continue
+     cnt = curs.execute(insert, {'code': code, 'cert': base64.b64encode(cert_der), 'sha1': fpsha1, 'sha256': fpsha256})
+     print 'certificate for', code, 'added'
+     print(update % {'code': code})
+     curs.execute(update % {'code': code})
+   else:
+       print "Certificate not found, nothing happened"
+  else:
+      print 'No such federation: '+code
+ except IndexError:
+  print 'brak', code
+
+
diff --git a/lib/API.php b/lib/API.php
index b6dd1dd1fb5f3f993b3ff0536ad95a7eafc946dc..502c62428399f49769ac38a609a1f42f2b97a3f1 100644
--- a/lib/API.php
+++ b/lib/API.php
@@ -428,7 +428,7 @@ class API {
                     'required' => FALSE,
                     'default' => '0',
                     'values' => '0, 1, 2, 3, 4',
-                    'description' => '0 - show all; 1 - only production feds; 2 - all members; 3 - only candidates; 4 - only voting-only)',
+                    'description' => '0 - show all; 1 - only production feds; 2 - all members; 3 - only candidates; 4 - only voting-only',
                 ],
                 $this->addStdArgument('format'),
             ],
@@ -454,14 +454,14 @@ class API {
     private function action_list_feds() {
         $this->description([
             'header' => "List federation details (code, name, contact email, registration authority string, status).",
-            'supportedFormats' => ['json', 'print_r'],
+            'supportedFormats' => ['json', 'csv', 'print_r'],
             'arguments' => [
                 [
                     'arg' => 'opt',
                     'required' => FALSE,
                     'default' => '0',
                     'values' => '0, 1, 2, 3, 4',
-                    'description' => '0 - show all; 1 - only production feds; 2 - all members; 3 - only candidates; 4 - only voting-only)'],
+                    'description' => '0 - show all; 1 - only production feds; 2 - all members; 3 - only candidates; 4 - only voting-only'],
                 $this->addStdArgument('format'),
             ],
             'returns' => [
@@ -487,6 +487,17 @@ class API {
         $edugain = new eduGAIN($this->opts['opt']);
         $F = $edugain->FEDS;
         ksort($F, SORT_STRING | SORT_FLAG_CASE);
+        
+
+        if ($this->format == 'csv') {
+            $out = '';
+            foreach ($F as $fed => $A) {
+                $ln = $fed . "\t" . implode("\t", $A) . "\n"; 
+                $out .= $ln;
+            }
+            return($out);
+        }
+
         foreach ($F as $fed => $A) {
             $out[$fed] = ['name' => $A['name'],
                 'email' => $A['contact_email'],
@@ -1378,7 +1389,6 @@ Click action names for a full description.</p>';
             'returns' => ['description' => 'Arrays of structured data - one entry per link type and test time.'],
             'examples' => [['opt' => [], 'title' => 'Status list from the last test'],],
         ]);
-
         if ($this->opts['help'] == 1) {
             return "";
         }
diff --git a/lib/Map_source/worldHigh.svg b/lib/Map_source/worldHigh.svg
index 950b31d2156ba08b587ba68012740313238e1544..f909ae70c5b3221f3da67070f2c0dbeac363b5a8 100644
--- a/lib/Map_source/worldHigh.svg
+++ b/lib/Map_source/worldHigh.svg
@@ -1,7 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- (c) ammap.com | SVG map of World High -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:amcharts="http://amcharts.com/ammap" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
-	<defs>
+<svg 
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:amcharts="http://amcharts.com/ammap"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    version="1.1"
+    width="1009"
+    height="500">
+
+    <defs>
 		<style type="text/css">
 			.land
 			{
@@ -277,4 +284,4 @@
 		<path id="ZM" title="Zambia" class="land" style="" d="M568.089,489.016L568.099,489.091L568.176,489.233L568.257,489.335L568.264,489.485L568.302,489.625L568.42,489.659L568.517,489.668L568.607,489.568L568.729,489.57L568.863,489.634L568.911,489.795L569.016,490.011L569.185,490.159L569.299,490.302L569.26,490.562L569.188,490.799L569.418,491.034L569.717,491.257L569.798,491.356L569.822,491.688L569.868,491.8L570.071,492.076L570.17,492.259L570.163,492.365L569.618,492.912L569.445,492.965L569.281,492.996L569.135,493.108L569.047,493.225L569.079,493.287L569.137,493.474L569.264,493.77L569.379,493.977L569.283,494.236L569.067,494.676L568.967,494.715L568.949,495.048L569.015,495.17L569.123,495.266L569.167,495.492L569.17,495.804L569.158,496.055L569.022,496.694L569.268,497.253L569.352,497.314L569.693,497.318L569.751,497.365L569.67,497.524L569.522,497.688L569.431,497.771L568.997,497.962L568.374,498.173L568.244,498.375L568.161,498.67L568.231,498.843L568.313,498.942L568.287,499.199L568.232,499.471L568.251,499.685L568.223,499.874L568.141,499.967L568.032,500.252L567.898,500.539L567.792,500.67L567.636,500.807L567.389,500.921L567.393,500.979L567.673,501.111L567.745,501.203L567.771,501.266L567.711,501.326L567.654,501.411L567.783,501.499L567.941,501.573L568.09,501.764L568.223,502.031L568.261,502.125L568.292,502.162L568.34,502.166L568.433,502.126L568.604,501.98L568.729,501.928L568.88,502.136L568.277,502.342L567.961,502.45L567.06,502.758L566.273,503.027L566.068,503.078L565.66,503.209L565.457,503.29L564.746,503.525L564.449,503.642L564.21,503.759L563.623,503.933L563.068,504.097L562.462,504.267L561.784,504.456L561.403,504.593L561.146,504.711L560.545,504.951L560.517,505.009L560.525,505.172L560.602,505.508L560.752,505.813L560.878,505.989L560.96,506.441L561.006,506.838L560.598,506.839L559.88,506.841L559.135,506.842L558.455,506.994L557.896,507.225L557.229,507.586L557.012,507.728L556.844,507.837L556.738,507.978L556.686,508.286L556.686,508.762L556.618,509.104L556.416,509.419L556.416,509.418L555.403,509.799L554.741,510.11L554.091,510.479L553.598,510.958L553.262,511.547L552.704,512.277L552.136,512.91L551.533,513.584L550.857,513.828L550.289,513.773L549.604,513.5L549.06,513.448L548.657,513.618L548.285,513.565L547.943,513.291L547.657,513.191L547.425,513.265L547.129,513.252L546.588,513.102L546.12,512.635L545.867,512.443L545.672,512.369L545.112,512.294L543.827,512.187L543.693,512.211L543.159,512.303L542.492,512.418L541.918,512.533L541.317,512.653L540.758,512.167L540.125,511.616L539.468,511.001L538.975,510.52L538.731,510.243L538.298,509.877L537.986,509.698L537.865,509.608L537.555,508.636L537.386,507.744L537.386,507.073L537.385,506.144L537.385,505.216L537.385,504.29L537.384,503.365L537.384,502.441L537.384,501.52L537.383,500.599L537.383,499.679L537.383,499.232L538.03,499.232L538.764,499.232L539.531,499.232L540.365,499.232L541.199,499.232L542.033,499.232L542.615,499.232L542.768,499.225L542.952,499.197L542.965,499.106L542.725,498.655L542.737,498.495L542.801,498.189L542.9,497.925L543.031,497.578L543.045,497.374L542.94,496.709L542.95,496.339L542.98,495.954L543.01,495.59L542.974,495.336L543.018,495.198L543.097,495L543.138,494.776L543.187,494.68L543.171,494.591L543.127,494.425L543.083,494.053L543.022,493.536L542.961,493.164L543.063,493.185L543.275,493.22L543.378,493.402L543.438,493.602L543.581,493.613L543.953,493.731L544.082,493.896L544.167,494.253L544.116,494.436L543.996,494.583L544.116,494.713L544.365,494.8L544.511,494.774L544.931,494.531L545.099,494.488L545.318,494.44L545.516,494.378L546.075,494.267L546.38,494.218L546.552,494.131L546.672,494.131L546.758,494.201L546.681,494.453L546.655,494.679L546.762,495.102L546.842,495.299L547.022,495.443L547.153,495.518L547.298,495.671L547.599,495.645L548.261,495.861L548.462,495.961L548.741,496.061L548.939,496.098L549.622,496.174L549.874,496.225L550.342,496.293L550.716,496.305L550.981,496.274L551.167,496.212L551.281,496.144L551.333,496.085L551.41,495.874L551.549,495.416L551.604,495.279L551.742,495.216L551.921,495.175L552.026,495.248L552.143,495.756L552.664,496.215L552.842,496.601L552.972,496.931L553.085,497.021L553.283,497.134L553.599,497.174L553.881,497.186L554.475,497.424L554.947,497.613L555.285,497.749L555.44,497.852L555.548,498.021L555.612,498.152L555.716,498.492L555.827,498.761L556.008,498.812L556.17,498.833L556.33,499.017L556.451,499.178L556.691,499.572L556.868,499.844L556.926,500.109L557.127,500.286L557.401,500.361L557.654,500.371L557.8,500.292L558.159,500.154L558.439,499.997L558.643,499.942L558.764,499.976L558.856,500.085L558.905,500.298L558.917,500.417L559.116,500.53L559.263,500.485L559.32,500.354L559.323,500.289L559.322,499.709L559.322,499.207L559.321,498.735L559.321,498.16L559.32,497.659L559.32,497.247L559.319,496.817L559.191,496.841L559.03,496.939L558.659,496.951L558.514,497.024L558.469,497.135L558.497,497.279L558.505,497.475L558.45,497.567L558.288,497.604L558.052,497.528L557.625,497.43L557.269,497.369L557.013,497.109L556.667,496.717L556.44,496.52L555.894,496.111L555.801,496.029L555.635,495.838L555.493,495.514L555.423,495.299L555.357,495.139L555.284,494.901L555.416,494.535L555.601,493.839L555.735,493.338L555.809,492.967L556.074,492.589L556.093,492.251L555.986,491.818L556.013,491.579L556.031,490.972L556.046,490.462L556.05,490.214L555.977,489.783L555.798,489.306L555.404,488.642L555.406,488.5L555.64,488.343L556.011,488.069L556.193,487.906L556.411,487.675L556.508,487.557L556.722,487.261L556.857,487.02L556.904,486.709L556.802,486.413L557.01,486.355L557.693,486.251L558.445,486.136L559.238,486.014L560.038,485.892L560.813,485.773L561.516,485.666L562.002,485.591L562.074,485.794L562.226,486.132L562.397,486.38L562.612,486.598L562.794,486.73L562.915,486.77L563.685,486.756L563.961,486.888L564.202,487.056L564.262,487.316L564.42,487.479L564.591,487.607L564.665,487.623L564.791,487.592L564.997,487.589L565.188,487.645L565.279,487.702L565.288,487.92L565.346,488.017L565.606,488.055L565.871,488.072L566.127,488.22L566.403,488.246L566.723,488.307L566.875,488.466L567.214,488.629L567.63,488.775L567.93,488.94z"/>
 		<path id="ZW" title="Zimbabwe" class="land" style="" d="M563.508,526.797L563.254,526.625L562.907,526.513L562.465,526.461L561.892,526.482L561.186,526.577L560.429,526.462L559.62,526.14L558.949,526.026L558.147,526.166L558.111,526.169L557.973,526.06L557.753,525.825L557.388,525.783L557.289,525.729L557.207,525.641L557.153,525.529L557.132,525.404L557.192,525.018L557.159,524.975L557.062,524.928L556.861,524.882L556.38,524.707L555.774,524.537L554.791,524.351L554.409,524.303L554.32,524.246L554.21,524.104L554.021,523.661L553.844,523.369L553.421,522.918L553.354,522.779L553.375,522.421L553.406,522.133L553.451,521.889L553.431,521.66L553.425,521.376L553.438,521.186L553.381,521.104L553.228,521.046L552.791,521.02L552.263,521.031L552.246,520.743L552.195,520.297L552.097,520.041L551.975,519.908L551.732,519.769L551.241,519.579L550.572,519.29L550,518.862L549.345,518.331L549.14,518.239L548.898,517.741L548.53,516.889L548.554,516.606L548.498,516.467L548.14,516.05L548.061,515.833L547.998,515.614L547.429,515.004L547.235,514.738L547.087,514.396L546.94,514.123L546.816,514.013L546.654,513.828L546.542,513.616L546.49,513.458L546.533,513.247L546.588,513.102L547.129,513.252L547.425,513.265L547.657,513.191L547.943,513.291L548.285,513.565L548.657,513.618L549.06,513.448L549.604,513.5L550.289,513.773L550.857,513.828L551.533,513.584L552.136,512.91L552.704,512.277L553.262,511.547L553.598,510.958L554.091,510.479L554.741,510.11L555.403,509.799L556.416,509.418L556.416,509.419L556.618,509.104L556.686,508.762L556.686,508.286L556.738,507.978L556.844,507.837L557.012,507.728L557.229,507.586L557.896,507.225L558.455,506.994L559.135,506.842L559.88,506.841L560.598,506.839L561.006,506.838L561.012,507.295L561.043,507.81L561.123,507.859L561.663,507.87L562.529,507.907L563.364,507.941L563.897,508.315L564.076,508.395L564.63,508.495L565.338,509.118L566.19,509.176L566.776,509.371L567.292,509.585L567.589,509.842L567.781,509.9L568.041,509.919L568.167,509.943L568.14,510.128L567.966,510.442L567.989,510.892L568.228,511.516L568.26,512.06L568.187,513.019L568.189,513.95L568.214,514.283L568.253,514.503L568.304,514.624L568.294,514.762L568.152,515.154L568.038,515.566L568.034,515.731L567.99,515.848L567.905,515.951L567.533,516.142L567.47,516.26L567.471,516.474L567.518,516.654L567.658,516.72L567.826,516.822L567.892,516.956L567.893,517.098L567.839,517.361L567.689,517.796L567.839,518.298L568.006,518.624L568.237,519L568.333,519.233L568.328,519.4L568.293,519.563L567.948,520.253L567.699,520.683L567.395,521.143L566.993,521.432L566.889,521.571L566.848,521.729L566.862,522.075L566.844,522.438L566.5,522.995L566.713,523.475L566.665,523.52L566.549,523.588L566.054,524.129L565.553,524.678L565.187,525.079L564.771,525.535L564.305,526.047L563.906,526.486z"/>
 	</g>
-</svg>
\ No newline at end of file
+</svg>
diff --git a/lib/config-template.php b/lib/config-template.php
index 3991236decd054dbe6d356f356c7740fd4acdfac..8a47e403376782b1f3c9e6d03326fae7754583aa 100644
--- a/lib/config-template.php
+++ b/lib/config-template.php
@@ -1,22 +1,56 @@
 <?php
 // site settings
 //
-// the directory holding user access config files 
-define('eduGAIN_config','path_to_the_config_directory');
+// the directory holding user access config files (the trailig slash in important)
+define('eduGAIN_config','path_to_the_config_directory/');
 // the root URL for the site
 define('ROOT_URL','https://technical.edugain.org');
 // the log file
 define('LOG_FILE','/var/log/edugain/technical_site.log');
+// the OTP server URL if you define this as null OTP will not be used
+//define('OTP_SERVER', null);
+define('OTP_SERVER', 'XXXX/otp_server.php');
+define('OTP_DATABASE', 'otp');
 
 // database details (user details are taken form appropraiate configs in eduGAIN_config directory)
 define('DB_HOST','localhost');
-define('DB_DATABASE','edugain_twoln');
+define('DB_DATABASE','edugain');
 
 // Validator locations
 define('VALIDATOR_SCRIPTS', '/srv/edugain.config/validator_scripts');
 define('VALIDATOR_SCHEMAS_LOG', '/srv/edugain.config/validator_scripts/logs');
 define('VALIDATOR_TMP', '/srv/edugain.config/mds/tmp');
 
+// details of simpleSAMLphp for management login
+
+define('SSP_PATH_TO_AUTOLOADER','/var/www/html/simplesamlphp/lib/_autoload.php');
+// set EDUGAIN_OT, EDUGAIN_SEC, EDUGAIN_SUPP to the value assinged in eduTEAMS
+define('EDUGAIN_OT', 'eduGAIN-OT');
+define('EDUGAIN_SEC', 'eduGAIN-Secretariat');
+define('EDUGAIN_SUPP', 'eduGAIN-Support');
+
+// if you set LOCAL_AUTH you will enable the option to use local accounts, not only federated
+define('LOCAL_AUTH', false);
+
+
+// system settings
+define('COCO_ALWAYS',1); // set this to 0 if CoCo stuff is to be shown only when CoCo EC is selected
+// the VERSION constant can be used to give templates/js/ files a version-dependent
+// name and avoid problems with cacheing. 
+define('VERSION','_t');
+// this is the duration that the federation news history will show
+define('BACK_TIME_FED', '18 months');
+// this is the duration that the eneities news history will show
+define('BACK_TIME_ENTITIES', '14 days');
+// the log level use 3 or less for normal production
+define('LOG_LEVEL',5);
+// number of seconds to the expire time that causes the warning icon being shown
+define('VALIDITY_WARNING_THRESHOLD', 388800);
+// number of seconds to the expire time that causes the alert icon being shown
+define('VALIDITY_ALERT_THRESHOLD', 10800);
+
+// The settings below can probably stay as they are
+// 
 // local URLs (mainly for the menu items)
 define('API_URL', ROOT_URL.'/api');
 define('PARTICIPATING_FEDERATIONS_URL', ROOT_URL.'/status');
@@ -56,16 +90,6 @@ define('WIKI_URL','https://wiki.edugain.org/');
 define('WIKI_ADD_IDP_URL','https://wiki.geant.org/display/eduGAIN/How+to+set+up+an+Identity+Provider+for+eduGAIN');
 define('WIKI_ADD_SERVICE_URL','https://wiki.geant.org/display/eduGAIN/How+to+Join+eduGAIN+as+Service+Provider');
 
-// system settings
-define('COCO_ALWAYS',1); // set this to 0 if CoCo stuff is to be shown only when CoCo EC is selected
-define('VERSION','_t');
-define('BACK_TIME_FED', '18 months');
-define('BACK_TIME_ENTITIES', '14 days');
-define('LOG_LEVEL',5);
-define('VALIDITY_WARNING_THRESHOLD', 388800);
-define('VALIDITY_ALERT_THRESHOLD', 10800);
-
-
 // TWIG directories - do not modify unless you know what you are doing
 define('CACHE_LOCATION', '../var/cache/');
 define('PAGE_LOGIC_LOCATION', '../page-logic/');
diff --git a/lib/eduGAIN_entity.php b/lib/eduGAIN_entity.php
index 717933e3710fb4c281ec1748c5873fa0b2f665a8..557c9d67562f109edfe7df11db33a4e409b5fba9 100644
--- a/lib/eduGAIN_entity.php
+++ b/lib/eduGAIN_entity.php
@@ -74,18 +74,6 @@ class eduGAIN_entity extends eduGAIN {
         $this->globalStats = $out;
     }
 
-    private function generateNamespaces() {
-        $out = '';
-        $q = "SELECT * FROM feed_namespaces";
-        $result = $this->databaseQuery($q);
-        while ($obj = $result->fetch_assoc()) {
-            $nskey = $obj['nskey'];
-            $nsvalue = $obj['nsvalue'];
-            $out .= $nskey . '="' . $nsvalue . '" ';
-        }
-        return $out;
-    }
-
     /**
      * Show entity metadata XML code
      */
@@ -99,13 +87,11 @@ class eduGAIN_entity extends eduGAIN {
         if (!is_numeric($entity_row_id)) {
             exit;
         }
-        $ns = $this->generateNamespaces();
         $q = "SELECT entity FROM entities WHERE id=$entity_row_id";
         $result = $this->databaseQuery($q);
         if ($obj = $result->fetch_assoc()) {
             $entityText = base64_decode($obj['entity']);
-            $entity = preg_replace('/md:EntityDescriptor /','md:EntityDescriptor ' . $ns, $entityText);
-            return($entity);
+            return($entityText);
         } else {
             die("entity search problem");
         }
@@ -981,7 +967,7 @@ class eduGAIN_entity extends eduGAIN {
         $c_span = ' colspan=2';
         $out .= "<tr class='$row_class'><td style='text-align:right; width: 3em;  padding-right:8px' rowspan=$j>ROW_NUMBER_REPLACE</td><td class='entity-role'>" .
                 $V['type'] . "</td><td $c_span>" . $V['val'] . "</td><td rowspan=$j style='width:5em; vertical-align: top; padding:3px;'>$eccs $clash</td><td rowspan=$j class='$validatorClass'><a class='entity_links' id='link_" . $entity["id"] . "' href='javascript:show_details(" .
-                $entity["id"] . ",NEXT_REPLACE,PREV_REPLACE)'>Entity details</a></td>
+                $entity["id"] . ",NEXT_REPLACE,PREV_REPLACE)'>Entity&nbsp;details</a></td>
           </tr>\n";
 
         foreach ($TR as $V) {
diff --git a/lib/eduGAIN_manage.php b/lib/eduGAIN_manage.php
new file mode 100644
index 0000000000000000000000000000000000000000..4ae0cb346c899dfa80e5f2de6602a1920d101a5f
--- /dev/null
+++ b/lib/eduGAIN_manage.php
@@ -0,0 +1,358 @@
+<?php
+
+setlocale(LC_ALL, 'en_GB.UTF8');
+date_default_timezone_set('UTC');
+
+
+class eduGAIN_manage extends eduGAIN {
+
+    public function __construct($production_only = 0) {
+        parent::__construct($production_only);
+        $this->load_federation_map_codes();
+        $this->load_federation_country_names();
+        $this->load_federation_search_strings();
+    }
+
+    private static $Colors = [
+        1 => "#00899F",
+        4 => "#B5D000",
+        6 => "#004359",
+    ];
+    private $Color;
+    
+    private $args = [];
+    private $changedFields = [];
+    public $code = '';
+    private $updateMaps = true;
+
+    public function load_colors() {
+        $this->Color = [];
+        $this->Color['va'] = '#c0c0c0';
+        $this->Color['im'] = '#c0c0c0';
+        $this->Color['gg'] = '#c0c0c0';
+        $this->Color['je'] = '#c0c0c0';
+        foreach ($this->FEDS as $fed => $F) {
+            preg_match('/([A-Z]*)-?/', $F['code'], $m);
+            $country = strtolower($m[1]);
+            $this->Color[$country] = empty($this->Colors[$F['status']]) ? 'none' : $this->Colors[$F['status']];
+        }
+    }
+
+    
+    private function sanitizePost($postInput) {
+        $output = [];
+        $code = $postInput['code'];
+        if (!preg_match('/^[A-Z]+-?[A-Z]*$/', $code)) {
+            exit;
+        }
+        $this->code = $code;
+        
+        $output['code'] = $code;
+        $this->changedFields = json_decode($this->cleanInput($postInput["changed_fields"], false));
+        foreach ([
+        'europe',
+        'policy_english',
+        'tsg_delegate_0_3',
+        'tsg_deputy_0_3',
+        'tsg_delegate_1_3',
+        'tsg_deputy_1_3',
+        ] as $key) {
+            if (isset($postInput[$key])) {
+                $output[$key] = 1;
+            } else {
+                $output[$key] = 0;
+            }
+        }
+
+        foreach ( ['url', 'metadata_url', 'reg_auth', 'security_contact_url_value',] as $key) {
+            if(!empty($postInput[$key])) {
+                $output[$key] = filter_var($this->cleanInput($postInput[$key]), FILTER_SANITIZE_URL);
+            }
+        }
+        
+        for ($i = 0; $i < 6; $i++) {
+            foreach (['policy', 'registration'] as $type) {
+                if (isset($postInput[$type . '_' . $i])) {
+                    $output[$type . '_' . $i] = filter_var($this->cleanInput($postInput[$type . '_' . $i]), FILTER_SANITIZE_URL);
+                }
+            }
+        }
+        
+        foreach ( ['status', 'fed_status', 'policy_version', 'policy_hardcopy'] as $key) {
+            if(isset($postInput[$key]) &&  $postInput[$key] !== "") {
+                $output[$key] = filter_var($postInput[$key], FILTER_SANITIZE_NUMBER_INT);
+            }
+        }
+        
+        foreach ( ['contact_email', 'security_contact_mail_value', 'tsg_delegate_0_2', 'tsg_delegate_1_2', 'tsg_deputy_0_2', 'tsg_deputy_1_2'] as $key) {
+            if(!empty($postInput[$key])) {
+                $output[$key] =  filter_var($postInput[$key], FILTER_SANITIZE_EMAIL);
+            }
+        }
+        
+        if (!empty($postInput[$key]) && preg_match('/^\+?[\d ]+$/', $postInput['security_contact_phone_value'])) {
+            $output['security_contact_phone_value'] = $postInput['security_contact_phone_value'];
+        }
+        
+        foreach ( ['policy_date', 'membership_date', 'production_date'] as $key) {
+            if (!empty($postInput[$key]) && preg_match('/^\d\d\d\d-\d\d-\d\d$/', $postInput[$key])) {
+                 $output[$key] = $postInput[$key];
+            }
+        }
+        
+        foreach ( ['name', 'tsg_delegate_0_0', 'tsg_delegate_0_1', 'tsg_delegate_1_0', 'tsg_delegate_1_1', 'tsg_deputy_0_0', 'tsg_deputy_0_1', 'tsg_deputy_1_0', 'tsg_deputy_1_1'] as $key) {
+            if(!empty($postInput[$key])) {
+                $output[$key] = $this->cleanInput($postInput[$key]);
+            }
+        }
+        
+        for ($i = 0; $i < 5; $i++) {
+            $key = 'country_code_' . $i;
+            if(isset($postInput[$key]) && ($postInput[$key] === "0" || $postInput[$key] === "1" || preg_match('/^[a-z][a-z]$/', $postInput[$key]))) {
+                if ($postInput[$key] === "0" || $postInput[$key] === "1" || isset(Countries::names[strtoupper($postInput[$key])])) {
+                    $output[$key] = $postInput[$key];
+                }
+            }
+        }
+        $this->args = $output;
+        Utils::debug(4, $this->changedFields, "sanitizePost: Changed fields: ", "\n");
+    }
+    
+    private function handleDeclaration() {
+        if (isset($_FILES['pol_file']['error']) && ($_FILES['pol_file']['error'] === UPLOAD_ERR_OK)) {
+            if (isset($_FILES['pol_file']['tmp_name'])) {
+                if ("eduGAIN_Declaration_" . $this->code . "_" . $this->args['policy_version'] . ".pdf" != $_FILES["pol_file"]["name"]) {
+                    echo("wrong file name: " . $_FILES["pol_file"]["name"] . ", upload skipped");
+                } else {
+                    if (file_exists("../../Declarations/" . $_FILES["pol_file"]["name"])) {
+                        echo("file " . $_FILES["pol_file"]["name"] . " already exists - upload skipped");
+                    } else {
+                        move_uploaded_file($_FILES["pol_file"]["tmp_name"], "../../Declarations/" . $_FILES["pol_file"]["name"]);
+                        echo("file " . $_FILES["pol_file"]["name"] . " uploaded");
+                    }
+                }
+            }
+        }        
+    }
+    
+    private function uploadDeclaration() {
+        if (isset($_FILES['pol_file']['error']) && ($_FILES['pol_file']['error'] === UPLOAD_ERR_OK)) {
+            if (isset($_FILES['pol_file']['tmp_name'])) {
+                $declarationFileName = "eduGAIN_Declaration_" . $this->code . "_" . $this->args['policy_version'] . ".pdf";
+                move_uploaded_file($_FILES["pol_file"]["tmp_name"], "../../Declarations/" . $declarationFileName);
+                echo("file " . $_FILES["pol_file"]["name"] . " uploaded");                
+            }
+        }  
+    }
+    
+    private function updateFederationStatus($superadmin) {
+        
+    }
+    
+    private function updateUniqueRecord($table, $fields) {
+        Utils::debug(4, $fields, "updateUniqueRecord: Fields:", "\n");
+        Utils::debug(4, $this->changedFields, "updateUniqueRecord: Changed fields:", "\n");
+        $update_map = 0;
+        $noUpdates = true;
+        $U = [];
+        $V = [];
+        $FL = [];
+        foreach ($fields as $f) {
+            if (!isset($this->args[$f])) {
+                continue;
+            }
+            $FL[] = $f;
+            $v = $this->args[$f];
+            $val = is_numeric($v) ? $v : "'$v'";
+            $V[] = $val;
+            if (in_array($f, $this->changedFields)) {
+                $U[] = "$f=$val";
+                $noUpdates = false;
+            }
+        }
+        if ($noUpdates) {
+            Utils::debug(4, "updateUniqueRecord: noupdates\n");
+            return false;
+        }
+        $q = "INSERT INTO $table (" . join(',', $FL) . ") VALUES (" . join(',', $V) . ")" .
+        " ON DUPLICATE KEY UPDATE " . join(',', $U);
+        $this->databaseQuery($q);
+        return true;
+    }
+    
+    private function updateMdsData($superadmin) {
+        if ($superadmin !== 1) {
+            if ($this->args['status'] > 4 || in_array('code', $this->changedFields) ||
+                    in_array('metadata_url', $this->changedFields) || in_array('reg_auth', $this->changedFields)) {
+                Utils::debug(4, "updateMdsData - updates not allowed without superadmin rights\n");
+                return;
+            }
+        }
+        Utils::debug(4, $this->args['status'], "MDS Status:", "\n");
+        $f = ['code', 'metadata_url', 'reg_auth', 'status'];
+        $this->updateUniqueRecord('mds_data', $f);
+        if ($this->args['status'] != $this->args['fed_status'] && $this->updateMaps) {
+            $this->updateMaps = false;
+            eduGAIN_map::make_maps();
+            eduGAIN_map::make_maps(1);
+        }
+    }
+    
+    private function updateFederationTable() {
+        $f = ['fed_id', 'code', 'europe', 'contact_email', 'url', 'policy_english',
+         'policy_hardcopy', 'policy_date', 'membership_date', 'production_date', 'policy_version',
+         'name'];
+        $this->updateUniqueRecord('federation', $f);
+    }
+    
+    private function updateSecurityContacts() {
+        $noUpdates = true;
+        $table = 'security_contact';
+        $fields = ['mail', 'url', 'phone'];
+        $inserts = [];
+        foreach ($fields as $field) {
+            $field_value = 'security_contact_'.$field.'_value';
+            Utils::debug(4, $field, "updateSecurityContacts: testing: ", "\n");
+            if (in_array($field_value, $this->changedFields)) {
+                $noUpdates = false;
+            }
+            if (!empty($this->args[$field_value])) {
+                Utils::debug(4, $field, "updateSecurityContacts: updating: ", "\n");
+                $q = "INSERT INTO $table (code, type, value) VALUES ('$this->code', '$field', '" . $this->args[$field_value] . "')";
+                $inserts[] = $q;
+            }
+        }
+        $this->runUpdates($table, $noUpdates, $inserts);
+    }
+
+    private function updatePersonTable() {
+        $Person = [];
+        $noUpdates = true;
+        foreach ([
+            'tsg_delegate_0_0',
+             'tsg_delegate_0_1',
+             'tsg_delegate_0_2',
+             'tsg_delegate_0_3',
+             'tsg_deputy_0_0',
+             'tsg_deputy_0_1',
+             'tsg_deputy_0_2',
+             'tsg_deputy_0_3',
+             'tsg_delegate_1_0',
+             'tsg_delegate_1_1',
+             'tsg_delegate_1_2',
+             'tsg_delegate_1_3',
+             'tsg_deputy_1_0',
+             'tsg_deputy_1_1',
+             'tsg_deputy_1_2',
+             'tsg_deputy_1_3',
+            ] as $key) {
+            $m = explode('_', $key);
+            $function = $m[1];
+            $functionIndex = $m[2];
+            $functionField = $m[3];
+            $v = $this->args[$key];
+            $v = is_numeric($v) ? $v : "'$v'";
+            $Person[$function][$functionIndex][$functionField] = $v;
+            if (in_array($key, $this->changedFields)) {
+                $noUpdates = false;
+            }
+        }
+        if ($noUpdates) {
+            return;
+        }
+        $table = 'person';
+        $q = "DELETE FROM $table WHERE code = '$this->code'";
+        $this->databaseQuery($q);
+        foreach ($Person as $function => $PR) {
+            foreach ($PR as $p) {
+                // we assume that the person must have a last-name
+                if ($p[1] == "''") {
+                    continue;
+                }
+                $q = "INSERT INTO $table (given_name, last_name, email, show_email, code, role) VALUES (" . join(',', $p) . ",'$this->code','tsg_$function')";
+                $this->databaseQuery($q);
+            }
+        }
+        
+    }
+
+    private function updatePolicyTable() {
+        $table = 'policy';
+        $noUpdates = true;
+        $inserts = [];
+        for ($i = 0; $i < 6; $i++) {
+            foreach (['policy', 'registration'] as $type) {
+                if (isset($this->args[$type . '_' . $i]) && $this->args[$type . '_' . $i]) {
+                    $q = "INSERT INTO $table (code,type,url) VALUES ('$this->code','$type','" . $this->args[$type . '_' . $i] . "')";
+                    $inserts[] = $q;
+                    if (in_array($type . '_' . $i, $this->changedFields)) {
+                        $noUpdates = false;
+                    }
+                }
+            }
+        }
+        $this->runUpdates($table, $noUpdates, $inserts);
+    }
+    
+    private function runUpdates($table, $noUpdates, $inserts) {
+        if ($noUpdates) {
+            return;
+        }
+        $q = "DELETE FROM $table WHERE code = '$this->code'";
+        $this->databaseQuery($q);
+        foreach ($inserts as $q) {
+            $this->databaseQuery($q);
+        }
+    }
+
+    private function updateRegions() {
+        $table = 'regions';
+        $noUpdates = true;
+        $inserts = [];
+        for ($i = 0; $i < 5; $i++) {
+            $key = 'country_code_' . $i;
+            if (!isset($this->args[$key])) {
+                continue;
+            }
+            if ($this->args[$key] !== "0" && in_array($key, $this->changedFields)) {
+                $noUpdates = false;
+            }
+        }
+        if ($noUpdates) {
+            return;
+        }
+        
+        for ($i = 0; $i < 5; $i++) {
+            $key = 'country_code_' . $i;
+            $countryCode = $this->args[$key];
+            if (empty($countryCode) || $countryCode === "1") {
+                continue;
+            }
+            $inserts[]= "INSERT INTO $table (code,country_code,priority) VALUES('$this->code','$countryCode',$i)";
+        }
+        $this->runUpdates($table, $noUpdates, $inserts);
+        
+        if ($this->updateMaps) {
+            $this->updateMaps = false;
+            eduGAIN_map::make_maps();
+            eduGAIN_map::make_maps(1);
+        }
+    }
+
+    public function update_EduGAINTables($A, $superadmin) {
+        if (empty($A['code'])) {
+            return;
+        }
+        $this->sanitizePost($A);
+        $this->uploadDeclaration();
+        $this->updateMdsData($superadmin);
+        $this->updateSecurityContacts();
+        $this->updateFederationTable();
+        $this->updatePersonTable();
+        $this->updatePolicyTable();
+        $this->load_colors();
+        $this->updateRegions();
+    }
+
+}
+
diff --git a/lib/eduGAIN_manage_gui.php b/lib/eduGAIN_manage_gui.php
new file mode 100644
index 0000000000000000000000000000000000000000..3674c246046ead1fec4fc781acc195f0950ca429
--- /dev/null
+++ b/lib/eduGAIN_manage_gui.php
@@ -0,0 +1,185 @@
+<?php
+
+/**
+ * Description of eduGAIN_manage_gui
+ *
+ * @author twoln
+ */
+setlocale(LC_ALL, 'en_GB.UTF8');
+
+class eduGAIN_manage_gui {
+    public function __construct($role) {
+        $this->role = $role;
+    }
+    
+    public static function pageGraphics() {
+        print '<head>
+<link rel="stylesheet" type="text/css" href="css/master.css">
+<link rel="stylesheet" href="third-party/theme/jquery-ui.css">
+<script src="third-party/jquery.js"></script>
+<script src="third-party/jquery-ui.js"></script>
+</head>
+<body>
+    <div id="inner">
+<header>
+       <div style="position: relative; height: 115px; width: 100%; background: #003f5f">
+     <img id="geant_logo_t" src="images/geant_logo1.png" style="position: absolute; left: 20px; bottom: 0px;">
+     <img src="images/edugain_logo1.png" style="position:absolute; right: 40px; bottom: 0px;">
+     </div>
+    </header>';
+    }
+        
+    public function row($label, $type, $id, $sub_id = '', $critical = 0, $hidden = 0, $nonempty_level = 10) {
+        $class = '';
+        $additionalClass = '';
+        $ro = '';
+        $disabled = '';
+        $statusDisabled = '';
+        $out = '';
+        $outArray;
+
+        switch ($critical) {
+            case 2: // OT only
+                $class = 'critical ot';
+                if ($this->role != 'OT') {
+                    $ro = ' readonly';
+                    $disabled = ' disabled';
+                }
+                break;
+            case 1: // requires OTP
+                $class = 'critical';
+                $ro = '';
+                break;
+            default:
+                break;
+        }
+        
+        
+        
+        if ($nonempty_level < 10) {
+            if ($class != '') {
+                $class = ' class="nonempty_level_'.$nonempty_level.' '.$class.'"';
+            } else {
+                $class = ' class="nonempty_level_'.$nonempty_level.'"';
+            }
+        } else {
+            if ($class != '') {
+                $class = ' class="'.$class.'"';
+            }
+        }
+
+        if ($hidden == 1) {
+            $additionalClass = ' class="additional"';
+        }
+        $inp_size = 100;
+        $h_style = 'font-weight: bold;';
+        if ($sub_id !== '') {
+            if ($sub_id !== -1)
+                $id = $id . '_' . $sub_id;
+            $h_style = 'font-style: italic;';
+            $inp_size = 80;
+        }
+        $out .= '<tr' . $additionalClass . '><td style="vertical-align: top; ' . $h_style . '">' . $label . '</td><td>';
+        if ($type == 'I') {
+            $out .= '<input type="text" size=' . $inp_size . $class . $ro . ' name="' . $id . '" id="' . $id . '">';
+        }
+        if ($type == 'R') {
+            $out .= '<input type="radio" name="' . $id . '"' . $class . ' id="' . $id . '">';
+        }
+        if ($type == 'C') {
+            $out .= '<input type="checkbox" name="' . $id . '"' . $class . $disabled . ' id="' . $id . '">';
+        }
+        if ($type == 'AREA') {
+            $out .= '<select name="' . $id . '"' . $class . $ro . ' id="' . $id . '">
+     <option value="0">select one</option><option value="1">remove</option>';
+            foreach (Countries::names as $code => $name) {
+                $out .= '<option value="' . strtolower($code) . '">' . $name . '</option>';
+            }
+            $out .= '</select>';
+        }
+        if ($type == 'ST') {
+            $out .= '<select name="' . $id . '"' . $class . $statusDisabled . ' id="' . $id . '">
+<option value="0" id="status_none" class="critical" selected>none</option>
+<option value="1" class="critical">candidate</option>
+<option value="4" class="critical">voting only</option>
+<option value="5" class="critical, ot">suspended</option>
+<option value="6" class="critical, ot">paricipant</option>
+</select>';
+        }
+        if ($type == "SIRTFI") {
+            $out .= '<table>';
+            $out .= $this->row('E-mail', 'I', $id . '_mail_value', -1, 1);
+            $out .= $this->row('preferred', 'C', $id . '_mail_preferred', -1, 1);
+            $out .= $this->row('WWW', 'I', $id . '_url_value', -1, 1);
+            $out .= $this->row('preferred', 'C', $id . '_url_preferred', -1, 1);
+            $out .= $this->row('Tel', 'I', $id . '_phone_value', -1, 1);
+            $out .= $this->row('preferred', 'C', $id . '_phone_preferred', -1, 1);
+            $out .= '</table>';
+        }
+        if ($type == "PERS") {
+            $out .= '<table>';
+            $out .= $this->row('Given name', 'I', $id . '_0', 0);
+            $out .= $this->row('Last name', 'I', $id . '_0', 1);
+            $out .= $this->row('E-mail', 'I', $id . '_0', 2);
+            $out .= $this->row('Public e-mail', 'C', $id . '_0', 3);
+            $out .= $this->row('Given name', 'I', $id . '_1', 0);
+            $out .= $this->row('Last name', 'I', $id . '_1', 1);
+            $out .= $this->row('E-mail', 'I', $id . '_1', 2);
+            $out .= $this->row('Public e-mail', 'C', $id . '_1', 3);
+            $out .= '</table>';
+        }
+        if ($type == "EDECL") {
+            $out .= '<table>';
+            $out .= $this->row('Policy version', 'I', 'policy_version', -1, 0, 0, 1);
+            $out .= $this->row('Policy date', 'I', 'policy_date', -1, 0, 0, 1);
+            $out .= $this->row('Signed policy', 'SPOL', 'signed_policy', -1);
+//            $out .= $this->row('Hardcopy', 'I', 'policy_hardcopy', -1);
+            $out .= $this->row('Membership date', 'I', 'membership_date', -1, 0, 0, 4);
+            $out .= $this->row('Production date', 'I', 'production_date', -1, 2, 0, 6);
+            $out .= '</table>';
+        }
+        if ($type == "POL") {
+            $out .= '<div><input type="text" class="nonempty_level_4" size=' . $inp_size . ' name="' . $id . '_0" id="' . $id . '_0"></div>';
+            $out .= '<div class="additional ' . $id . '"><input type="text" size=' . $inp_size . ' name="' . $id . '_1" id="' . $id . '_1"></div>';
+            $out .= '<div class="additional ' . $id . '"><input type="text" size=' . $inp_size . ' name="' . $id . '_2" id="' . $id . '_2"></div>';
+            $out .= '<div class="additional ' . $id . '"><input type="text" size=' . $inp_size . ' name="' . $id . '_3" id="' . $id . '_3"></div>';
+            $out .= '<div class="additional ' . $id . '"><input type="text" size=' . $inp_size . ' name="' . $id . '_4" id="' . $id . '_4"></div>';
+            $out .= '<div class="additional ' . $id . '"><input type="text" size=' . $inp_size . ' name="' . $id . '_5" id="' . $id . '_5"></div>';
+            $out .= '<button id="' . $id . '" class="next_button ui-button ui-corner-all ui-widget">Add another</button>';
+        }
+
+        if ($type == "AREAS") {
+            $out .= '<table>';
+            $out .= $this->row('', 'AREA', $id, 0);
+            $out .= $this->row('', 'AREA', $id, 1, 0, 1);
+            $out .= $this->row('', 'AREA', $id, 2, 0, 1);
+            $out .= $this->row('', 'AREA', $id, 3, 0, 1);
+            $out .= $this->row('', 'AREA', $id, 4, 0, 1);
+            $out .= '<tr><td></td><td><button id="next_country_button" class="ui-button ui-corner-all ui-widget" id="' . $id . '">Add another</button></td></tr>';
+            $out .= '</table>';
+        }
+
+        if ($type == "CERT") {
+
+            $out .= '<div class="additional ' . $id . '"><div id="' . $id . '_0"></div></div>';
+            $out .= '<div class="additional ' . $id . '"><div id="' . $id . '_1"></div></div>';
+            $out .= '<div class="additional ' . $id . '"><div id="' . $id . '_2"></div></div>';
+            $out .= '<div class="additional ' . $id . '"><div id="' . $id . '_3"></div></div>';
+        }
+
+        if ($type == "SPOL") {
+            $out .= '<div class="additional ' . $id . '"><div id="' . $id . '_0"></div></div>';
+            $out .= '<div class="additional ' . $id . '"><div id="' . $id . '_1"></div></div>';
+            $out .= '<div class="additional ' . $id . '"><div id="' . $id . '_2"></div></div>';
+            $out .= '<div class="additional ' . $id . '"><div id="' . $id . '_3"></div></div>';
+            $out .= '<div style="display:none" id="pol_div"><input type="file" name="pol_file" id="pol_file"></div>';
+            $out .= '<div style="display:none" id="declaration_div"><input type="text" size="80" id="declaration_file" readonly></div>';
+        }
+
+        $out .= '</td></tr>' . "\n";
+        return $out;
+    }
+
+    private $role = '';
+    
+}
diff --git a/lib/eduTeams.php b/lib/eduTeams.php
new file mode 100644
index 0000000000000000000000000000000000000000..6deee1d22dd4c6edf977bb1195535a4711a56872
--- /dev/null
+++ b/lib/eduTeams.php
@@ -0,0 +1,64 @@
+<?php
+
+use Exception;
+
+include_once SSP_PATH_TO_AUTOLOADER;
+
+class eduTeams {
+
+    /**
+     * initialise ourselves, and simpleSAMLphp
+     */
+   public function __construct() {
+       $this->auth = new \SimpleSAML\Auth\Simple('default-sp');
+       $this->session = \SimpleSAML\Session::getSessionFromRequest();
+   }
+    /**
+     * finds out whether the user is already authenticated. Does not trigger an authentication if not.
+     *
+     * @return boolean auth state
+     */
+    public function isAuthenticated() {
+        $status = $this->auth->isAuthenticated();
+        $this->session->cleanup();
+        return $status;
+    }
+
+    /**
+     * authenticates a user.
+     * 
+     * @return void
+     * @throws Exception
+     */
+    
+    public function authenticate() {
+        $this->auth->requireAuth();
+        $this->session->cleanup();
+        Utils::debug(3,"authenticate via eduTEAMS done\n");
+    }
+    
+    public function getUser() {
+        $attributes = $this->auth->getAttributes();
+        $this->session->cleanup();
+        if (!isset($attributes['urn:oid:1.3.6.1.4.1.5923.1.1.1.13'][0])) {
+            $failtext = "FATAL ERROR: we did not receive a unique user identifier from the authentication source!";
+            echo $failtext;
+            throw new Exception($failtext);
+        }
+        $user = $attributes['urn:oid:1.3.6.1.4.1.5923.1.1.1.13'][0];
+        $email = $attributes['urn:oid:0.9.2342.19200300.100.1.3'][0];
+        $groups = $attributes['urn:oid:1.3.6.1.4.1.5923.1.1.1.7'];
+        $G = [];
+        foreach ( $groups as $group) {
+            $m = [];
+            if (preg_match('/^urn:geant:eduteams.org:service:eduteams:group:eduGAIN:(.*)#eduteams\.org$/', $group, $m)) {
+                $G[] = $m[1];
+            }
+        }
+        return(['user' => $user, 'groups' => $G, 'email' => $email]);
+    }
+    public $session;
+    public $auth;
+}
+
+
diff --git a/lib/localAuth.php b/lib/localAuth.php
new file mode 100644
index 0000000000000000000000000000000000000000..8bd6d86c500622149fa587ccb8e9169c6bab359a
--- /dev/null
+++ b/lib/localAuth.php
@@ -0,0 +1,53 @@
+<?php
+session_start();
+
+use Exception;
+
+class localAuth {
+    
+    public function isAuthenticated() {
+        if (isset($_SESSION['auth_ok']) && $_SESSION['auth_ok'] == 'authenticated') {
+            return true;
+        } else {
+            return false;
+        }
+        
+    }
+    
+    public function authenticate() {
+        Utils::debug(3,"Authenticating locally\n");
+        require_once(eduGAIN_config . "admin.php");
+        if (isset($_POST['user'])) {
+            if (array_key_exists($_POST['user'], $admins)) {
+                Utils::debug(3,"local AUTH testing\n");
+                if (isset($_POST['passwd']) && password_verify($_POST['passwd'], $admins[$_POST['user']]['hash'])) {
+                                Utils::debug(3,"local AUTH testing1\n");
+
+                    $_SESSION['auth_ok'] = 'authenticated';
+                    $user = [
+                        'user' => $admins[$_POST['useśr']]['mail'],
+                        'groups' => $admins[$_POST['user']]['groups'],
+                        'email' => $admins[$_POST['user']]['mail']
+                    ];
+                    $_SESSION['user'] = $user;
+                    Utils::debug(3,"local AUTH successful\n");
+                    header("Location: ".ROOT_URL."/authenticate");
+                    exit;
+                }
+            }
+            Utils::debug(3,"local AUTH failed\n");
+            header("Location: ".ROOT_URL."/authenticate?retry");
+            exit;
+        }
+    }
+    
+    public function getUser() {
+        require_once(eduGAIN_config . "admin.php");
+        $user = $_SESSION['user'];
+        $email = $admins[$user['user']]['mail'];
+        return(['user' => $user, 'groups' => $user['groups'], 'email' => $email]);
+    }
+            
+}
+
+ 
diff --git a/lib/validatorClass.php b/lib/validatorClass.php
index 842973a41ddb2f151aceac261700026612cca834..bd5abb3caf229f14a9a844ff64e2ca53237e3507 100644
--- a/lib/validatorClass.php
+++ b/lib/validatorClass.php
@@ -92,6 +92,7 @@ class Validator {
                          }
                          fputs($ff, "-----END CERTIFICATE-----\n");
                          fclose($ff);
+                         $this->edugain->FEDS[$fed_id]['certificate'][$n]['certfile'] = $certfilename;
                      }
                    }
                    $ff = fopen($this->params['filename'].'-certinfo.json', 'w');
@@ -279,6 +280,7 @@ class Validator {
     public function checkmetadata() {
         $params = $this->params;
         $schres = 0;
+        $certidx = -1;
         if ( ($params['validate'] || $params['entities']) && ($params['url'] != "") ) {
         $ret = $this->get_data();
         if ($ret[0] == 304) {
@@ -325,8 +327,11 @@ class Validator {
                 if ($params['edugain'] || $params['gobetween'])  $fromdb = 1;
                 else $fromdb = 0;
                 system($this->validatordir."/aggregator.py ".$params['filename']." $schres $fromdb >".$params['filename']."-aggregator.log 2>&1",$aggres);
+                $certidx = trim(file_get_contents($params['filename']."-aggregator.log"));
+                if ($certidx == '') {
+                  $certidx = 0;
+                }
                 system($this->validatordir."/mda.sh --verbose ".$params['filename'].".xml main > ".$params['filename']."-mda.log 2>&1", $res);
-                $certidx = 0;
                 $searchfor = "Unable to validate signature";
                 $matches = array();
                 $handle = @fopen($params['filename']."-mda.log", "r");
@@ -343,7 +348,10 @@ class Validator {
                 if (count($matches)) {
                   system($this->validatordir."/aggregator.py ".$params['filename']." $schres $fromdb 1 >".$params['filename']."-aggregator2.log 2>&1",$aggres);
                   system($this->validatordir."/mda.sh --verbose ".$params['filename']."_1.xml main > ".$params['filename']."-mda.log 2>&1", $res);
-                  $certidx = 1;
+                  $certidx = trim(file_get_contents($params['filename']."-aggregator2.log"));
+                  if ($certidx == '') {
+                    $certidx = 1;
+                  }
                 }
                 system($this->validatordir."/pyff.sh ".$params['filename'].'>'.$params['filename']."-pyff.log 2>&1", $pyffres);
             } else $res = $schres;
diff --git a/page-logic/authenticate.php b/page-logic/authenticate.php
new file mode 100644
index 0000000000000000000000000000000000000000..b7c64bf802b117e5c2f2d54a8ba620a556645cf5
--- /dev/null
+++ b/page-logic/authenticate.php
@@ -0,0 +1,94 @@
+<?php
+/*
+ * This takes care of the first step of authentication - either federated or via local username/passwrd
+ * when authentication is done the user is redirected to "manage"
+ */
+session_start();
+
+
+function runAuth($auth_type) {
+    if ($auth_type == 'none') {
+        return;
+    }
+    if ($auth_type == 'local') {
+        $auth = new \localAuth();
+    } else {
+        $auth = new \eduTeams();
+    }
+    if (!$auth->isAuthenticated()) {
+        Utils::debug(3,"AUTH required\n");
+        $auth->authenticate();
+        Utils::debug(3,"AUTH successful\n");
+        exit;
+    }
+    $user = $auth->getUser();
+    if (in_array(EDUGAIN_OT, $user['groups'])) {
+        $admin = true;
+    } elseif (in_array(EDUGAIN_SEC, $user['groups'])) {
+        $secretariat = true;
+    } else {
+        print("Sorry, no proper authorization");
+        Utils::debug(2, $user, "Unauthorised access:", "\n");
+        exit;
+    }
+    $_SESSION['auth_ok'] = 'authenticated';
+    $_SESSION['user'] = $user;
+    header("Location: ".ROOT_URL."/manage");
+    exit;
+    
+}
+
+$authType = 'none';
+if(isset($_POST['user'])) {
+$authType = 'local';
+//exit;
+} elseif(isset($_GET['eduteams'])) {
+    $authType = 'eduteams';
+}
+
+
+// if already authenticated send to "manage"
+if (isset($_SESSION) && isset($_SESSION['auth_ok']) && isset($_SESSION['user']) && $_SESSION['auth_ok'] == 'authenticated') {
+    Utils::debug(3,"authenticate: AUTH not needed\n");
+    header("Location: ".ROOT_URL."/manage");
+    exit;
+}
+
+runAuth($authType);
+
+$admin = 0;
+$secretariat = 0;
+
+// first test if is SSP authenticated and if not, authenticate and return back to the start of this script
+
+
+eduGAIN_manage_gui::pageGraphics();
+    ?>
+<div style="margin-left: 2em; margin-top:4ex">
+    <h2>
+<?php
+if (isset($_GET['retry'])) {
+    print ('Incorrect credentials entered, please retry');
+} else {
+    print('Authentication required for accessing the federations management inteface');
+}
+?>
+</h2>
+<p>
+<div style="width: 20em;padding: 1ex; font-size:30px; font-weight: bold; background:#f1893b; text-align: center">
+    <a href="<?php echo ROOT_URL ?>/authenticate?eduteams">Login via eduGAIN</a>
+</div>
+<?php if (LOCAL_AUTH === true) { ?>
+<p>
+or use the form below to login with a local account<p>
+<form action="<?php echo ROOT_URL ?>/authenticate" method = POST>
+<table>
+<tr><td>Username:</td><td><input name="user" type="text"></td></tr>
+<tr><td>Password:</td><td><input name="passwd" type="password"></td></tr>
+<tr><td>&nbsp;</td><td><input type="submit" value="Submit"></td></tr>
+</table>
+</form>
+<?php } ?>
+</div>
+</div>
+
diff --git a/page-logic/manage.php b/page-logic/manage.php
new file mode 100644
index 0000000000000000000000000000000000000000..1526c2cb5797016b8f0021d25c3a00c63ad81006
--- /dev/null
+++ b/page-logic/manage.php
@@ -0,0 +1,242 @@
+<?php
+    use OTPHP\TOTP;
+
+//Set the cookie lifetime of the session
+ini_set( "session.cookie_lifetime", 3600 );
+session_start();
+Utils::debug(3,"Starting MANAGE\n");
+
+
+class Management {
+    public function __construct() {
+        $this->otpCode = filter_var($_POST['second_factor'], FILTER_SANITIZE_NUMBER_INT);
+        require(eduGAIN_config . "admin.php");
+        if (isset($_SESSION['user'])) {
+            $this->user = $_SESSION['user'];
+            $this->groups = $this->user['groups'];
+        }
+        $this->setUserRole();
+        if (isset($_POST['critical_save']) && $_POST['critical_save'] == 1) {
+            $this->criticalSave = 1;
+        }
+    }
+ 
+    public function setMFA() {
+        Utils::debug(3,$_SESSION['mfa_authenticated'], "MFA auth: ", "\n");
+        if (!isset($_SESSION['mfa_authenticated']) || $_SESSION['mfa_authenticated'] != 1) {
+            if ($this->verifyOtp($this->user, $this->otpCode) == 3) {
+                $this->authOtp();
+            }
+        }
+    }
+    
+    public function verifyMFASetup() {
+        
+        if (!isset($_SESSION['mfa_authenticated']) || $_SESSION['mfa_authenticated'] != 1) {
+            $this->verifyOtp($this->user);
+        }
+    }
+    
+    public function setPostVar() {
+        if (LOCAL_AUTH == true && OTP_SERVER == null && $this->admin == 1) {
+            $this->superadmin = 1;
+            $this->post_var = $_POST;
+            return;
+        }
+        if ($this->otpCode != '') {
+            $ooR = $this->verifyOtp($this->user, $this->otpCode);
+            if ($ooR === 1) {
+                if ($this->admin == 1) {
+                    $this->superadmin = 1;
+                }
+                $this->post_var = $_SESSION['last_post'];
+                unset($_SESSION['last_post']);
+            } else {
+                print "Verification failed";
+                unset($_SESSION['last_post']);
+                exit;
+            }
+        } else {
+            $this->post_var = $_POST;
+        }
+    }    
+    
+    public function checkCriticalSave() {
+        if ($this->criticalSave) {
+            if ($this->admin == 0 && $this->secretariat == 0) {
+                eduGAIN_manage_gui::pageGraphics();
+                print("Not authorized to perform this operation");
+                Utils::debug(4, "Not authorized for critical save\n");
+                exit;
+            }
+            $this->authOtp();
+        }
+    }
+    
+    private function authOtp() {
+        if (is_null(OTP_SERVER)) {
+            return(1);
+        }
+        $_SESSION['last_post'] = $_POST;
+        header("Location: ".ROOT_URL."/otp_code");
+        exit;
+    }
+    
+    private function verifyOtp($user, $otpCode = '') {
+        if (is_null(OTP_SERVER)) {
+            return(1);
+        }
+        if ($this->otp_succeeded === 1 && $otpCode !== '') {
+            return(1);
+        }
+        $this->otp_succeeded = 0;
+        $url = OTP_SERVER.'?user='.$user['email'].'&otp='.$otpCode;
+        Utils::debug(3,$url, "OTP server link: ", "\n");
+        $res = json_decode(file_get_contents($url));
+        Utils::debug(3,$res, "Result form OTP: ", "\n");
+        if (!isset($res)) {
+            print("Sorry there was a problem with OTP authentication");
+            exit;
+        }
+        switch ($res) {
+            case -1: // user not found, generate code
+                header("Location: ".ROOT_URL."/otp_start");
+                exit;
+            case 0: // user found but no OTP match
+                $_SESSION['otp_set'] = 1;
+                if ($otpCode !== '') {
+                    eduGAIN_manage_gui::pageGraphics();
+                    print '<div style="margin-left: 2em; margin-top:4ex">';
+                    print "<h1>Verification failed</h1><p>";
+                    print("<p><a href=''><b>Try again</b>");
+                            header("Location: ".ROOT_URL."/otp_code?repeat");
+                    exit;
+                }
+                return(0);
+            case 1: // OTP match
+                $_SESSION['otp_set'] = 1;
+                Utils::debug(3, "setting session mfa_authenticated\n");
+                $_SESSION['mfa_authenticated'] = 1;
+                $this->otp_succeeded = 1;
+                return(1);
+            case 2: // match but not verified
+                $_SESSION['unverified'] = 1;
+                $this->authOtp();
+                return(2);
+            case 3:
+                $_SESSION['otp_set'] = 1;
+                return(3);
+            case 4:
+                eduGAIN_manage_gui::pageGraphics();
+                print '<div style="margin-left: 2em; margin-top:4ex">';
+                print "<h1>This code has already been used. Please wait for the new code and </h1><p>";
+                unset($_SESSION['last_post']);
+                print("<p><a href=''><b>try again</b>");
+                exit;
+            default:
+                exit;
+        }
+
+    } 
+    
+    private function setUserRole () {
+        if (in_array(EDUGAIN_OT, $this->groups)) {
+            $this->admin = true;
+            $this->role = "OT";
+        } elseif (in_array(EDUGAIN_SEC, $this->groups)) {
+            $this->secretariat = true;
+            $this->role = "Secretariat";
+        } else {
+            print("Sorry, no proper authorization");
+            exit;
+        }
+    }
+        
+    public $user = [];
+    public $admin = 0;
+    public $secretariat = 0;
+    public $superadmin = 0;
+    public $otpRequired = 0;
+    public $role = '';
+    public $groups = [];
+    public $criticalSave = 0;
+    public $otpCode = '';
+    public $post_var = '';
+    public $otp_succeeded = 0;
+    public $authType = 'eduteams';
+    
+}
+// if not authenticated send to "authenticate"
+Utils::debug(3,"Starting AUTH\n");
+if (!isset($_SESSION['auth_ok']) || $_SESSION['auth_ok'] != 'authenticated') {
+    Utils::debug(3,"AUTH required\n");
+    header("Location: ".ROOT_URL."/authenticate");
+    exit;
+}
+Utils::debug(3,$_SESSION['auth_ok'], "Authentication successfull: ", "\n");
+$mgm = new Management();
+Utils::debug(3,$mgm->role, "User role:", "\n");
+
+
+// the user needs to be MFA authenticated 
+if ($mgm->role == 'OT') {
+    $mgm->setMFA();
+} else {
+    $mgm->verifyMFASetup();
+}
+$mgm->checkCriticalSave();
+$mgm->setPostVar();
+
+if ($mgm->superadmin)
+    require(eduGAIN_config . "database_policy.php");
+else
+    require(eduGAIN_config . "database_rw.php");
+
+require("../lib/eduGAIN.php");
+
+$page_title = "eduGAIN database management tool";
+$edugain = new eduGAIN_manage(-1);
+if (!empty($mgm->post_var['code'])) {
+    $edugain->update_EduGAINTables($mgm->post_var, $mgm->superadmin);
+    $edugain = new eduGAIN_manage(-1);
+}
+
+$edugainGUI = new eduGAIN_manage_gui($mgm->role);
+$federations = [];
+$federationCodes = [];
+foreach ($edugain->FEDS as $fedCode => $federation) {
+    $processedFederation = [];
+    $processedFederation['fed'] = $federation;
+    $processedFederation['searchString'] = $federation['searchString'];
+    $federations[] = $processedFederation;
+    $federationCodes[] = $federation['code'];
+}
+$data = [];
+
+$rows = [];
+
+$rows[] = $edugainGUI->row('Federation code', 'I', 'code', '');
+$rows[] = $edugainGUI->row('Areas', 'AREAS', 'country_code');
+$rows[] = $edugainGUI->row('Name', 'I', 'name', '', 0, 0, 0);
+$rows[] = $edugainGUI->row('In Europe', 'C', 'europe');
+$rows[] = $edugainGUI->row('Status', 'ST', 'status', '', 1);
+$rows[] = $edugainGUI->row('eduGAIN declaration', 'EDECL', 'edecl');
+$rows[] = $edugainGUI->row('Federation URL', 'I', 'url', '', 0, 0, 4);
+$rows[] = $edugainGUI->row('Registration practice', 'POL', 'registration');
+$rows[] = $edugainGUI->row('Contact email', 'I', 'contact_email', '', 0, 0, 4);
+$rows[] = $edugainGUI->row('TSG delegate', 'PERS', 'tsg_delegate');
+$rows[] = $edugainGUI->row('TSG deputy', 'PERS', 'tsg_deputy');
+$rows[] = $edugainGUI->row('Security contact', 'SIRTFI', 'security_contact');
+$rows[] = $edugainGUI->row('Policy', 'POL', 'policy');
+$rows[] = $edugainGUI->row('Policy in English', 'C', 'policy_english');
+$rows[] = $edugainGUI->row('Metadata URL', 'I', 'metadata_url', '', 2,0,6);
+$rows[] = $edugainGUI->row('Registration Authority', 'I', 'reg_auth', '', 2,0,6);
+$rows[] = $edugainGUI->row('Signing certificate', 'CERT', 'certificate');
+
+$data['federations'] = $federations;
+$data['rows'] = $rows;
+$data['federation_codes'] = $federationCodes;
+$data['role'] = $mgm->role;
+echo "<script>var admin = $mgm->admin; var role = '$mgm->role';</script>";
+
+echo $twig->render('manage.html', $data);
diff --git a/page-logic/otp_code.php b/page-logic/otp_code.php
new file mode 100644
index 0000000000000000000000000000000000000000..a0be4e89a9c4df46c9c21f3900ce53790f987880
--- /dev/null
+++ b/page-logic/otp_code.php
@@ -0,0 +1,39 @@
+<?php
+eduGAIN_manage_gui::pageGraphics();
+$repeatText = '';
+if (isset($_GET['repeat'])) {
+    $repeatText = "<b>The last code you entered failed. </b><p>";
+}
+
+    print '<div style="margin-left: 2em; margin-top:4ex">';
+    print "<h1>Additional verification required</h1><p>";
+    print '<form id="otpform" action="'.ROOT_URL.'/manage" method = POST>';
+    print $repeatText."Remember that the code can only be used once, if you have already used it you need to wait for the new one to be generated by your device."
+        . "<p>Please enter your validation code: "
+        . '<input type="text" size=6 name="second_factor" id="second_factor">&nbsp;&nbsp;'
+        . '<button id="otpsubmit" class="ui-button ui-corner-all ui-widget">Submit</button>'
+        . ' '
+        . '<button id="otpcancel" class="ui-button ui-corner-all ui-widget">Cancel</button>'
+            . '<input type="hidden" name ="otpstatus" id="otpstatus" value="1"/ >'
+            ;
+    print '</form></div>';
+    print '<script>
+    window.onload = function() {
+       document.getElementById("second_factor").focus();
+    };
+    $("#otpsubmit").on("click", function(event) {
+        event.preventDefault();
+        if(!$("#second_factor").val().match(/\d\d\d\d\d\d/)) {
+           alert("The code needs to be 6 digits");
+           return false;
+        }
+        $("#otpform").submit();
+    });
+    $("#otpcancel").on("click", function(event) {
+        event.preventDefault();
+        $("#second_factor").val("");
+        $("#otpstatus").val("0");
+        $("#otpform").submit();
+    });
+    </script>';
+
diff --git a/page-logic/otp_start.php b/page-logic/otp_start.php
new file mode 100644
index 0000000000000000000000000000000000000000..427f12a53010f368958b671fec4f4a8d585264c5
--- /dev/null
+++ b/page-logic/otp_start.php
@@ -0,0 +1,93 @@
+<?php
+eduGAIN_manage_gui::pageGraphics();
+session_start();
+header("Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate");
+if (!isset($_SESSION['auth_ok']) || $_SESSION['auth_ok'] != 'authenticated') {
+    header("Location: ".ROOT_URL."/authenticate");
+    exit;
+}
+$user = $_SESSION['user']['email'];
+?>
+<script src="third-party/jquery.js"></script>
+
+<div style="margin-left: 2em; margin-top:4ex">
+<h1>Registration for access to eduGAIN database management</h1>
+
+<?php
+    
+require_once('../vendor/autoload.php');
+require_once('../lib/config.php');
+#require_once('../lib/autoload.php');
+$otpCode = filter_var($_POST['second_factor'], FILTER_SANITIZE_NUMBER_INT);
+print("OTP:$otpCode");
+
+use OTPHP\TOTP;
+
+$db_user = 'otp_init';
+$db_password = '1qaz2wsx';
+
+$mysqli = new mysqli(DB_HOST, $db_user, $db_password, OTP_DATABASE);
+if ($mysqli->connect_error) {
+    die("Not connected");
+}
+
+$mysqli->set_charset('utf8');
+$mysqli->query("SET time_zone='+00:00'");
+// first we generate a new secret and try to save it to the DB. if this fails then we just
+// report that the user is already registered
+$otp = TOTP::create();
+$secret = $otp->getSecret();
+$_SESSION['otp_set'] = 1;
+$otp->setLabel($user);
+$otp->setIssuer('eduGAIN management');
+$uri = $otp->getProvisioningUri();
+$query = "INSERT INTO otp VALUES('$user', '$secret', 0, 0)";
+$result = $mysqli->query($query);
+if (!$result) {
+    print ("User already registered.<p>");
+    print ("The secret access code may be regenerated for you by the eduGAIN Operations Team.");
+    print ("</div>");
+    exit;
+}
+
+?>
+
+One-time codes will be required for certain database update operations.
+<p>
+
+Please scan this code into an application like Google Auth<p>
+
+<img src="<?php echo (new \chillerlan\QRCode\QRCode)->render($uri) ?>" alt="QR Code" />
+<p>or copy the code below into your application;<p>
+<?php echo $secret; ?>
+<p>Please save this info in a secure place (not your mailbox).
+    This code may be regenerated for you by the eduGAIN Operations Team.
+</p>
+<p>After you have done it, the aplication should generate the validation code - please paste it here:
+
+<form action="<?php echo ROOT_URL ?>/otp_start" method = POST>
+<input type="text" size=6 name="second_factor" id="second_factor">&nbsp;&nbsp;<button id="submit_otp" class="ui-button ui-corner-all ui-widget">Submit</button><p>
+</form>
+<p style="font-size:20px">When done <a href="<?php echo ROOT_URL."/manage"; ?>">CLICK HERE</a> to go to the management page.
+</div>
+<script>
+    window.onload = function() {
+       document.getElementById("second_factor").focus();
+    }
+    
+    jQuery(document).ready(function () {
+        $("#submit_otp").on("click", function(event) {
+            event.preventDefault();
+            $.post('<?php echo ROOT_URL."/verify-otp.php"; ?>', {otp: $('#second_factor').val()},
+            function (data) {
+                if (data == 1 ) {
+                    alert("Your registration was successful.");
+                    window.location.replace("<?php echo ROOT_URL."/manage"; ?>");
+                } else {
+                    alert("Sorry this code was invalid, please try again");
+                }
+            }, 
+            'json');
+        });
+    });
+    </script>
diff --git a/page-logic/update_maps.php b/page-logic/update_maps.php
index 8389666f6945349e516f0a2d511f171458e29d1c..1a1311595830cc9d3fb1eb8ac3e25b134e9f84b2 100644
--- a/page-logic/update_maps.php
+++ b/page-logic/update_maps.php
@@ -13,9 +13,12 @@ if (isset($_POST['logout'])) {
     unset($_POST);
     unset($_SESSION);
 }
-require("../lib/config.php");
+require_once("../lib/config.php");
 $mapsDir = TECHNICAL_HOME."/web/maps/";
 require(eduGAIN_config . "admin.php");
+// it might appear that we are opening a security hole publishing the password
+// in the code, but it can only be used during the system installation therefore
+// poses no threat
 if (isset($_POST['user']) && isset($_POST['passwd'])) {
     if ($_POST['user'] === 'init' && $_POST['passwd'] === 'edu9ain') {
         $mapsDirContent = scandir($mapsDir);
diff --git a/scripts/CoCointerface.php b/scripts/CoCointerface.php
new file mode 100644
index 0000000000000000000000000000000000000000..0625e0417609c27b3e006ed3d8baabe8ed67e448
--- /dev/null
+++ b/scripts/CoCointerface.php
@@ -0,0 +1,30 @@
+<?php
+require_once(dirname(__FILE__)."/../lib/config.php");
+require(eduGAIN_config."coco_update.php");
+require_once(TECHNICAL_HOME.'/lib/autoload.php');
+
+
+class CoCointerface extends eduGAIN_core{
+   public function load_coco() {
+      $j = file_get_contents(COCO_URL.'/json.php?all_sps=true&attributes=id_status;entityID;status');
+      $e = json_decode($j);
+      foreach ($e as $r) 
+         if($r->id_status != NULL)
+            $this->CoCo[$r->entityID] = array('id'=>$r->id,'code'=>$r->id_status,'msg'=>$r->status);
+   }
+   public $CoCo;
+}
+
+$e = new CoCointerface();
+
+$e->load_coco();
+$q = "DELETE FROM coco_stat";
+$e->databaseQuery($q);
+
+foreach ($e->CoCo as $e_id => $s) {
+   $q = "INSERT into coco_stat (entityid,status,msg,coco_id) VALUES ('$e_id'," . $s['code'] . ", \"" . $s['msg'] . "\", " . $s['id'] . ")";
+   $e->databaseQuery($q);
+}
+
+?>
+
diff --git a/scripts/mk_map.php b/scripts/mk_map.php
index 7f0eb508635b660c9d57d84ab2bc70fcce9e68d2..6822cfed280e8da68aeba59cfcaadd48b7319908 100644
--- a/scripts/mk_map.php
+++ b/scripts/mk_map.php
@@ -4,7 +4,7 @@
  * The same can be acheved  wia web with update_maps
  */
 require_once("../lib/config.php");
-require_once("../lib/eduGAIN_map.php");
+require_once('../lib/autoload.php');
 eduGAIN_map::make_maps();
 eduGAIN_map::make_maps(1);
 ?>
diff --git a/scripts/update_coco.sh b/scripts/update_coco.sh
new file mode 100644
index 0000000000000000000000000000000000000000..e65d1f78e6bcdb0fd37d6632975c1e25e2ebe69a
--- /dev/null
+++ b/scripts/update_coco.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+spath="$(readlink -n -f "$0")"
+cd "$(dirname "$spath")"
+/usr/bin/php CoCointerface.php 1> /dev/null 2>>/var/log/coco_error.log
diff --git a/templates/js/entities.js b/templates/js/entities.js
index cfbc4c340dfa1503a01fec451c692691700a3ffd..992512027360f621f8a578a8b12491b144fa7d40 100644
--- a/templates/js/entities.js
+++ b/templates/js/entities.js
@@ -15,11 +15,9 @@ $(function(){
          autoOpen: false,
          width: 800,
          close: function(event, ui){
-           $(".entity_links").css('color','#336699');
-           $(".entity_links").css('font-weight','normal');
+           $(".entity_links").css('color','#336699').css('background-color', 'inherit');
            $("#entity_details_content").html("");
          },
-         position: { my: "left top", at: "left+50 top+50", of : window }
      });
 });
 
@@ -241,23 +239,24 @@ function show_details(id,next,prev) {
    $("#show_prev").attr('href','javascript:show_next('+prev+')');
    $("#show_next").attr('href','javascript:show_next('+next+')');
    $("#loading").show();
-     $("body").addClass("busy");
+   $("body").addClass("busy");
    current_id = id;
    coco_id = 0;
-   $(".entity_links").css('color','#336699');
-   $(".entity_links").css('font-weight','normal');
-   $("#link_"+id).css('color','green');
-   $("#link_"+id).css('font-weight','bold');
+   $(".entity_links").css('color','#336699').css('background-color', 'inherit');
+   $("#link_"+id).css('color','white').css('background-color', '#F1893B');
+
    $.get("api.php",{action: 'show_entity_details', row_id: id, format: 'html', opt: 'internal'}, function(data) {
      $("#loading").hide();
      $("body").removeClass("busy");
      $("#link_"+id).removeClass("busy");
      $("#entity_details").dialog("open");
+     $("#entity_details").parent().css("top", $(window).scrollTop()+30).css("left", "30px");
      $("#entity_details_content").html(data);
      if(coco_id != 0)
         $("#show_coco").show();
      else
         $("#show_coco").hide();
+
      if(eccs_status != 0)
         $("#show_eccs").show();
      else
diff --git a/templates/manage.html b/templates/manage.html
new file mode 100644
index 0000000000000000000000000000000000000000..fddc1a8a9abc3358862774a51e98f77c5fca7b11
--- /dev/null
+++ b/templates/manage.html
@@ -0,0 +1,296 @@
+
+
+
+{% extends 'common/master.html' %}
+
+{% block title %}eduGAIN mangement inteface{% endblock title %}
+{% block additional_head %}
+<link rel="stylesheet" type="text/css" href="css/manage.css">
+{% endblock %}
+
+{% block main_body %}
+{% block javasctipts %}
+<script>
+    var code;
+    var declaration_version;
+    var flags = Object;
+    var changedFields = new Array();
+    var _participants = {{ federations|json_encode()|raw() }};
+    
+    var _participant_codes = JSON.parse('{{ federation_codes|json_encode|e('js') }}');
+
+    function quickSearch(input) {
+            return _participants.filter(function(federation){
+                    return federation.searchString.indexOf(input) !== -1;
+            });
+    }
+    
+    function field(data, id, sub_id1, sub_id2) {
+        var obj;
+        if (sub_id2 != undefined) {
+            if (data[id] == undefined || data[id][sub_id1] == undefined || data[id][sub_id1][sub_id2] == undefined)
+                obj = null;
+            else
+                obj = data[id][sub_id1][sub_id2];
+            id = id + '_' + sub_id1 + '_' + sub_id2;
+        } else if (sub_id1 != undefined) {
+            if (data[id] == undefined || data[id][sub_id1] == undefined)
+                obj = null;
+            else
+                obj = data[id][sub_id1];
+            id = id + '_' + sub_id1;
+        } else {
+            if (data[id] == undefined)
+                obj = null;
+            else
+                obj = data[id];
+        }
+        if ($("#" + id).is('div')) {
+            $("#" + id).html(obj);
+            $("#" + id).parent().show();
+        }
+        if ($("#" + id).prop('type') == 'text') {
+            $("#" + id).val(obj);
+            $("#" + id).parent().show();
+        }
+        if ($("#" + id).prop('type') == 'checkbox') {
+            if (obj == 1)
+                $("#" + id).prop('checked', true);
+            else
+                $("#" + id).prop('checked', false);
+        }
+        if ($("#" + id).prop('type') == 'select-one') {
+            $("#" + id + " option").each(function () {
+                if ($(this).val() == obj) {
+                    $(this).prop('selected', true);
+                    $(this).parents("tr").show();
+                } else
+                    $(this).prop('selected', false);
+            });
+        }
+
+    }
+
+    function cleanup() {
+        $("#new_fed").val(0);
+        $("#status option:selected").prop('selected', false);
+        $("#status").prop('disabled', false);
+        $("#status").removeClass('ot');
+        $(".additional").hide();
+        $(".next_button").show();
+        $("#next_country_button").show();
+        $("#federation_details input").val('');
+        $("#federation_details input:checked").prop('checked', false);
+        $("#federation_details option:selected").prop('selected', false);
+        if (admin == 1) {
+            $(".ot").prop('disabled', false);
+        } else {
+            $(".ot").prop('disabled', true);
+        }
+    }
+    
+    function discardChanges(event) {
+        event.preventDefault();
+        loadFederation();
+    }
+    
+    function testNewFed() {
+        if ($.inArray($("#code").val(), _participant_codes) >= 0) {
+            alert("Identfier "+$("#code").val()+" is already in use");
+            return(false);
+        }
+        if ($("#code").val().match(/^[A-Z]+-?[A-Z]*$/) == null) {
+            alert("The codes must consist of capital letters and possibly a single hyphen inside");
+            return(false);
+        }
+        return(true);
+    }
+    
+    function testSaveCompleteness(event) {
+        event.preventDefault();
+        missing_fields = 0;
+        errors = new Array();
+        status = $("#status  option:selected").val();
+        if ($("#code").val() == "") {
+            alert("Missing federation identifier");
+            return(false);
+        }
+        if ($("#new_fed").val() == 1) {
+            if (testNewFed() == false) {
+                return(false);
+            };
+        }
+        if (status == 6 && $("#certificate_0").text() == "") {
+            alert("You must upload the federation certificate first");
+            return(false);
+        }
+         $("input[class*='nonempty_level_']").each(function(index) {
+            x = Number($(this).attr("class").substring(15,16));
+            if (x <= status && ($(this).val() == '' || $(this).val() == '0000-00-00')) {
+                $(this).addClass("missing");
+                missing_fields = 1;
+            } else {
+                $(this).removeClass("missing");
+            }
+            });
+            
+        if(missing_fields == 1) {
+            alert("The fields marked in light-red cannot be empty");
+            return(false);
+        }
+        $("#changed_fields").val(JSON.stringify(changedFields));
+        $("#main_form").submit();  
+    }
+    
+    function newFederation(event) {
+        event.preventDefault();
+        cleanup();
+        $("#code").removeAttr("readonly");
+        if( admin == 1) {
+            $("#status").prop('disabled', false);
+            $("#status option").prop('disabled', false);
+        } else {
+            $("#status").prop('disabled', false);
+            $("#status").removeClass('critical');
+        } 
+        $("#federation_details").show();
+        $("#save").show();
+        $("#discard").show();
+        $("#new_fed").val(1);
+        handlePolicyUploadDiv();
+    }
+    
+    function handlePolicyUploadDiv() {
+        if ($("#policy_version").val() > 0) {
+            if ($("#declaration_file").val()) {
+                $("#pol_div").hide();
+                $("#declaration_div").show();
+            } else {
+                $("#pol_div").show();
+                $("#declaration_div").hide();
+            }
+        } else {
+            $("#pol_div").hide();
+            $("#declaration_div").hide();
+        }
+    }
+    
+    function loadFederation() {
+    $("#code").attr("readonly", "true");
+    $.get('{{ constant('API_URL') }}', {action: 'show_federation', fed_id: $("#feds option:selected").attr('id')},
+            function (data) {
+                cleanup();
+                $("#federation_details").show();
+                $("#save").show();
+                $("#discard").show();
+                $.each(data, function (key, element) {
+                    if (typeof element == 'object' && element != null) {
+                        $.each(element, function (k, e) {
+                            if (typeof e == 'object') {
+                                $.each(e, function (k1, e1) {
+                                    field(data, key, k, k1);
+                                });
+                            } else {
+                                field(data, key, k);
+                            }
+                        });
+                    } else {
+                        field(data, key);
+                    }
+                });
+                handlePolicyUploadDiv();
+                $("#fed_status").val(data.status);
+                if (data.status > 4 && admin != 1) {
+                    $("#status").addClass('ot');
+                    $("#status").prop('disabled', true);
+                }
+            }, "json");
+        }
+    
+jQuery(document).ready(function () {
+    $("#new_pol").click(function (event) {
+        event.preventDefault();
+        $("#pol_div").show();
+        $("#new_pol").hide();
+    });
+
+    $("#new_cert").click(function (event) {
+        event.preventDefault();
+        $("#cert_div").show();
+        $("#new_cert").hide();
+    });
+
+    $("#next_country_button").click(function (event) {
+        event.preventDefault();
+        $(this).parents("table").first().find("tr:hidden").first().show();
+        if ($(this).parents("table").first().find("tr:hidden").size() == 0)
+            $(this).hide();
+    });
+
+    $(".next_button").click(function (event) {
+            event.preventDefault();
+            $("." + $(this).attr('id') + ":hidden").first().show();
+            if ($("." + $(this).attr('id') + ":hidden").size() == 0)
+                $(this).hide();
+    });
+        
+    $('#fed_search_box').keyup(function(){
+        var input = $(this).val().toLowerCase().replace('~', '');
+        var results = quickSearch(input);
+        $('#feds').find('option').hide();
+
+        for(index in results) {
+            var federation_code = results[index].fed.code;
+            console.log(federation_code);
+            $('#' + federation_code).show();
+        }
+    });
+    $("#feds").change(loadFederation);
+        $("#main_fed_table").on('change', "select, input, file", function (event) {
+            changedFields.push($(this).attr("id"));
+        });
+
+        $("#main_fed_table").on('change', ".critical", function (event) {
+            $("#critical_save").val(1);
+        });
+        
+        $("#save").on('click', testSaveCompleteness);
+        $("#discard").on('click', discardChanges);
+        $("#new_f").on('click', newFederation);
+    });
+</script>
+{% endblock %}
+
+<div class="central-section">
+    <form id="main_form" action="" method="post" enctype="multipart/form-data">
+    <!-- <input type="submit" name="logout" value="Logout"> -->
+    <h1>Manage federations (your current role is {{role}})</h1>
+    <button id="new_f" class="ui-button ui-corner-all ui-widget">Create a new Federation</button><p>
+
+
+         Quick search: <input type="text" id="fed_search_box" title="type here"/><p>
+
+	<select id="feds" size="4" style="width: 550px;">
+	{% for F in federations %}
+                <option id='{{ F.fed.code }}'>{{ F.fed.name }} ({{ F.fed.countries|join(', ') }})</option>
+	{% endfor %}
+	</select>
+    <div id="federation_details" style="display:none">
+        <h3>Fields with <span class="critical">yellow background</span> require second factor authentication; those with <span class="ot">grey</span> and can be changed only by the OT members and are disabled for other users.</h3> 
+        <table id="main_fed_table">  
+        {% for row in rows %}
+             {{row|raw}}
+        {% endfor %}
+        </table>
+    </div>
+        <input type="hidden" name="critical_save" id="critical_save" value="0">
+        <input type="hidden" name="changed_fields" id="changed_fields" value="">
+        <input type="hidden" name="fed_status" id="fed_status" value="XXX">
+        <input type="hidden" name="new_fed" id="new_fed" value="0">
+        <button id="save" style="display:none; font-size: 20px" class="ui-button ui-corner-all ui-widget">Save changes</button>
+        <button id="discard" style="display:none; font-size: 20px" class="ui-button ui-corner-all ui-widget">Discard changes</button>
+    </form>
+</div>
+
+{% endblock main_body %}
+
diff --git a/templates/metadata.html b/templates/metadata.html
index 652783d99d644d512a3f45b7236f101f33af6448..b8920b43dd60da2057f45da4559f42820e138774 100644
--- a/templates/metadata.html
+++ b/templates/metadata.html
@@ -25,18 +25,9 @@ Should an error persist beyond the validUntil period of the copy, the data of th
 <dt>SHA-256</dt><dd>  BD:21:40:48:9A:9B:D7:40:44:DD:68:05:34:F7:78:88:A9:C1:3B:0A:C1:7C:4F:3A:03:6E:0F:EC:6D:89:99:95
 </dl>
 
-<h1>The old feed</h1>
-<p>Until the end of June 2022 the previous <a href="mds-v1.cer"><b>key</b></a> will still be used for signing of <a href="https://mds.edugain.org/edugain-v1.xml">https://mds.edugain.org/edugain-v1.xml</a></p>. 
-<h3>The old Signing certificate fingerprints</h3>
-<dl>
-<dt>SHA-1</dt><dd> 1A:D3:8C:97:3E:22:9C:84:59:C8:5C:BF:D7:FB:F1:8A:5E:61:07:CA</dd>
-<dt>SHA-256</dt><dd> 3E:A8:A0:D2:9C:B4:0E:73:96:FA:B1:77:C5:BF:F2:21:81:4E:60:67:51:78:5D:5E:7B:FA:59:4E:36:2F:C6:A6</dd>
-</dl>
+<h1>The old feeds</h1>
+The old feeds (using the previous and now expired certificate) were removed on My 31st 2023.
 
-<h2>Other feeds</h2>
-The feeds
- <a href="https://mds.edugain.org">https://mds.edugain.org</a> and <a href="https://mds.edugain.org/feed-sha256.xml">https://mds.edugain.org/feed-sha256.xml</a> 
-are just copies of https://mds.edugain.org//edugain-v1.xml and are provided for backwards compalitibility but should be considered deprecated.
 <!-- <h2>Pre-production feed</h2>
 Whenever the aggregator software is updated in a way that introduces changes to the eduGAIN metadata aggregate, a pre-production feed will be made available for testing. Typically the testing period will be two weeks. The test feed will be availabe at <a href=https://mds.edugain.org/edugain-v1-1-beta.xml">https://mds.edugain.org/edugain-v1-1-beta.xml</a>.
 -->
diff --git a/templates/status.html b/templates/status.html
index f05b258f1f260f1fd6224f84e414662c4351a693..bcba572e95838f9e1a855d259887aef93a58ab9e 100644
--- a/templates/status.html
+++ b/templates/status.html
@@ -18,8 +18,13 @@
 		<input type="checkbox" class="type_filter" id="include_candidates" value="candidates_group" checked="checked" />
 		<label for="include_candidates">Candidates ({{ candidates | length }})</label>
 	</div>
+        <div>
+            <input type="checkbox" id="problem_filter" />
+            <label for="include_problems">Limit to current feed problems</label>
+        </div>
 </div>
 
+
 <div class="central-section">
 
 	<div id="search_results" class="col-2-1">
@@ -177,24 +182,25 @@ function deselectAll(){
 			$dialog_test.dialog();
 			$dialog_test.dialog("destroy");
 			$dialog_test.dialog({ 'width' : w + 30,
-														'height' : h + 70,
-														'position' : {
-															my: 'right top',
-															at: 'right-50 top+50',
-															of: $('#breadcrumb')
-														},
-														'close': function(event, ui){
-															deselectAll();
-														}
-
-													});
-
+					      'height' : h + 70,
+					      'position' : {my: 'right top', at: 'right-50 top+50', of: $('#breadcrumb')},
+					      'close': function(event, ui){deselectAll();
+                                              }
+                        });
 			$dialog_test.html($memberInfo.html());
 		}
 
 
             showTime($dialog_test.find('span.timer'));
         }
+        
+        $('#problem_filter').on('change', function() {
+            if($('#problem_filter').prop('checked') ) {
+                $('.feed-ok').hide();
+            } else {
+        $('.feed-ok').show();    
+        }
+    });
 
 	$(document).keyup(function(e){
 		if(e.keyCode == 27){
diff --git a/templates/status/status-members.html b/templates/status/status-members.html
index 6a8c5774822d39d9443f98454f9b6a2d93bccd25..70c027b807755e2279e50cbd734181fdd8188a5b 100644
--- a/templates/status/status-members.html
+++ b/templates/status/status-members.html
@@ -6,9 +6,13 @@
  {% set alert_icon = 'green_dot.png' %}
  {% set alert_text = 'status OK' %}
  {% set error_message = '' %}
+ {% set problem_class = ' feed-ok' %}
  {% if federation.fed.feed_problem %}
   {% set alert_icon = 'orange_dot.png' %}
- {% set alert_text = 'metadata update problem' %}
+  {% set alert_text = 'metadata update problem' %}
+  {% if federation.fed.status == 6 %}
+   {% set problem_class = ' feed-problem' %}
+  {% endif %}
  {% endif %}
  {% if federation.information_missing %}
   {% set error_message = error_message ~ ' information missing' %}
@@ -18,12 +22,12 @@
  {% set alert_text = 'metadata dropped' %}
  {% elseif federation.fed.valid_sec <= constant('VALIDITY_ALERT_THRESHOLD') %}
   {% set alert_icon = 'orange_dot.png' %}
-  {% set alert_text = 'validUnitl under 3 hours' %}
+  {% set alert_text = 'validUnitl under ' ~ constant('VALIDITY_ALERT_THRESHOLD')/3600 ~ ' hours' %}
  {% elseif federation.fed.valid_sec < constant('VALIDITY_WARNING_THRESHOLD') %}
   {% set alert_icon = 'blue_dot.png' %}
-  {% set alert_text = 'validUnitl between 3 and 108 hours' %}
+  {% set alert_text = 'validUnitl between ' ~ constant('VALIDITY_ALERT_THRESHOLD')/3600 ~ ' and ' ~ constant('VALIDITY_WARNING_THRESHOLD')/3600 ~ ' hours' %}
  {% endif %}
-<div id="{{ federation.fed.code }}_option_div" class="member-div" style="padding-left: 1em;{%
+<div id="{{ federation.fed.code }}_option_div" class="member-div{{ problem_class }}" style="padding-left: 1em;{%
 if federation.information_missing
 %}color: red;{%
 endif
diff --git a/web/css/manage.css b/web/css/manage.css
new file mode 100644
index 0000000000000000000000000000000000000000..7fafa9142cdfb77449e16d9a87dff02d1e517ca5
--- /dev/null
+++ b/web/css/manage.css
@@ -0,0 +1,29 @@
+    .additional {
+        display: none;
+    }
+    #main_fed_table {
+        background: rgb(214, 214, 224);
+
+    }
+
+    #main_fed_table td {
+        background: white;
+        padding: 2px;
+        padding-left: 5px;
+    }
+    
+    .critical {
+        background: #fffa90;
+    }
+    
+    .ot {
+        background: #AAB7B8;
+    }
+    
+    .critical.ot {
+        background: #AAB7B8;
+    }
+    .missing {
+        background: #F5B7B1;
+    }
+
diff --git a/web/css/master.css b/web/css/master.css
index 15fbafa03bdbcde93db2f231b6c02863dfed9678..8b6823a57a6d69d7d8e9a229d668f5f124d68624 100644
--- a/web/css/master.css
+++ b/web/css/master.css
@@ -163,6 +163,10 @@ footer a {
 	background-position: 90% 5px;
 	background-image: url('../images/warning-icon.png');
 }
+
+.entity_links {
+        padding: 5px;
+}
 /* ------- Table Style 1 ------- */
 
 .ts-1 {
diff --git a/web/otp-server.php b/web/otp-server.php
new file mode 100644
index 0000000000000000000000000000000000000000..429d275d96e2f4a5f2f180dd14f3e7a7b349736d
--- /dev/null
+++ b/web/otp-server.php
@@ -0,0 +1,83 @@
+<?php
+/*
+ * The server can dwo two things - it can test if the user is defined or it can
+ * validate the otp_code against the secret in the database.
+ * 
+ * Return values:
+ *  -1 - user not found in the DB
+ *   0 - user exists but there was a missmatch in the code
+ *   1 - there was a success in verification of the code against the user secret
+ *   2 - the code has not been provided -  the user has not been verified yet
+ *   3 - the code has not been provided - just confirming that the user is verified
+ *   4 - the code has been used for a second time
+ */
+session_start();
+
+require_once('../vendor/autoload.php');
+require_once('/var/www/html/technical_tw_new/edugain.config/otp_sec.php');
+use OTPHP\TOTP;
+
+$mysqli = new mysqli(DB_HOST, USER, PASSWORD, DB_DATABASE);
+if ($mysqli->connect_error) {
+    die("Not connected");
+}
+$mysqli->set_charset('utf8');
+$mysqli->query("SET time_zone='+00:00'");
+
+if (empty($_GET['user'])) {
+    exit;
+}
+
+$user = filter_var($_GET['user'], FILTER_SANITIZE_EMAIL);
+$out = 0;
+
+$result = $mysqli->query("SELECT secret, last_code, verified from otp where user ='$user'");
+
+
+if ($result) {
+    if ($result->num_rows == 0) {
+        $out = -1; // the user is not defined
+    } else {
+        $r = $result->fetch_row();
+        $otpSecret = $r[0];
+        $otpLastCode = $r[1];
+        $verified = $r[2];
+        $out = 0; // the user exists in the database - this is a temporary code value
+    }
+} else {
+    exit; 
+}
+
+$otpCode = filter_var($_GET['otp'], FILTER_SANITIZE_NUMBER_INT);
+
+// check if any code has been passed and if so update the result code accordingle - again this value is temporary
+
+if ($otpCode == '' && $out == 0) {
+    if ($verified == 1) {
+        $out = 3;
+    } else {
+        $out = 2;
+    }
+}
+
+
+if ($out == 0) { // the otp code must have been provided and the user exists in the DB, the secret is taken form the DB
+    $otpObject = TOTP::create($otpSecret);
+    $otpTestCode = $otpObject->now();
+    if ($otpCode === $otpTestCode) {
+        if($otpCode === $otpLastCode) {
+            $out = 4;
+        } else {
+            $mysqli->query("UPDATE otp SET verified = 1, last_code = $otpCode where user = '$user'");
+            $out = 1;
+        }
+    } else {
+        // there was a missmatch in the codes
+        $out = 0;
+    }
+}
+header('Content-type: application/json; charset=utf-8');
+header('Access-Control-Allow-Origin: *');
+print json_encode($out, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+
+
diff --git a/web/verify-otp.php b/web/verify-otp.php
new file mode 100644
index 0000000000000000000000000000000000000000..34118acc32a72e542f522d5d1edce9adef5b146a
--- /dev/null
+++ b/web/verify-otp.php
@@ -0,0 +1,16 @@
+<?php
+ini_set( "session.cookie_lifetime", 3600 );
+session_start();
+require_once('../lib/config.php');
+
+// this is a temporary solution and will be incorporated inyot the Management class
+// in the future
+if (isset($_POST['otp'])) {
+    $otpCode = filter_var($_POST['otp'], FILTER_SANITIZE_NUMBER_INT);
+    $user = $_SESSION['user']['email'];
+    $url = OTP_SERVER.'?user='.$user.'&otp='.$otpCode;
+    $out = file_get_contents($url);
+    header('Content-type: application/json; charset=utf-8');
+    header('Access-Control-Allow-Origin: *');
+    print($out);
+}