Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added logging of request and response during SPID login using mySql or SqlServer #142

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion example/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,16 @@
'sp_attributeconsumingservice' => [
["name", "familyName", "fiscalNumber", "email"],
["name", "familyName", "fiscalNumber", "email", "spidCode"]
]
],
/*'database' => [
'type' => 'mysql', // sqlserver, mysql
'host' => 'localhost', // database host
'instance' => '', // database instance
'name' => 'databaseName', // database name
'table_name' => 'SPID_LOGS', // table name for logging
'user' => 'username', // username
'password' => 'password' // password
]*/
];
$sp = new Italia\Spid\Sp($settings);

Expand Down
135 changes: 135 additions & 0 deletions src/Db.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

namespace Italia\Spid;

use Italia\Spid\Spid\Saml\Idp;
use Italia\Spid\Spid\Saml\Out\AuthnRequest;
use Italia\Spid\Spid\Saml\In\BaseResponse;
use PDO;

class Db {

public $idp;
private $dbType;
private $dbHost;
private $dbInstance;
private $dbName;
private $tableName;
private $dbUser;
private $dbPassword;
private $conn;

public function __construct(Idp $idp)
{
$this->idp = $idp;
$this->dbType = $this->idp->sp->settings['database']['type'];
$this->dbHost = $this->idp->sp->settings['database']['host'];
$this->dbInstance = $this->idp->sp->settings['database']['instance'];
$this->dbName = $this->idp->sp->settings['database']['name'];
$this->tableName = $this->idp->sp->settings['database']['table_name'];
$this->dbUser = $this->idp->sp->settings['database']['user'];
$this->dbPassword = $this->idp->sp->settings['database']['password'];
$this->createTableIfNotExist();
}

private function createConn() {
if($this->dbType == 'mysql') {
try {
$conn = new PDO("mysql:host=".$this->dbHost.";dbname=".$this->dbName, $this->dbUser, $this->dbPassword);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch(PDOException $e) {
$conn = null;
throw new \Exception($e->getMessage());
}
}
if($this->dbType == 'sqlserver') {
$serverName = $this->dbHost;
if($this->dbInstance) {
$serverName = $serverName."\\".$this->dbInstance;
}
$connectionInfo = array("Database"=>$this->dbName, "UID"=>$this->dbUser, "PWD"=>$this->dbPassword);
$conn = sqlsrv_connect($serverName, $connectionInfo);
}
return $conn;
}

private function createTableIfNotExist() {
$conn = $this->createConn();

if($this->dbType == 'mysql') {
$sql = 'CREATE TABLE IF NOT EXISTS `' . $this->tableName . '` ( `ID` int NOT NULL AUTO_INCREMENT, `AUTHNREQUEST` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, `RESPONSE` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `AUTHNREQ_ID` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `AUTHNREQ_ISSUEINSTANT` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `RESP_ID` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `RESP_ISSUEINSTANT` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `RESP_ISSUER` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `ASSERTION_ID` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `ASSERTION_SUBJECT` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `ASSERTION_SUBJECT_NAMEQUALIFIER` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, PRIMARY KEY (`ID`)) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;';
$conn->exec($sql);
}

if($this->dbType == 'sqlserver') {
$sql = 'IF NOT EXISTS (SELECT [name] FROM sys.tables where [name] = \'' . $this->tableName . '\') CREATE TABLE ' . $this->tableName . ' ( [ID] [bigint] IDENTITY(1,1) NOT NULL, [AUTHNREQUEST] [nvarchar](max) NOT NULL, [RESPONSE] [nvarchar](max) NULL, [AUTHNREQ_ID] [nvarchar](max) NULL, [AUTHNREQ_ISSUEINSTANT] [nvarchar](max) NULL, [RESP_ID] [nvarchar](max) NULL, [RESP_ISSUEINSTANT] [nvarchar](max) NULL, [RESP_ISSUER] [nvarchar](max) NULL, [ASSERTION_ID] [nvarchar](max) NULL, [ASSERTION_SUBJECT] [nvarchar](max) NULL, [ASSERTION_SUBJECT_NAMEQUALIFIER] [nvarchar](max) NULL) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]';
$result = sqlsrv_query($conn, $sql);
}
$this->closeConn($conn);
}

public function insertAuthnDataIntoLog(AuthnRequest $authnReq) {
$conn = $this->createConn();
$authnReqXML = $authnReq->xml;
$authnReqID = $authnReq->id;
$authnReqIssueIstant = $authnReq->issueInstant;
$assertionID = $this->idp->assertID;
if($this->dbType == 'mysql') {
$sql = "INSERT INTO " . $this->tableName . " (AUTHNREQUEST, AUTHNREQ_ID, AUTHNREQ_ISSUEINSTANT, ASSERTION_ID) VALUES ('".$authnReqXML."', '".$authnReqID."', '".$authnReqIssueIstant."', ".$assertionID.")";
$conn->exec($sql);
$_SESSION['LogID'] = $conn->lastInsertId();
}
if($this->dbType == 'sqlserver') {
$query = "INSERT INTO " . $this->tableName . " (AUTHNREQUEST, AUTHNREQ_ID, AUTHNREQ_ISSUEINSTANT, ASSERTION_ID) VALUES (?, ?, ?, ?); SELECT SCOPE_IDENTITY()";
$params = array($authnReqXML, $authnReqID, $authnReqIssueIstant, $assertionID);
$result = sqlsrv_query($conn, $query, $params);
sqlsrv_next_result($result);
sqlsrv_fetch($result);
$_SESSION['LogID'] = sqlsrv_get_field($result, 0);
}
$this->closeConn($conn);
}

public function updateLogWithResponseData(BaseResponse $response) {
if(isset($_SESSION['LogID'])) {
$conn = $this->createConn();
$logID = $_SESSION['LogID'];
$responseXML = $response->getXml();
if($responseXML) {
$responseXMLString = simplexml_import_dom($responseXML)->asXML();
$responseID = $responseXML->getAttribute('ID');
$responseIssueInstant = $responseXML->getAttribute('IssueInstant');
$responseIssuer = $responseXML->getElementsByTagName('Issuer')->item(0)->nodeValue;
$assertionSubject = simplexml_import_dom($responseXML->getElementsByTagName('Assertion')->item(0)->getElementsByTagName('Subject')->item(0))->asXML();
$assertionSubjectNameQualifier = $responseXML->getElementsByTagName('Assertion')->item(0)->getElementsByTagName('Subject')->item(0)->getElementsByTagName('NameID')->item(0)->getAttribute('NameQualifier');
if($this->dbType == 'mysql') {
$sql = "UPDATE " . $this->tableName . " SET RESPONSE = '".$responseXMLString."', RESP_ID = '".$responseID."', RESP_ISSUEINSTANT = '".$responseIssueInstant."', RESP_ISSUER = '".$responseIssuer."', ASSERTION_SUBJECT = '".$assertionSubject."', ASSERTION_SUBJECT_NAMEQUALIFIER = '".$assertionSubjectNameQualifier."' WHERE ID = ".$logID."";
$conn->exec($sql);
}
if($this->dbType == 'sqlserver') {
$query = "UPDATE " . $this->tableName . " SET RESPONSE = ?, RESP_ID = ?, RESP_ISSUEINSTANT = ?, RESP_ISSUER = ?, ASSERTION_SUBJECT = ?, ASSERTION_SUBJECT_NAMEQUALIFIER = ? WHERE ID = ?";
$params = array($responseXMLString, $responseID, $responseIssueInstant, $responseIssuer, $assertionSubject, $assertionSubjectNameQualifier, $logID);
$result = sqlsrv_query($conn, $query, $params);
}
}
unset($_SESSION['LogID']);
$this->closeConn($conn);
}
}

private function closeConn(&$conn) {
if($this->dbType == 'mysql') {
if($conn) {
$conn = null;
}
}
if($this->dbType == 'sqlserver') {
if($conn) {
sqlsrv_close($conn);
}
}
}

}

?>
5 changes: 5 additions & 0 deletions src/Spid/Saml.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Italia\Spid\Spid\Saml\SignatureUtils;
use Italia\Spid\Spid\Interfaces\SAMLInterface;
use Italia\Spid\Spid\Session;
use Italia\Spid\Db;

class Saml implements SAMLInterface
{
Expand Down Expand Up @@ -219,6 +220,10 @@ public function isAuthenticated() : bool
$session = new Session($_SESSION['spidSession']);
if ($session->isValid()) {
$this->session = $session;
if (isset($this->settings['database'])) {
$db = new Db($idp);
SeemoneB marked this conversation as resolved.
Show resolved Hide resolved
$db->updateLogWithResponseData($response);
}
return true;
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/Spid/Saml/Idp.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Italia\Spid\Spid\Saml\Out\LogoutRequest;
use Italia\Spid\Spid\Session;
use Italia\Spid\Spid\Saml\Out\LogoutResponse;
use Italia\Spid\Db;

class Idp implements IdpInterface
{
Expand Down Expand Up @@ -96,6 +97,11 @@ public function authnRequest($ass, $attr, $binding, $level = 1, $redirectTo = nu
$_SESSION['idpEntityId'] = $this->metadata['idpEntityId'];
$_SESSION['acsUrl'] = $this->sp->settings['sp_assertionconsumerservice'][$ass];

if (isset($this->sp->settings['database'])) {
$db = new Db($this);
$db->insertAuthnDataIntoLog($authn);
}

if (!$shouldRedirect || $binding == Settings::BINDING_POST) {
return $url;
}
Expand Down
8 changes: 8 additions & 0 deletions src/Spid/Saml/In/BaseResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,12 @@ public function validate($cert) : bool
}
return $this->response->validate($this->xml, $hasAssertion);
}

public function getXml() {
if($this->xml) {
return $this->xml->getElementsByTagName('Response')->item(0);
} else {
return '';
}
}
}
51 changes: 50 additions & 1 deletion src/Spid/Saml/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,18 @@ class Settings
]
],
'idp_metadata_folder' => self::REQUIRED,
'accepted_clock_skew_seconds' => self::NOT_REQUIRED
'accepted_clock_skew_seconds' => self::NOT_REQUIRED,
'database' => [
self::NOT_REQUIRED => [
'type' => self::REQUIRED,
'host' => self::REQUIRED,
'instance' => self::NOT_REQUIRED,
'name' => self::REQUIRED,
'table_name' => self::REQUIRED,
'user' => self::REQUIRED,
'password' => self::REQUIRED,
]
]
];

private static $validAttributeFields = [
Expand Down Expand Up @@ -246,5 +257,43 @@ private static function checkSettingsValues($settings)
'"exact", "minimum", "better" or "maximum"');
}
}

if (isset($settings['database'])) {
if (!is_array($settings['database'])) {
throw new \Exception('database should be an array');
}
foreach ($settings['database'] as $key => $value) {
if (!is_string($value)) {
throw new \Exception(
'database values should be strings. Valued provided for key ' . $key .
' is not a string'
);
}
}
if (isset($settings['database']['type'])) {
if (strcasecmp($settings['database']['type'], "mysql") != 0 &&
strcasecmp($settings['database']['type'], "sqlserver") != 0) {
throw new \InvalidArgumentException('type value should be one of:' .
'"mysql", "sqlserver"');
}
if (!isset($settings['database']['host']) || empty($settings['database']['host'])) {
throw new \Exception('Missing settings field: host');
}
if (!isset($settings['database']['name']) || empty($settings['database']['name'])) {
throw new \Exception('Missing settings field: name');
}
if (!isset($settings['database']['table_name']) || empty($settings['database']['table_name'])) {
throw new \Exception('Missing settings field: table_name');
}
if (!isset($settings['database']['user']) || empty($settings['database']['user'])) {
throw new \Exception('Missing settings field: user');
}
if (!isset($settings['database']['password']) || empty($settings['database']['password'])) {
throw new \Exception('Missing settings field: password');
}
} else {
throw new \Exception('Missing settings field: type');
}
}
}
}