diff --git a/core/EntityWithDBProperties.php b/core/EntityWithDBProperties.php index f308ec955..b4d0c8837 100644 --- a/core/EntityWithDBProperties.php +++ b/core/EntityWithDBProperties.php @@ -153,11 +153,15 @@ private function getRelevantIdentifier() * are not considered. * * @param string $optionName optionally, the name of the attribute that is to be retrieved + * @param string$omittedOptionName optionally drop attibutes with that name * @return array of arrays of attributes which were set for this IdP */ - public function getAttributes(string $optionName = NULL) + public function getAttributes(string $optionName = NULL, string $omittedOptionName = NULL) { if ($optionName !== NULL) { + if ($optionName === $omittedOptionName) { + throw new Exception("The attibute to be shown has the same name as that to be omitted"); + } $returnarray = []; foreach ($this->attributes as $theAttr) { if ($theAttr['name'] == $optionName) { @@ -166,9 +170,19 @@ public function getAttributes(string $optionName = NULL) } return $returnarray; } + if ($omittedOptionName !== NULL) { + $returnarray = []; + foreach ($this->attributes as $theAttr) { + if ($theAttr['name'] !== $omittedOptionName) { + $returnarray[] = $theAttr; + } + } + return $returnarray; + } return $this->attributes; } + /** * deletes all attributes in this profile except the _file ones, these are reported as array * diff --git a/core/Federation.php b/core/Federation.php index a3494bca5..24b85894e 100644 --- a/core/Federation.php +++ b/core/Federation.php @@ -68,35 +68,63 @@ class Federation extends EntityWithDBProperties * @var string */ public $tld; - + /** * retrieve the statistics from the database in an internal array representation * + * @param string $detail * @return array */ - private function downloadStatsCore() + private function downloadStatsCore($detail = '') { + if ($detail !== 'ORGANISATIONS' && $detail !== 'PROFILES') { + $detail = ''; + } $grossAdmin = 0; $grossUser = 0; $grossSilverbullet = 0; $dataArray = []; // first, find out which profiles belong to this federation - $cohesionQuery = "SELECT downloads.device_id as dev_id, sum(downloads.downloads_user) as dl_user, sum(downloads.downloads_silverbullet) as dl_sb, sum(downloads.downloads_admin) as dl_admin FROM profile, institution, downloads WHERE profile.inst_id = institution.inst_id AND institution.country = ? AND profile.profile_id = downloads.profile_id group by device_id"; - $profilesList = $this->databaseHandle->exec($cohesionQuery, "s", $this->tld); + if ($detail === 'ORGANISATIONS') { + $cohesionQuery = "SELECT profile.inst_id AS inst_id, downloads.device_id AS dev_id, sum(downloads.downloads_user) AS dl_user, sum(downloads.downloads_silverbullet) as dl_sb, sum(downloads.downloads_admin) AS dl_admin FROM downloads JOIN profile ON downloads.profile_id=profile.profile_id JOIN institution ON profile.inst_id=institution.inst_id WHERE institution.country = ? GROUP BY profile.inst_id, downloads.device_id"; + } elseif ($detail === 'PROFILES') { + $cohesionQuery = "SELECT profile.inst_id AS inst_id, profile.profile_id AS profile_id, downloads.device_id AS dev_id, sum(downloads.downloads_user) AS dl_user, sum(downloads.downloads_silverbullet) as dl_sb, sum(downloads.downloads_admin) AS dl_admin FROM downloads JOIN profile ON downloads.profile_id=profile.profile_id JOIN institution ON profile.inst_id=institution.inst_id WHERE institution.country = ? GROUP BY profile.inst_id, profile.profile_id, downloads.device_id"; + } else { + $cohesionQuery = "SELECT downloads.device_id as dev_id, sum(downloads.downloads_user) as dl_user, sum(downloads.downloads_silverbullet) AS dl_sb, sum(downloads.downloads_admin) as dl_admin FROM profile, institution, downloads WHERE profile.inst_id = institution.inst_id AND institution.country = ? AND profile.profile_id = downloads.profile_id group by device_id"; + } + $downloadsList = $this->databaseHandle->exec($cohesionQuery, "s", $this->tld); $deviceArray = \devices\Devices::listDevices(); // SELECT -> resource, no boolean - while ($queryResult = mysqli_fetch_object(/** @scrutinizer ignore-type */ $profilesList)) { - if (isset($deviceArray[$queryResult->dev_id])) { - $displayName = $deviceArray[$queryResult->dev_id]['display']; - } else { // this device has stats, but doesn't exist in current config. We don't even know its display name, so display its raw representation - $displayName = sprintf(_("(discontinued) %s"), $queryResult->dev_id); + while ($queryResult = mysqli_fetch_object(/** @scrutinizer ignore-type */ $downloadsList)) { + if ($detail === 'ORGANISATIONS' || $detail === 'PROFILES') { + $inst_id = $queryResult->inst_id; + if (isset($deviceArray[$queryResult->dev_id])) { + $displayName = $deviceArray[$queryResult->dev_id]['display']; + } else { // this device has stats, but doesn't exist in current config. We don't even know its display name, so display its raw representation + $displayName = sprintf(_("(discontinued) %s"), $queryResult->dev_id); + } + if (! isset($dataArray[$inst_id])) { + $dataArray[$inst_id] = []; + } + } + if ($detail === 'ORGANISATIONS') { + $dataArray[$inst_id][$displayName] = ["ADMIN" => $queryResult->dl_admin, "SILVERBULLET" => $queryResult->dl_sb, "USER" => $queryResult->dl_user]; + } elseif ($detail === 'PROFILES') { + $profile_id = $queryResult->profile_id; + if (! isset($dataArray[$inst_id][$profile_id])) { + $dataArray[$inst_id][$profile_id] = []; + } + $dataArray[$inst_id][$profile_id][$displayName] = ["ADMIN" => $queryResult->dl_admin, "SILVERBULLET" => $queryResult->dl_sb, "USER" => $queryResult->dl_user]; + } + if ($detail === '') { + $grossAdmin = $grossAdmin + $queryResult->dl_admin; + $grossSilverbullet = $grossSilverbullet + $queryResult->dl_sb; + $grossUser = $grossUser + $queryResult->dl_user; } - $dataArray[$displayName] = ["ADMIN" => $queryResult->dl_admin, "SILVERBULLET" => $queryResult->dl_sb, "USER" => $queryResult->dl_user]; - $grossAdmin = $grossAdmin + $queryResult->dl_admin; - $grossSilverbullet = $grossSilverbullet + $queryResult->dl_sb; - $grossUser = $grossUser + $queryResult->dl_user; } - $dataArray["TOTAL"] = ["ADMIN" => $grossAdmin, "SILVERBULLET" => $grossSilverbullet, "USER" => $grossUser]; + if ($detail === '') { + $dataArray["TOTAL"] = ["ADMIN" => $grossAdmin, "SILVERBULLET" => $grossSilverbullet, "USER" => $grossUser]; + } return $dataArray; } @@ -121,9 +149,9 @@ public function updateFreshness() * @return string|array * @throws Exception */ - public function downloadStats($format) + public function downloadStats($format, $detail = '') { - $data = $this->downloadStatsCore(); + $data = $this->downloadStatsCore($detail); $retstring = ""; switch ($format) { @@ -153,7 +181,6 @@ public function downloadStats($format) default: throw new Exception("Statistics can be requested only in 'table' or 'XML' format!"); } - return $retstring; } diff --git a/web/admin/API.php b/web/admin/API.php index 2c02723dc..7c95a5b4d 100644 --- a/web/admin/API.php +++ b/web/admin/API.php @@ -34,8 +34,8 @@ $adminApi->returnError(web\lib\admin\API::ERROR_API_DISABLED, "API is disabled in this instance of CAT"); exit(1); } - $inputRaw = file_get_contents('php://input'); + $inputDecoded = json_decode($inputRaw, TRUE); if (!is_array($inputDecoded)) { $adminApi->returnError(web\lib\admin\API::ERROR_MALFORMED_REQUEST, "Unable to decode JSON POST data." . json_last_error_msg() . $inputRaw); @@ -61,7 +61,6 @@ exit(1); } - // let's instantiate the fed, we will need it later $fed = new \core\Federation($federation); // it's a valid admin; what does he want to do? @@ -69,6 +68,7 @@ $adminApi->returnError(web\lib\admin\API::ERROR_NO_ACTION, "JSON request structure did not contain a valid ACTION"); exit(1); } + // it's a valid ACTION, so let's sanitise the input parameters $scrubbedParameters = $adminApi->scrub($inputDecoded, $fed); $paramNames = []; @@ -172,16 +172,30 @@ $adminApi->returnError(web\lib\admin\API::ERROR_INVALID_PARAMETER, "The admin with ID $toBeDeleted is not associated to IdP " . $idp->identifier); break; case web\lib\admin\API::ACTION_STATISTICS_FED: - $adminApi->returnSuccess($fed->downloadStats("array")); + $detail = $adminApi->firstParameterInstance($scrubbedParameters, web\lib\admin\API::AUXATTRIB_DETAIL); + $adminApi->returnSuccess($fed->downloadStats("array", $detail)); break; case \web\lib\admin\API::ACTION_FEDERATION_LISTIDP: $retArray = []; + $noLogo = null; $idpIdentifier = $adminApi->firstParameterInstance($scrubbedParameters, web\lib\admin\API::AUXATTRIB_CAT_INST_ID); + $logoFlag = $adminApi->firstParameterInstance($scrubbedParameters, web\lib\admin\API::FLAG_NOLOGO); + $detail = $adminApi->firstParameterInstance($scrubbedParameters, web\lib\admin\API::AUXATTRIB_DETAIL); + $idpStatFlag = $adminApi->firstParameterInstance($scrubbedParameters, web\lib\admin\API::FLAG_ADD_STATS); + if ($logoFlag === "TRUE") { + $noLogo = 'general:logo_file'; + } if ($idpIdentifier === FALSE) { $allIdPs = $fed->listIdentityProviders(0); + if ($idpStatFlag === "TRUE") { + $fedStats = $fed->downloadStats('array', $detail); + } foreach ($allIdPs as $instanceId => $oneIdP) { $theIdP = $oneIdP["instance"]; - $retArray[$instanceId] = $theIdP->getAttributes(); + $retArray[$instanceId] = $theIdP->getAttributes(null, $noLogo); + if ($idpStatFlag === "TRUE") { + $retArray[$instanceId]['STAT'] = $fedStats[$instanceId]; + } } } else { try { @@ -190,9 +204,9 @@ $adminApi->returnError(web\lib\admin\API::ERROR_INVALID_PARAMETER, "IdP identifier does not exist!"); exit(1); } - $retArray[$idpIdentifier] = $thisIdP->getAttributes(); + $retArray[$idpIdentifier] = $thisIdP->getAttributes(null, $noLogo); foreach ($thisIdP->listProfiles() as $oneProfile) { - $retArray[$idpIdentifier]["PROFILES"][$oneProfile->identifier] = $oneProfile->getAttributes(); + $retArray[$idpIdentifier]["PROFILES"][$oneProfile->identifier] = $oneProfile->getAttributes(null, $noLogo); } } foreach ($retArray as $instNumber => $oneInstData) { @@ -213,6 +227,16 @@ } } } + +/* + $retArray[$idpIdentifier] = []; + foreach ($thisIdP->listProfiles() as $oneProfile) { + $retArray[$idpIdentifier][$oneProfile->identifier] = $oneProfile->getUserDownloadStats(); + } + + * + */ + $adminApi->returnSuccess($retArray); break; case \web\lib\admin\API::ACTION_NEWPROF_RADIUS: diff --git a/web/lib/admin/API.php b/web/lib/admin/API.php index 467979306..c28859c6a 100644 --- a/web/lib/admin/API.php +++ b/web/lib/admin/API.php @@ -234,14 +234,19 @@ class API { const AUXATTRIB_SB_USERNAME = "ATTRIB-MANAGED-USERNAME"; const AUXATTRIB_SB_USERID = "ATTRIB-MANAGED-USERID"; const AUXATTRIB_SB_CERTSERIAL = "ATTRIB-MANAGED-CERTSERIAL"; - const AUXATTRIB_SB_CERTCN = "ATTRIB-MANAGED-CERTCN"; + const AUXATTRIB_SB_CERTCN = "ATTRIB-MANAGED-CERTCN"; const AUXATTRIB_SB_CERTANNOTATION = "ATTRIB-MANAGED-CERTANNOTATION"; const AUXATTRIB_SB_EXPIRY = "ATTRIB-MANAGED-EXPIRY"; /* MySQL timestamp format */ const AUXATTRIB_TOKEN = "ATTRIB-TOKEN"; const AUXATTRIB_TOKENURL = "ATTRIB-TOKENURL"; const AUXATTRIB_TOKEN_ACTIVATIONS = "ATTRIB-TOKEN-ACTIVATIONS"; const AUXATTRIB_INSTTYPE = "ATTRIB-INSTITUTION-TYPE"; - + const AUXATTRIB_DETAIL = "ATTRIB-DETAIL"; + /** + * This section defines allowed flags for actions + */ + const FLAG_NOLOGO = "FLAG-NO-LOGO"; // skip logos in attribute listings + const FLAG_ADD_STATS = "FLAG-ADD-STATS"; // add IdP statistice - only used in ACTION_FEDERATION_LISTIDP /* * ACTIONS consists of a list of keywords, and associated REQuired and OPTional parameters * @@ -263,6 +268,7 @@ class API { 'support:phone', 'support:url' ], + "FLAG" => [], ], API::ACTION_NEWINST => [ "REQ" => [API::AUXATTRIB_INSTTYPE,], // "IdP", "SP" or "IdPSP" @@ -280,20 +286,22 @@ class API { 'support:phone', 'support:url' ], + "FLAG" => [], "RETVAL" => [ API::AUXATTRIB_CAT_INST_ID, // New inst ID. - ], + ], ], API::ACTION_DELINST => [ "REQ" => [API::AUXATTRIB_CAT_INST_ID], "OPT" => [], + "FLAG" => [], "RETVAL" => [], ], // Inst administrator management. API::ACTION_ADMIN_LIST => [ "REQ" => [API::AUXATTRIB_CAT_INST_ID], - "OPT" => [ - ], + "OPT" => [], + "FLAG" => [], "RETVAL" => [ ["ID", "MAIL", "LEVEL"] // Array with all admins of inst. ] @@ -304,6 +312,7 @@ class API { API::AUXATTRIB_CAT_INST_ID ], "OPT" => [API::AUXATTRIB_TARGETMAIL], + "FLAG" => [], "RETVAL" => [ ["TOKEN URL", "EMAIL SENT", // Dependent on TARGETMAIL input. @@ -316,24 +325,28 @@ class API { API::AUXATTRIB_CAT_INST_ID ], "OPT" => [], + "FLAG" => [], "RETVAL" => [], ], // Statistics. API::ACTION_STATISTICS_INST => [ "REQ" => [API::AUXATTRIB_CAT_INST_ID], - "OPT" => [] + "OPT" => [], + "FLAG" => [], ], API::ACTION_STATISTICS_FED => [ "REQ" => [], - "OPT" => [], + "OPT" => [API::AUXATTRIB_DETAIL], + "FLAG" => [], "RETVAL" => [ ["device_id" => ["ADMIN", "SILVERBULLET", "USER"]] // Plus "TOTAL". ], ], API::ACTION_FEDERATION_LISTIDP => [ "REQ" => [], - "OPT" => [API::AUXATTRIB_CAT_INST_ID], + "OPT" => [API::AUXATTRIB_CAT_INST_ID, API::AUXATTRIB_DETAIL], "RETVAL" => [API::AUXATTRIB_CAT_INST_ID => "JSON_DATA"], + "FLAG" => [API::FLAG_NOLOGO, API::FLAG_ADD_STATS], ], // RADIUS profile actions. API::ACTION_NEWPROF_RADIUS => [ @@ -362,32 +375,38 @@ class API { API::AUXATTRIB_PROFILE_TESTUSER, API::AUXATTRIB_PROFILE_EAPTYPE, ], + "FLAG" => [], "RETVAL" => API::AUXATTRIB_CAT_PROFILE_ID, ], // Silverbullet profile actions. API::ACTION_NEWPROF_SB => [ "REQ" => [API::AUXATTRIB_CAT_INST_ID], "OPT" => [API::AUXATTRIB_SB_TOU], + "FLAG" => [], "RETVAL" => API::AUXATTRIB_CAT_PROFILE_ID, ], API::ACTION_ENDUSER_NEW => [ "REQ" => [API::AUXATTRIB_CAT_PROFILE_ID, API::AUXATTRIB_SB_USERNAME, API::AUXATTRIB_SB_EXPIRY], "OPT" => [], + "FLAG" => [], "RETVAL" => [API::AUXATTRIB_SB_USERNAME, API::AUXATTRIB_SB_USERID], ], API::ACTION_ENDUSER_CHANGEEXPIRY => [ "REQ" => [API::AUXATTRIB_CAT_PROFILE_ID, API::AUXATTRIB_SB_USERNAME, API::AUXATTRIB_SB_EXPIRY], "OPT" => [], + "FLAG" => [], "RETVAL" => [], ], API::ACTION_ENDUSER_DEACTIVATE => [ "REQ" => [API::AUXATTRIB_CAT_PROFILE_ID, API::AUXATTRIB_SB_USERID], "OPT" => [], + "FLAG" => [], "RETVAL" => [], ], API::ACTION_ENDUSER_LIST => [ "REQ" => [API::AUXATTRIB_CAT_PROFILE_ID], "OPT" => [], + "FLAG" => [], "RETVAL" => [ [API::AUXATTRIB_SB_USERID => API::AUXATTRIB_SB_USERNAME], ], @@ -395,11 +414,13 @@ class API { API::ACTION_ENDUSER_IDENTIFY => [ "REQ" => [API::AUXATTRIB_CAT_PROFILE_ID], "OPT" => [API::AUXATTRIB_SB_USERID, API::AUXATTRIB_SB_USERNAME, API::AUXATTRIB_SB_CERTSERIAL, API::AUXATTRIB_SB_CERTCN], + "FLAG" => [], "RETVAL" => [API::AUXATTRIB_SB_USERNAME, API::AUXATTRIB_SB_USERID], ], API::ACTION_TOKEN_NEW => [ "REQ" => [API::AUXATTRIB_CAT_PROFILE_ID, API::AUXATTRIB_SB_USERID], "OPT" => [API::AUXATTRIB_TOKEN_ACTIVATIONS, API::AUXATTRIB_TARGETMAIL, API::AUXATTRIB_TARGETSMS], + "FLAG" => [], "RETVAL" => [ API::AUXATTRIB_TOKENURL, API::AUXATTRIB_TOKEN, @@ -411,11 +432,13 @@ class API { API::ACTION_TOKEN_REVOKE => [ "REQ" => [API::AUXATTRIB_TOKEN], "OPT" => [], + "FLAG" => [], "RETVAL" => [], ], API::ACTION_TOKEN_LIST => [ "REQ" => [API::AUXATTRIB_CAT_PROFILE_ID], "OPT" => [API::AUXATTRIB_SB_USERID], + "FLAG" => [], "RETVAL" => [ [API::AUXATTRIB_SB_USERID => [API::AUXATTRIB_TOKEN, "STATUS"]], ] @@ -423,6 +446,7 @@ class API { API::ACTION_CERT_LIST => [ "REQ" => [API::AUXATTRIB_CAT_PROFILE_ID, API::AUXATTRIB_SB_USERID], "OPT" => [], + "FLAG" => [], "RETVAL" => [ [API::AUXATTRIB_SB_CERTSERIAL => ["ISSUED", "EXPIRY", "STATUS", "DEVICE", "CN"]] ] @@ -430,11 +454,13 @@ class API { API::ACTION_CERT_REVOKE => [ "REQ" => [API::AUXATTRIB_CAT_PROFILE_ID, API::AUXATTRIB_SB_CERTSERIAL], "OPT" => [], + "FLAG" => [], "RETVAL" => [], ], API::ACTION_CERT_ANNOTATE => [ "REQ" => [API::AUXATTRIB_CAT_PROFILE_ID, API::AUXATTRIB_SB_CERTSERIAL, API::AUXATTRIB_SB_CERTANNOTATION], "OPT" => [], + "FLAG" => [], "RETVAL" => [], ] ]; @@ -450,6 +476,7 @@ class API { */ public function __construct() { $this->validator = new \web\lib\common\InputValidation(); + $this->loggerInstance = new \core\common\Logging(); } /** @@ -460,10 +487,10 @@ public function __construct() { * @param \core\Federation $fedObject the federation the user is acting within * @return array the scrubbed attributes */ - public function scrub($inputJson, $fedObject) { + public function scrub($inputJson, $fedObject) { $optionInstance = \core\Options::instance(); $parameters = []; - $allPossibleAttribs = array_merge(API::ACTIONS[$inputJson['ACTION']]['REQ'], API::ACTIONS[$inputJson['ACTION']]['OPT']); + $allPossibleAttribs = array_merge(API::ACTIONS[$inputJson['ACTION']]['REQ'], API::ACTIONS[$inputJson['ACTION']]['OPT'], API::ACTIONS[$inputJson['ACTION']]['FLAG']); // some actions don't need parameters. Don't get excited when there aren't any. if (!isset($inputJson['PARAMETERS'])) { $inputJson['PARAMETERS'] = []; @@ -477,13 +504,7 @@ public function scrub($inputJson, $fedObject) { if (!array_key_exists("VALUE", $oneIncomingParam)) { continue; } - // is this multi-lingual, and not an AUX attrib? Then check for presence of LANG and CONTENT before considering to add - if (!preg_match("/^ATTRIB-/", $oneIncomingParam['NAME'])) { - $optionProperties = $optionInstance->optionType($oneIncomingParam['NAME']); - if ($optionProperties["flag"] == "ML" && !array_key_exists("LANG", $oneIncomingParam)) { - continue; - } - } else { // sanitise the AUX attr + if (preg_match("/^ATTRIB-/", $oneIncomingParam['NAME'])) {// sanitise the AUX attr switch ($oneIncomingParam['NAME']) { case API::AUXATTRIB_CAT_INST_ID: try { @@ -510,6 +531,16 @@ public function scrub($inputJson, $fedObject) { break; default: break; + } + } elseif (preg_match("/^FLAG-/", $oneIncomingParam['NAME'])) { + if ($oneIncomingParam['VALUE'] != "TRUE" && $oneIncomingParam['VALUE'] != "FALSE" ) { + continue; + } + } else { + // is this multi-lingual, and not an AUX attrib? Then check for presence of LANG and CONTENT before considering to add + $optionProperties = $optionInstance->optionType($oneIncomingParam['NAME']); + if ($optionProperties["flag"] == "ML" && !array_key_exists("LANG", $oneIncomingParam)) { + continue; } } if (in_array($oneIncomingParam['NAME'], $allPossibleAttribs)) { @@ -650,5 +681,7 @@ public function commonSbProfileChecks($fed, $id) { } return [$idp, $profile]; } + + public $loggerInstance; }