web/lib/admin/API.php
<?php
/**
* *****************************************************************************
* Contributions to this work were made on behalf of the GÉANT project, a
* project that has received funding from the European Union’s Framework
* Programme 7 under Grant Agreements No. 238875 (GN3) and No. 605243 (GN3plus),
* Horizon 2020 research and innovation programme under Grant Agreements No.
* 691567 (GN4-1) and No. 731122 (GN4-2).
* On behalf of the aforementioned projects, GEANT Association is the sole owner
* of the copyright in all material which was developed by a member of the GÉANT
* project. GÉANT Vereniging (Association) is registered with the Chamber of
* Commerce in Amsterdam with registration number 40535155 and operates in the
* UK as a branch of GÉANT Vereniging.
*
* Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands.
* UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
*
* License: see the web/copyright.inc.php file in the file structure or
* <base_url>/copyright.php after deploying the software
*
* @package AdminAPI
* @author Stefan Winter <stefan.winter@restena.lu>
* @license https://github.com/GEANT/CAT/blob/master/web/copyright.inc.php GEANT Standard Open Source Software Outward Licence
* @link API
*/
namespace web\lib\admin;
use Exception;
/**
* This class defines the various actions doable with the admin API, the
* parameters and return values.
*
*/
class API {
/**
* This error is returned if the API is globally disabled on a deployment.
*/
const ERROR_API_DISABLED = 1;
/**
* This error is returned if the API request did not contain an API key.
*/
const ERROR_NO_APIKEY = 2;
/**
* This error is returned if the API request contained an unknown API key.
*/
const ERROR_INVALID_APIKEY = 3;
/**
* An action was requested, but one if its required parameters was missing.
*/
const ERROR_MISSING_PARAMETER = 4;
/**
* An action was requested, but one if its required parameters was
* malformed.
*/
const ERROR_INVALID_PARAMETER = 5;
/**
* The API request did not contain a requested action.
*/
const ERROR_NO_ACTION = 6;
/**
* The action that was requested is not recognised.
*/
const ERROR_INVALID_ACTION = 7;
/**
* The API request as a whole did not parse correctly.
*/
const ERROR_MALFORMED_REQUEST = 8;
/**
* An internal error occurred and the requested action could not be
* performed.
*/
const ERROR_INTERNAL_ERROR = 9;
/**
* An action for a Silverbullet profile was requested, but the profile admin
* has not accepted the Terms of Use for Silverbullet yet.
*
* Note: Silverbullet is currently marketed as "eduroam Managed IdP" on the
* eduroam deployment of the code base.
*/
const ERROR_NO_TOU = 10;
/**
* This action creates a new institution. The institution is identified by
* a reference to the external DB.
*/
const ACTION_NEWINST_BY_REF = "NEWINST-BY-REF";
/**
* This action creates a new institution. The institution is created in the
* system, all institution properties are set by including optional
* parameters.
*/
const ACTION_NEWINST = "NEWINST";
/**
* This action deletes an existing institution.
*/
const ACTION_DELINST = "DELINST";
/**
* This action lists all administrators of an institution.
*/
const ACTION_ADMIN_LIST = "ADMIN-LIST";
/**
* This action creates a new invitation token for administering an existing
* institution. The invitation can be sent directly via mail, or the sign-up
* token can be returned in the API response for the caller to hand it out.
*/
const ACTION_ADMIN_ADD = "ADMIN-ADD";
/**
* This action de-authorises an existing administrator from administering an
* institution. The institution is not deleted. If the administrator is also
* managing other institutions, that is not changed.
*/
const ACTION_ADMIN_DEL = "ADMIN-DEL";
/**
* This action retrieves download statistics for a given institution.
*/
const ACTION_STATISTICS_INST = "STATISTICS-INST";
/**
* This action retrieves cumulated statistics for the entire federation.
*/
const ACTION_STATISTICS_FED = "STATISTICS-FED";
/**
* Dumps all configured information about IdPs in the federation
*/
const ACTION_FEDERATION_LISTIDP = "DATADUMP-FED";
/**
* This action creates a new RADIUS profile (i.e. a classic profile for
* institutions with their own RADIUS server, delivering one installer for
* this profile).
*/
const ACTION_NEWPROF_RADIUS = "NEWPROF-RADIUS";
/**
* This action creates a new Managed IdP profile (i.e. a profile where all
* RADIUS is handled by our system and the administrator merely needs to
* provision users via a web interface).
*/
const ACTION_NEWPROF_SB = "NEWPROF-MANAGED";
/**
* This action creates a new end-user within a Managed IdP profile.
*/
const ACTION_ENDUSER_NEW = "ENDUSER-NEW";
/**
* This action changes the end user expiry date
*/
const ACTION_ENDUSER_CHANGEEXPIRY = "ENDUSER-CHANGEEXPIRY";
/**
* This action deactivates an existing end user in a Managed IdP profile.
*/
const ACTION_ENDUSER_DEACTIVATE = "ENDUSER-DEACTIVATE";
/**
* This action lists all end users in a given Managed IdP profile.
*/
const ACTION_ENDUSER_LIST = "ENDUSER-LIST";
/**
* This action identifies a user account from either his user ID, username
* or any of their certificate CNs.
*/
const ACTION_ENDUSER_IDENTIFY = "ENDUSER-IDENTIFY";
/**
* This action creates a new end-user voucher for eduroam credential
* installation.
*/
const ACTION_TOKEN_NEW = "TOKEN-NEW";
/**
* This action cancels a currently valid end-user voucher. Existing redeemed
* credentials based on that voucher remain valid.
*/
const ACTION_TOKEN_REVOKE = "TOKEN-REVOKE";
/**
* This action lists all vouchers for a given end-user.
*/
const ACTION_TOKEN_LIST = "TOKEN-LIST";
/**
* This action lists all client certificate credentials issued to a given
* end user.
*/
const ACTION_CERT_LIST = "CERT-LIST";
/**
* This action revokes a specific client cert.
*/
const ACTION_CERT_REVOKE = "CERT-REVOKE";
/**
* This action adds internal notes regarding this certificate. These notes
* are included when retrieving certificate information with
* ACTION_CERT_LIST but are not actively used for anything.
*/
const ACTION_CERT_ANNOTATE = "CERT-ANNOTATE";
const AUXATTRIB_ADMINID = "ATTRIB-ADMINID";
const AUXATTRIB_TARGETMAIL = "ATTRIB-TARGETMAIL";
const AUXATTRIB_TARGETSMS = "ATTRIB-TARGETSMS";
const AUXATTRIB_EXTERNALID = "ATTRIB-EXTERNALID";
const AUXATTRIB_CAT_INST_ID = "ATTRIB-CAT-INSTID";
const AUXATTRIB_CAT_PROFILE_ID = "ATTRIB-CAT-PROFILEID";
const AUXATTRIB_PROFILE_REALM = 'ATTRIB-PROFILE-REALM';
const AUXATTRIB_PROFILE_OUTERVALUE = 'ATTRIB-PROFILE-OUTERVALUE';
const AUXATTRIB_PROFILE_TESTUSER = 'ATTRIB-PROFILE-TESTUSER';
const AUXATTRIB_PROFILE_INPUT_HINT = 'ATTRIB-PROFILE-HINTREALM';
const AUXATTRIB_PROFILE_INPUT_VERIFY = 'ATTRIB-PROFILE-VERIFYREALM';
const AUXATTRIB_PROFILE_EAPTYPE = "ATTRIB-PROFILE-EAPTYPE";
const AUXATTRIB_SB_TOU = "ATTRIB-MANAGED-TOU";
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_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
/*
* ACTIONS consists of a list of keywords, and associated REQuired and OPTional parameters
*
*/
const ACTIONS = [
// Inst-level actions.
API::ACTION_NEWINST_BY_REF => [
"REQ" => [API::AUXATTRIB_EXTERNALID,],
"OPT" => [
'general:geo_coordinates',
'general:logo_file',
'media:SSID',
'media:wired',
'media:remove_SSID',
'media:consortium_OI',
'media:force_proxy',
'support:email',
'support:info_file',
'support:phone',
'support:url'
],
"FLAG" => [],
],
API::ACTION_NEWINST => [
"REQ" => [API::AUXATTRIB_INSTTYPE,], // "IdP", "SP" or "IdPSP"
"OPT" => [
'general:instname',
'general:geo_coordinates',
'general:logo_file',
'media:SSID',
'media:wired',
'media:remove_SSID',
'media:consortium_OI',
'media:force_proxy',
'support:email',
'support:info_file',
'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" => [],
"FLAG" => [],
"RETVAL" => [
["ID", "MAIL", "LEVEL"] // Array with all admins of inst.
]
],
API::ACTION_ADMIN_ADD => [
"REQ" => [
API::AUXATTRIB_ADMINID,
API::AUXATTRIB_CAT_INST_ID
],
"OPT" => [API::AUXATTRIB_TARGETMAIL],
"FLAG" => [],
"RETVAL" => [
["TOKEN URL",
"EMAIL SENT", // Dependent on TARGETMAIL input.
"EMAIL TRANSPORT SECURE"], // Dependent on TARGETMAIL input.
]
],
API::ACTION_ADMIN_DEL => [
"REQ" => [
API::AUXATTRIB_ADMINID,
API::AUXATTRIB_CAT_INST_ID
],
"OPT" => [],
"FLAG" => [],
"RETVAL" => [],
],
// Statistics.
API::ACTION_STATISTICS_INST => [
"REQ" => [API::AUXATTRIB_CAT_INST_ID],
"OPT" => [],
"FLAG" => [],
],
API::ACTION_STATISTICS_FED => [
"REQ" => [],
"OPT" => [API::AUXATTRIB_DETAIL],
"FLAG" => [],
"RETVAL" => [
["device_id" => ["ADMIN", "SILVERBULLET", "USER"]] // Plus "TOTAL".
],
],
API::ACTION_FEDERATION_LISTIDP => [
"REQ" => [],
"OPT" => [API::AUXATTRIB_CAT_INST_ID],
"RETVAL" => [API::AUXATTRIB_CAT_INST_ID => "JSON_DATA"],
"FLAG" => [API::FLAG_NOLOGO],
],
// RADIUS profile actions.
API::ACTION_NEWPROF_RADIUS => [
"REQ" => [API::AUXATTRIB_CAT_INST_ID],
"OPT" => [
'eap:ca_file',
'eap:server_name',
'media:SSID',
'media:wired',
'media:remove_SSID',
'media:consortium_OI',
'media:force_proxy',
'profile:name',
'profile:customsuffix',
'profile:description',
'profile:production',
'support:email',
'support:info_file',
'support:phone',
'support:url',
'device-specific:redirect',
API::AUXATTRIB_PROFILE_INPUT_HINT,
API::AUXATTRIB_PROFILE_INPUT_VERIFY,
API::AUXATTRIB_PROFILE_OUTERVALUE,
API::AUXATTRIB_PROFILE_REALM,
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],
],
],
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,
"EMAIL SENT", // Dependent on TARGETMAIL input.
"EMAIL TRANSPORT SECURE", // Dependent on TARGETMAIL input.
"SMS SENT", // Dependent on TARGETSMS input.
]
],
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"]],
]
],
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"]]
]
],
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" => [],
]
];
/**
*
* @var \web\lib\common\InputValidation
*/
private $validator;
/**
* construct the API class
*/
public function __construct() {
$this->validator = new \web\lib\common\InputValidation();
$this->loggerInstance = new \core\common\Logging();
}
/**
* Only leave attributes in the request which are related to the ACTION.
* Also sanitise by enforcing LANG attribute in multi-lang attributes.
*
* @param array $inputJson the incoming JSON request
* @param \core\Federation $fedObject the federation the user is acting within
* @return array the scrubbed attributes
*/
public function scrub($inputJson, $fedObject) {
$optionInstance = \core\Options::instance();
$parameters = [];
$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'])) {
return [];
}
foreach ($inputJson['PARAMETERS'] as $number => $oneIncomingParam) {
// index has to be an integer
if (!is_int($number)) {
continue;
}
// do we actually have a value?
if (!array_key_exists("VALUE", $oneIncomingParam)) {
continue;
}
if (preg_match("/^ATTRIB-/", $oneIncomingParam['NAME'])) {// sanitise the AUX attr
switch ($oneIncomingParam['NAME']) {
case API::AUXATTRIB_CAT_INST_ID:
try {
$inst = $this->validator->existingIdP($oneIncomingParam['VALUE']);
} catch (Exception $e) {
continue 2;
}
if (strtoupper($inst->federation) != strtoupper($fedObject->tld)) {
// IdP in different fed, scrub it.
continue 2;
}
break;
case API::AUXATTRIB_TARGETMAIL:
if ($this->validator->email($oneIncomingParam['VALUE']) === FALSE) {
continue 2;
}
break;
case API::AUXATTRIB_ADMINID:
try {
$oneIncomingParam['VALUE'] = $this->validator->string($oneIncomingParam['VALUE']);
} catch (Exception $e) {
continue 2;
}
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)) {
$parameters[$number] = $oneIncomingParam;
}
}
return $parameters;
}
/**
* extracts the first occurrence of a given parameter name from the set of inputs
*
* @param array $inputs incoming set of arrays
* @param string $expected attribute that is to be extracted
* @return mixed the value, or FALSE if none was found
*/
public function firstParameterInstance($inputs, $expected) {
foreach ($inputs as $attrib) {
if ($attrib['NAME'] == $expected) {
return $attrib['VALUE'];
}
}
return FALSE;
}
/**
* we are coercing the submitted JSON-style parameters into the same format
* we use for the HTML POST user-interactively.
* That's ugly, hence the function name.
*
* @param array $parameters the parameters as provided by JSON input
* @return array
* @throws Exception
*/
public function uglify($parameters) {
$coercedInline = [];
$coercedFile = [];
$optionObject = \core\Options::instance();
$dir = \core\common\Entity::createTemporaryDirectory('test');
foreach ($parameters as $number => $oneAttrib) {
if (preg_match("/^ATTRIB-/", $oneAttrib['NAME'])) {
continue;
}
$optionInfo = $optionObject->optionType($oneAttrib['NAME']);
$basename = "S$number";
$extension = "";
switch ($optionInfo['type']) {
case \core\Options::TYPECODE_COORDINATES:
$extension = \core\Options::TYPECODE_TEXT;
$coercedInline["option"][$basename] = $oneAttrib['NAME'] . "#";
$coercedInline["value"][$basename . "-" . $extension] = $oneAttrib['VALUE'];
break;
case \core\Options::TYPECODE_TEXT:
// Fall-through: they all get the same treatment.
case \core\Options::TYPECODE_BOOLEAN:
// Fall-through: they all get the same treatment.
case \core\Options::TYPECODE_STRING:
// Fall-through: they all get the same treatment.
case \core\Options::TYPECODE_INTEGER:
$extension = $optionInfo['type'];
$coercedInline["option"][$basename] = $oneAttrib['NAME'] . "#";
$coercedInline["value"][$basename . "-" . $extension] = $oneAttrib['VALUE'];
if ($optionInfo['flag'] == "ML") {
$coercedInline["value"][$basename . "-lang"] = $oneAttrib['LANG'];
}
break;
case \core\Options::TYPECODE_FILE:
// Binary data is expected in base64 encoding. This is true also for PEM files!
$extension = $optionInfo['type'];
$coercedInline["option"][$basename] = $oneAttrib['NAME'] . "#";
file_put_contents($dir['dir'] . "/" . $basename . "-" . $extension, base64_decode($oneAttrib['VALUE']));
$coercedFile["value"]['tmp_name'][$basename . "-" . $extension] = $dir['dir'] . "/" . $basename . "-" . $extension;
break;
default:
throw new Exception("We don't seem to know this type code!");
}
}
return ["POST" => $coercedInline, "FILES" => $coercedFile];
}
/**
* Returns a JSON construct detailing the error that happened
*
* @param int $code error code to return
* @param string $description textual description to return
* @return string
*/
public function returnError($code, $description) {
echo json_encode(["result" => "ERROR", "details" => ["errorcode" => $code, "description" => $description]], JSON_PRETTY_PRINT);
}
/**
* Returns a JSON construct with details of the successful API call
*
* @param array $details details to return with the SUCCESS
* @return string
*/
public function returnSuccess($details) {
$output = json_encode(["result" => "SUCCESS", "details" => $details], JSON_PRETTY_PRINT);
if ($output === FALSE) {
$this->returnError(API::ERROR_INTERNAL_ERROR, "Unable to JSON encode return data: ". json_last_error(). " - ". json_last_error_msg());
}
else {
echo $output;
}
}
/**
* Checks if the profile is a valid SB profile belonging to the federation,
* and fulfills all the prerequisites for being manipulated over API
*
* @param \core\Federation $fed federation identifier
* @param integer $id profile identifier
* @return boolean|array
*/
public function commonSbProfileChecks($fed, $id) {
$validator = new \web\lib\common\InputValidation();
$adminApi = new \web\lib\admin\API();
try {
$profile = $validator->existingProfile($id);
} catch (Exception $e) {
$adminApi->returnError(self::ERROR_INVALID_PARAMETER, "Profile identifier does not exist!");
return FALSE;
}
if (!$profile instanceof \core\ProfileSilverbullet) {
$adminApi->returnError(self::ERROR_INVALID_PARAMETER, sprintf("Profile identifier is not %s!", \core\ProfileSilverbullet::PRODUCTNAME));
return FALSE;
}
$idp = new \core\IdP($profile->institution);
if (strtoupper($idp->federation) != strtoupper($fed->tld)) {
$adminApi->returnError(self::ERROR_INVALID_PARAMETER, "Profile is not in the federation for this APIKEY!");
return FALSE;
}
if (count($profile->getAttributes("hiddenprofile:tou_accepted")) < 1) {
$adminApi->returnError(self::ERROR_NO_TOU, "The terms of use have not yet been accepted for this profile!");
return FALSE;
}
return [$idp, $profile];
}
public $loggerInstance;
}