|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/* |
|
4
|
|
|
* ****************************************************************************** |
|
5
|
|
|
* Copyright 2011-2017 DANTE Ltd. and GÉANT on behalf of the GN3, GN3+, GN4-1 |
|
6
|
|
|
* and GN4-2 consortia |
|
7
|
|
|
* |
|
8
|
|
|
* License: see the web/copyright.php file in the file structure |
|
9
|
|
|
* ****************************************************************************** |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
/** |
|
13
|
|
|
* This file contains the SilverbulletInvitation class. |
|
14
|
|
|
* |
|
15
|
|
|
* @author Stefan Winter <[email protected]> |
|
16
|
|
|
* @author Tomasz Wolniewicz <[email protected]> |
|
17
|
|
|
* |
|
18
|
|
|
* @package Developer |
|
19
|
|
|
* |
|
20
|
|
|
*/ |
|
21
|
|
|
|
|
22
|
|
|
namespace core; |
|
23
|
|
|
|
|
24
|
|
|
use \Exception; |
|
25
|
|
|
|
|
26
|
|
|
class SilverbulletInvitation extends common\Entity { |
|
27
|
|
|
|
|
28
|
|
|
/** |
|
29
|
|
|
* row ID in the database pertaining to this invitation. 0 on invalid invitations. |
|
30
|
|
|
* |
|
31
|
|
|
* @var int |
|
32
|
|
|
*/ |
|
33
|
|
|
public $identifier; |
|
34
|
|
|
|
|
35
|
|
|
/** |
|
36
|
|
|
* The profile this invitation belongs to. 0 on invalid invitations. |
|
37
|
|
|
* |
|
38
|
|
|
* @var int |
|
39
|
|
|
*/ |
|
40
|
|
|
public $profile; |
|
41
|
|
|
|
|
42
|
|
|
/** |
|
43
|
|
|
* The user this invitation was created for (integer DB ID). 0 on invalid invitations. |
|
44
|
|
|
* |
|
45
|
|
|
* @var int |
|
46
|
|
|
*/ |
|
47
|
|
|
public $userId; |
|
48
|
|
|
|
|
49
|
|
|
/** |
|
50
|
|
|
* |
|
51
|
|
|
* @var string |
|
52
|
|
|
*/ |
|
53
|
|
|
public $invitationTokenString; |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* |
|
57
|
|
|
* @var int |
|
58
|
|
|
*/ |
|
59
|
|
|
public $invitationTokenStatus; |
|
60
|
|
|
|
|
61
|
|
|
/** |
|
62
|
|
|
* Expiry timestamp of invitation token. 2000-01-01 00:00:00 on invalid invitations. |
|
63
|
|
|
* |
|
64
|
|
|
* @var string |
|
65
|
|
|
*/ |
|
66
|
|
|
public $invitationTokenExpiry; |
|
67
|
|
|
|
|
68
|
|
|
/** |
|
69
|
|
|
* How many devices were allowed to be activated in total? 0 on invalid invitations. |
|
70
|
|
|
* |
|
71
|
|
|
* @var int |
|
72
|
|
|
*/ |
|
73
|
|
|
public $activationsTotal; |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* How many devices have not yet been activated? 0 on invalid invitations. |
|
77
|
|
|
* |
|
78
|
|
|
* @var int |
|
79
|
|
|
*/ |
|
80
|
|
|
public $activationsRemaining; |
|
81
|
|
|
|
|
82
|
|
|
/** |
|
83
|
|
|
* |
|
84
|
|
|
* @var array |
|
85
|
|
|
*/ |
|
86
|
|
|
public $associatedCertificates; |
|
87
|
|
|
|
|
88
|
|
|
const SB_TOKENSTATUS_VALID = 0; |
|
89
|
|
|
const SB_TOKENSTATUS_PARTIALLY_REDEEMED = 1; |
|
90
|
|
|
const SB_TOKENSTATUS_REDEEMED = 2; |
|
91
|
|
|
const SB_TOKENSTATUS_EXPIRED = 3; |
|
92
|
|
|
const SB_TOKENSTATUS_INVALID = 4; |
|
93
|
|
|
|
|
94
|
|
|
public function __construct($invitationId) { |
|
95
|
|
|
parent::__construct(); |
|
96
|
|
|
$this->invitationTokenString = $invitationId; |
|
97
|
|
|
$databaseHandle = DBConnection::handle("INST"); |
|
98
|
|
|
/* |
|
99
|
|
|
* Finds invitation by its token attribute and loads all certificates generated using the token. |
|
100
|
|
|
* Certificate details will always be empty, since code still needs to be adapted to return multiple certificates information. |
|
101
|
|
|
*/ |
|
102
|
|
|
$invColumnNames = "`id`, `profile_id`, `silverbullet_user_id`, `token`, `quantity`, `expiry`"; |
|
103
|
|
|
$invitationsResult = $databaseHandle->exec("SELECT $invColumnNames FROM `silverbullet_invitation` WHERE `token`=? ORDER BY `expiry` DESC", "s", $this->invitationTokenString); |
|
104
|
|
|
if ($invitationsResult->num_rows == 0) { |
|
105
|
|
|
$this->loggerInstance->debug(2, "Token $this->invitationTokenString not found in database or database query error!\n"); |
|
106
|
|
|
$this->invitationTokenStatus = SilverbulletInvitation::SB_TOKENSTATUS_INVALID; |
|
107
|
|
|
$this->associatedCertificates = []; |
|
108
|
|
|
$this->identifier = 0; |
|
109
|
|
|
$this->profile = 0; |
|
110
|
|
|
$this->userId = 0; |
|
111
|
|
|
$this->expiry = "2000-01-01 00:00:00"; |
|
|
|
|
|
|
112
|
|
|
$this->activationsTotal = 0; |
|
113
|
|
|
$this->activationsRemaining = 0; |
|
114
|
|
|
} else { |
|
115
|
|
|
// if not returned, we found the token in the DB |
|
116
|
|
|
// -> instantiate the class |
|
117
|
|
|
// SELECT -> resource, no boolean |
|
118
|
|
|
$invitationRow = mysqli_fetch_object(/** @scrutinizer ignore-type */ $invitationsResult); |
|
119
|
|
|
$this->identifier = $invitationRow->id; |
|
120
|
|
|
$this->profile = $invitationRow->profile_id; |
|
121
|
|
|
$this->userId = $invitationRow->silverbullet_user_id; |
|
122
|
|
|
$this->expiry = $invitationRow->expiry; |
|
|
|
|
|
|
123
|
|
|
$this->activationsTotal = $invitationRow->quantity; |
|
124
|
|
|
$certColumnNames = "`id`, `profile_id`, `silverbullet_user_id`, `silverbullet_invitation_id`, `serial_number`, `cn`, `issued`, `expiry`, `device`, `revocation_status`, `revocation_time`, `OCSP`, `OCSP_timestamp`"; |
|
125
|
|
|
$certificatesResult = $databaseHandle->exec("SELECT $certColumnNames FROM `silverbullet_certificate` WHERE `silverbullet_invitation_id` = ? ORDER BY `revocation_status`, `expiry` DESC", "i", $this->identifier); |
|
126
|
|
|
$certificatesNumber = ($certificatesResult ? $certificatesResult->num_rows : 0); |
|
127
|
|
|
$this->loggerInstance->debug(5, "At token validation level, " . $certificatesNumber . " certificates exist.\n"); |
|
128
|
|
|
$this->associatedCertificates = \core\ProfileSilverbullet::enumerateCertDetails(/** @scrutinizer ignore-type */ $certificatesResult); |
|
129
|
|
|
$this->activationsRemaining = $this->activationsTotal - $certificatesNumber; |
|
|
|
|
|
|
130
|
|
|
switch ($certificatesNumber) { |
|
131
|
|
|
case 0: |
|
132
|
|
|
// find out if it has expired |
|
133
|
|
|
$now = new \DateTime(); |
|
134
|
|
|
$expiryObject = new \DateTime($this->invitationTokenExpiry); |
|
135
|
|
|
$delta = $now->diff($expiryObject); |
|
136
|
|
|
if ($delta->invert == 1) { |
|
137
|
|
|
$this->invitationTokenStatus = SilverbulletInvitation::SB_TOKENSTATUS_EXPIRED; |
|
138
|
|
|
$this->activationsRemaining = 0; |
|
139
|
|
|
break; |
|
140
|
|
|
} |
|
141
|
|
|
$this->invitationTokenStatus = SilverbulletInvitation::SB_TOKENSTATUS_VALID; |
|
142
|
|
|
break; |
|
143
|
|
|
case $invitationRow->quantity: |
|
144
|
|
|
$this->invitationTokenStatus = SilverbulletInvitation::SB_TOKENSTATUS_REDEEMED; |
|
145
|
|
|
break; |
|
146
|
|
|
default: |
|
147
|
|
|
assert($certificatesNumber > 0); // no negatives allowed |
|
148
|
|
|
assert($certificatesNumber < $invitationRow->quantity || $invitationRow->quantity == 0); // not more than max quantity allowed (unless quantity is zero) |
|
149
|
|
|
$this->invitationTokenStatus = SilverbulletInvitation::SB_TOKENSTATUS_PARTIALLY_REDEEMED; |
|
150
|
|
|
} |
|
151
|
|
|
} |
|
152
|
|
|
$this->loggerInstance->debug(5, "Done creating invitation token state from DB.\n"); |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
public function link() { |
|
156
|
|
|
if (isset($_SERVER['HTTPS'])) { |
|
157
|
|
|
$link = 'https://'; |
|
158
|
|
|
} else { |
|
159
|
|
|
$link = 'http://'; |
|
160
|
|
|
} |
|
161
|
|
|
$link .= $_SERVER['SERVER_NAME']; |
|
162
|
|
|
$relPath = dirname(dirname($_SERVER['SCRIPT_NAME'])); |
|
163
|
|
View Code Duplication |
if (substr($relPath, -1) == '/') { |
|
|
|
|
|
|
164
|
|
|
$relPath = substr($relPath, 0, -1); |
|
165
|
|
|
if ($relPath === FALSE) { |
|
166
|
|
|
throw new Exception("Uh. Something went seriously wrong with URL path mangling."); |
|
167
|
|
|
} |
|
168
|
|
|
} |
|
169
|
|
|
$link = $link . $relPath; |
|
170
|
|
|
|
|
171
|
|
View Code Duplication |
if (preg_match('/admin$/', $link)) { |
|
|
|
|
|
|
172
|
|
|
$link = substr($link, 0, -6); |
|
173
|
|
|
if ($link === FALSE) { |
|
174
|
|
|
throw new Exception("Impossible: the string ends with '/admin' but it's not possible to cut six characters from the end?!"); |
|
175
|
|
|
} |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
return $link . '/accountstatus/accountstatus.php?token=' . $this->invitationTokenString; |
|
179
|
|
|
} |
|
180
|
|
|
|
|
181
|
|
|
/** |
|
182
|
|
|
* returns the subject to use in an invitation mail |
|
183
|
|
|
* @return string |
|
184
|
|
|
*/ |
|
185
|
|
|
public function invitationMailSubject() { |
|
186
|
|
|
return sprintf(_("Your %s access is ready"), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']); |
|
187
|
|
|
} |
|
188
|
|
|
|
|
189
|
|
|
/** |
|
190
|
|
|
* returns the body to use in an invitation mail |
|
191
|
|
|
* @param string $invitationLink the activation token link to embed |
|
|
|
|
|
|
192
|
|
|
* @return string |
|
193
|
|
|
*/ |
|
194
|
|
|
public function invitationMailBody() { |
|
195
|
|
|
$text = _("Hello!"); |
|
196
|
|
|
$text .= "\n\n"; |
|
197
|
|
|
$text .= sprintf(_("A new %s access credential has been created for you by your network administrator."), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']); |
|
198
|
|
|
$text .= " "; |
|
199
|
|
|
$text .= sprintf(_("Please follow the following link with the device you want to enable for %s to get a custom %s installation program just for you. You can click on the link, copy and paste it into a browser or scan the attached QR code."), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name'], CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']); |
|
200
|
|
|
$text .= "\n\n" . $this->link() . "\n\n"; // gets replaced with the token value by getBody() |
|
201
|
|
|
$text .= sprintf(_("Please keep this email or bookmark this link for future use. After picking up your %s installation program, you can use the same link to get status information about your %s account."), CONFIG_CONFASSISTANT['CONSORTIUM']['display_name'], CONFIG_CONFASSISTANT['CONSORTIUM']['display_name']); |
|
202
|
|
|
$text .= "\n\n"; |
|
203
|
|
|
$text .= _("Regards,"); |
|
204
|
|
|
$text .= "\n\n"; |
|
205
|
|
|
$text .= sprintf("%s", CONFIG['APPEARANCE']['productname_long']); |
|
206
|
|
|
|
|
207
|
|
|
return $text; |
|
208
|
|
|
} |
|
209
|
|
|
|
|
210
|
|
|
/** |
|
211
|
|
|
* generates a new hex string to be used as an activation token |
|
212
|
|
|
* |
|
213
|
|
|
* @return string |
|
214
|
|
|
*/ |
|
215
|
|
|
private static function generateInvitation() { |
|
216
|
|
|
return hash("sha512", base_convert(rand(0, (int) 10e16), 10, 36)); |
|
217
|
|
|
} |
|
218
|
|
|
|
|
219
|
|
|
/** |
|
220
|
|
|
* creates a new invitation in the database |
|
221
|
|
|
* @param int $profileId |
|
222
|
|
|
* @param int $userId |
|
223
|
|
|
* @param int $activationCount |
|
224
|
|
|
*/ |
|
225
|
|
|
public static function createInvitation($profileId, $userId, $activationCount) { |
|
226
|
|
|
$handle = DBConnection::handle("INST"); |
|
227
|
|
|
$query = "INSERT INTO silverbullet_invitation (profile_id, silverbullet_user_id, token, quantity, expiry) VALUES (?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 7 DAY))"; |
|
228
|
|
|
$newToken = SilverbulletInvitation::generateInvitation(); |
|
229
|
|
|
$handle->exec($query, "iisi", $profileId, $userId, $newToken, $activationCount); |
|
230
|
|
|
return new SilverbulletInvitation($newToken); |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
/** |
|
234
|
|
|
* revokes an invitation |
|
235
|
|
|
* |
|
236
|
|
|
* @param int $invitationId |
|
|
|
|
|
|
237
|
|
|
*/ |
|
238
|
|
|
public function revokeInvitation() { |
|
239
|
|
|
$query = "UPDATE silverbullet_invitation SET expiry = NOW() WHERE id = ? AND profile_id = ?"; |
|
240
|
|
|
$this->databaseHandle->exec($query, "ii", $this->invitationTokenString, $this->identifier); |
|
|
|
|
|
|
241
|
|
|
} |
|
242
|
|
|
|
|
243
|
|
|
} |
|
244
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.