Passed
Push — master ( 0adbbf...5c1c26 )
by Stefan
04:55
created

SilverbulletInvitation::createInvitation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nc 1
nop 3
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
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";
0 ignored issues
show
Bug introduced by
The property expiry does not seem to exist. Did you mean invitationTokenExpiry?

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.

Loading history...
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;
0 ignored issues
show
Bug introduced by
The property expiry does not seem to exist. Did you mean invitationTokenExpiry?

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.

Loading history...
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;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->activationsTotal - $certificatesNumber can also be of type double. However, the property $activationsRemaining is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
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) == '/') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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
0 ignored issues
show
Bug introduced by
There is no parameter named $invitationLink. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
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
0 ignored issues
show
Bug introduced by
There is no parameter named $invitationId. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
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);
0 ignored issues
show
Bug introduced by
The property databaseHandle does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
241
    }
242
243
}
244