69 public function issue($user, $toolName, array $args, $preview)
75 $this->error =
'No instance secret available to sign the confirmation';
80 $state =
dol_hash($secret.
'|'.$toolName.
'|'.$user->id.
'|'.
dol_now().
'|'.microtime(
true).
'|'.mt_rand(),
'sha256');
81 $ttl =
getDolGlobalInt(
'AI_WRITE_CONFIRMATION_TTL_MINUTES', self::DEFAULT_TTL_MINUTES);
86 $sql =
"INSERT INTO ".MAIN_DB_PREFIX.
"ai_write_confirmation";
87 $sql .=
" (entity, state_hash, fk_user, tool_name, args_hash, preview, date_creation, date_expiration, ip)";
88 $sql .=
" VALUES (".((int)
$conf->entity);
89 $sql .=
", '".$this->db->escape($this->
stateHash($state, $secret)).
"'";
90 $sql .=
", ".((int) $user->id);
91 $sql .=
", '".$this->db->escape($toolName).
"'";
92 $sql .=
", '".$this->db->escape($this->
argsHash($args)).
"'";
93 $sql .=
", '".$this->db->escape(
dol_trunc((
string) $preview, 60000,
'right',
'UTF-8', 1)).
"'";
94 $sql .=
", '".$this->db->idate(
dol_now()).
"'";
95 $sql .=
", '".$this->db->idate(
dol_now() + ($ttl * 60)).
"'";
99 if (!$this->db->query($sql)) {
100 $this->error =
'Cannot store the pending confirmation';
101 dol_syslog(
'[AiWriteConfirmation] '.$this->error.
': '.$this->db->lasterror(), LOG_ERR);
108 if (mt_rand(1, 100) === 1) {
128 public function consume($user, $toolName, array $args, $state)
134 if ($secret ===
'' || !is_string($state) || $state ===
'') {
135 $this->error =
'Invalid confirmation';
140 $sql =
"SELECT rowid, fk_user, tool_name, args_hash, date_expiration, date_consumed";
141 $sql .=
" FROM ".MAIN_DB_PREFIX.
"ai_write_confirmation";
142 $sql .=
" WHERE state_hash = '".$this->db->escape($this->
stateHash($state, $secret)).
"'";
143 $sql .=
" AND entity = ".((int)
$conf->entity);
145 $resql = $this->db->query($sql);
146 if (!$resql || !($obj = $this->db->fetch_object($resql))) {
147 $this->error =
'Unknown or forged confirmation';
148 dol_syslog(
'[AiWriteConfirmation] rejected: no state matches', LOG_NOTICE);
153 if ((
int) $obj->fk_user !== (
int) $user->id) {
154 $this->error =
'This confirmation was issued to another user';
155 } elseif ((
string) $obj->tool_name !== (
string) $toolName) {
156 $this->error =
'This confirmation was issued for another action';
157 } elseif ((
string) $obj->args_hash !== $this->argsHash($args)) {
158 $this->error =
'The action changed since it was confirmed';
159 } elseif (!empty($obj->date_consumed)) {
160 $this->error =
'This confirmation was already used';
161 } elseif ($this->db->jdate($obj->date_expiration) <
dol_now()) {
162 $this->error =
'This confirmation expired, ask again';
165 if ($this->error !==
'') {
166 dol_syslog(
'[AiWriteConfirmation] rejected for '.$toolName.
': '.$this->error, LOG_NOTICE);
173 $sql =
"UPDATE ".MAIN_DB_PREFIX.
"ai_write_confirmation";
174 $sql .=
" SET date_consumed = '".$this->db->idate(
dol_now()).
"'";
175 $sql .=
" WHERE rowid = ".((int) $obj->rowid).
" AND date_consumed IS NULL";
177 $resupdate = $this->db->query($sql);
180 if (!$resupdate || $this->db->affected_rows($resupdate) === 0) {
181 $this->error =
'This confirmation was already used';
182 dol_syslog(
'[AiWriteConfirmation] rejected for '.$toolName.
': concurrent use', LOG_NOTICE);
196 public function purge($keepdays = 30)
198 $sql =
"DELETE FROM ".MAIN_DB_PREFIX.
"ai_write_confirmation";
199 $sql .=
" WHERE date_expiration < '".$this->db->idate(
dol_now() - ((
int) $keepdays * 86400)).
"'";
201 $resql = $this->db->query($sql);
206 return (
int) $this->db->affected_rows($resql);
221 global $dolibarr_main_instance_unique_id, $dolibarr_main_cookie_cryptkey;
223 $instanceid = empty($dolibarr_main_instance_unique_id) ? (empty($dolibarr_main_cookie_cryptkey) ?
'' : $dolibarr_main_cookie_cryptkey) : $dolibarr_main_instance_unique_id;
224 if (empty($instanceid)) {
228 return dol_hash(
'ai-write-confirmation'.$instanceid,
'sha256');
240 return dol_hash($secret.
'|'.$state,
'sha256');
252 unset($normalized[
'requestState']);
255 return dol_hash((
string) json_encode($normalized),
'sha256');
267 foreach ($arr as &$value) {
268 if (is_array($value)) {
Class AiWriteConfirmation.
ksortRecursive(array &$arr)
Sort an array by key at every level, so argument order cannot change the hash.
argsHash(array $args)
Stable hash of the arguments a state was issued for.
stateHash($state, $secret)
Keyed hash of a state, so the database never holds the state itself.
signingSecret()
Secret used to sign states.
__construct($db)
Constructor.
consume($user, $toolName, array $args, $state)
Validate a state presented on the confirming call, and consume it.
issue($user, $toolName, array $args, $preview)
Issue a state for a pending write.
const DEFAULT_TTL_MINUTES
Minutes a state stays valid, long enough to read a preview.
purge($keepdays=30)
Remove expired states that were never confirmed.
dol_now($mode='gmt')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getUserRemoteIP($trusted=0)
Return the real IP of remote user.
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_hash($chain, $type='0', $nosalt=0, $mode=0)
Returns a hash (non reversible encryption) of a string.