Completed
Pull Request — newinternal (#547)
by Simon
05:45
created
includes/Exceptions/EnvironmentException.php 1 patch
Indentation   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -21,13 +21,13 @@
 block discarded – undo
21 21
  */
22 22
 class EnvironmentException extends Exception
23 23
 {
24
-    /**
25
-     * EnvironmentException constructor.
26
-     *
27
-     * @param string $friendlyMessage
28
-     */
29
-    public function __construct($friendlyMessage)
30
-    {
31
-        parent::__construct($friendlyMessage);
32
-    }
24
+	/**
25
+	 * EnvironmentException constructor.
26
+	 *
27
+	 * @param string $friendlyMessage
28
+	 */
29
+	public function __construct($friendlyMessage)
30
+	{
31
+		parent::__construct($friendlyMessage);
32
+	}
33 33
 }
34 34
\ No newline at end of file
Please login to merge, or discard this patch.
includes/PdoDatabase.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -50,7 +50,7 @@
 block discarded – undo
50 50
             }
51 51
             catch (PDOException $ex) {
52 52
                 // wrap around any potential stack traces which may include passwords
53
-                throw new EnvironmentException("Error connecting to database '$connectionName': " . $ex->getMessage());
53
+                throw new EnvironmentException("Error connecting to database '$connectionName': ".$ex->getMessage());
54 54
             }
55 55
 
56 56
             $databaseObject->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Please login to merge, or discard this patch.
Indentation   +109 added lines, -109 removed lines patch added patch discarded remove patch
@@ -15,113 +15,113 @@
 block discarded – undo
15 15
 
16 16
 class PdoDatabase extends PDO
17 17
 {
18
-    /**
19
-     * @var PdoDatabase[]
20
-     */
21
-    private static $connections = array();
22
-    /**
23
-     * @var bool True if a transaction is active
24
-     */
25
-    protected $hasActiveTransaction = false;
26
-
27
-    /**
28
-     * Unless you're doing low-level work, this is not the function you want.
29
-     *
30
-     * @param string $connectionName
31
-     *
32
-     * @return PdoDatabase
33
-     * @throws Exception
34
-     */
35
-    public static function getDatabaseConnection($connectionName)
36
-    {
37
-        if (!isset(self::$connections[$connectionName])) {
38
-            global $cDatabaseConfig;
39
-
40
-            if (!array_key_exists($connectionName, $cDatabaseConfig)) {
41
-                throw new Exception("Database configuration not found for alias $connectionName");
42
-            }
43
-
44
-            try {
45
-                $databaseObject = new PdoDatabase(
46
-                    $cDatabaseConfig[$connectionName]["dsrcname"],
47
-                    $cDatabaseConfig[$connectionName]["username"],
48
-                    $cDatabaseConfig[$connectionName]["password"],
49
-                    $cDatabaseConfig[$connectionName]["options"]
50
-                );
51
-            }
52
-            catch (PDOException $ex) {
53
-                // wrap around any potential stack traces which may include passwords
54
-                throw new EnvironmentException("Error connecting to database '$connectionName': " . $ex->getMessage());
55
-            }
56
-
57
-            $databaseObject->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
58
-
59
-            // emulating prepared statements gives a performance boost on MySQL.
60
-            //
61
-            // however, our version of PDO doesn't seem to understand parameter types when emulating
62
-            // the prepared statements, so we're forced to turn this off for now.
63
-            // -- stw 2014-02-11
64
-            $databaseObject->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
65
-
66
-            self::$connections[$connectionName] = $databaseObject;
67
-        }
68
-
69
-        return self::$connections[$connectionName];
70
-    }
71
-
72
-    /**
73
-     * Determines if this connection has a transaction in progress or not
74
-     * @return boolean true if there is a transaction in progress.
75
-     */
76
-    public function hasActiveTransaction()
77
-    {
78
-        return $this->hasActiveTransaction;
79
-    }
80
-
81
-    /**
82
-     * Summary of beginTransaction
83
-     * @return bool
84
-     */
85
-    public function beginTransaction()
86
-    {
87
-        // Override the pre-existing method, which doesn't stop you from
88
-        // starting transactions within transactions - which doesn't work and
89
-        // will throw an exception. This eliminates the need to catch exceptions
90
-        // all over the rest of the code
91
-        if ($this->hasActiveTransaction) {
92
-            return false;
93
-        }
94
-        else {
95
-            // set the transaction isolation level for every transaction.
96
-            $this->exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;");
97
-
98
-            // start a new transaction, and return whether or not the start was
99
-            // successful
100
-            $this->hasActiveTransaction = parent::beginTransaction();
101
-
102
-            return $this->hasActiveTransaction;
103
-        }
104
-    }
105
-
106
-    /**
107
-     * Commits the active transaction
108
-     */
109
-    public function commit()
110
-    {
111
-        if ($this->hasActiveTransaction) {
112
-            parent::commit();
113
-            $this->hasActiveTransaction = false;
114
-        }
115
-    }
116
-
117
-    /**
118
-     * Rolls back a transaction
119
-     */
120
-    public function rollBack()
121
-    {
122
-        if ($this->hasActiveTransaction) {
123
-            parent::rollback();
124
-            $this->hasActiveTransaction = false;
125
-        }
126
-    }
18
+	/**
19
+	 * @var PdoDatabase[]
20
+	 */
21
+	private static $connections = array();
22
+	/**
23
+	 * @var bool True if a transaction is active
24
+	 */
25
+	protected $hasActiveTransaction = false;
26
+
27
+	/**
28
+	 * Unless you're doing low-level work, this is not the function you want.
29
+	 *
30
+	 * @param string $connectionName
31
+	 *
32
+	 * @return PdoDatabase
33
+	 * @throws Exception
34
+	 */
35
+	public static function getDatabaseConnection($connectionName)
36
+	{
37
+		if (!isset(self::$connections[$connectionName])) {
38
+			global $cDatabaseConfig;
39
+
40
+			if (!array_key_exists($connectionName, $cDatabaseConfig)) {
41
+				throw new Exception("Database configuration not found for alias $connectionName");
42
+			}
43
+
44
+			try {
45
+				$databaseObject = new PdoDatabase(
46
+					$cDatabaseConfig[$connectionName]["dsrcname"],
47
+					$cDatabaseConfig[$connectionName]["username"],
48
+					$cDatabaseConfig[$connectionName]["password"],
49
+					$cDatabaseConfig[$connectionName]["options"]
50
+				);
51
+			}
52
+			catch (PDOException $ex) {
53
+				// wrap around any potential stack traces which may include passwords
54
+				throw new EnvironmentException("Error connecting to database '$connectionName': " . $ex->getMessage());
55
+			}
56
+
57
+			$databaseObject->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
58
+
59
+			// emulating prepared statements gives a performance boost on MySQL.
60
+			//
61
+			// however, our version of PDO doesn't seem to understand parameter types when emulating
62
+			// the prepared statements, so we're forced to turn this off for now.
63
+			// -- stw 2014-02-11
64
+			$databaseObject->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
65
+
66
+			self::$connections[$connectionName] = $databaseObject;
67
+		}
68
+
69
+		return self::$connections[$connectionName];
70
+	}
71
+
72
+	/**
73
+	 * Determines if this connection has a transaction in progress or not
74
+	 * @return boolean true if there is a transaction in progress.
75
+	 */
76
+	public function hasActiveTransaction()
77
+	{
78
+		return $this->hasActiveTransaction;
79
+	}
80
+
81
+	/**
82
+	 * Summary of beginTransaction
83
+	 * @return bool
84
+	 */
85
+	public function beginTransaction()
86
+	{
87
+		// Override the pre-existing method, which doesn't stop you from
88
+		// starting transactions within transactions - which doesn't work and
89
+		// will throw an exception. This eliminates the need to catch exceptions
90
+		// all over the rest of the code
91
+		if ($this->hasActiveTransaction) {
92
+			return false;
93
+		}
94
+		else {
95
+			// set the transaction isolation level for every transaction.
96
+			$this->exec("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;");
97
+
98
+			// start a new transaction, and return whether or not the start was
99
+			// successful
100
+			$this->hasActiveTransaction = parent::beginTransaction();
101
+
102
+			return $this->hasActiveTransaction;
103
+		}
104
+	}
105
+
106
+	/**
107
+	 * Commits the active transaction
108
+	 */
109
+	public function commit()
110
+	{
111
+		if ($this->hasActiveTransaction) {
112
+			parent::commit();
113
+			$this->hasActiveTransaction = false;
114
+		}
115
+	}
116
+
117
+	/**
118
+	 * Rolls back a transaction
119
+	 */
120
+	public function rollBack()
121
+	{
122
+		if ($this->hasActiveTransaction) {
123
+			parent::rollback();
124
+			$this->hasActiveTransaction = false;
125
+		}
126
+	}
127 127
 }
Please login to merge, or discard this patch.
includes/ExceptionHandler.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -58,7 +58,7 @@
 block discarded – undo
58 58
         $errorId = sha1($state);
59 59
 
60 60
         // Save the error for later analysis
61
-        file_put_contents($siteConfiguration->getErrorLog() . '/' . $errorId . '.log', $state);
61
+        file_put_contents($siteConfiguration->getErrorLog().'/'.$errorId.'.log', $state);
62 62
 
63 63
         // clear and discard any content that's been saved to the output buffer
64 64
         if (ob_get_level() > 0) {
Please login to merge, or discard this patch.
Indentation   +92 added lines, -92 removed lines patch added patch discarded remove patch
@@ -13,22 +13,22 @@  discard block
 block discarded – undo
13 13
 
14 14
 class ExceptionHandler
15 15
 {
16
-    /**
17
-     * Global exception handler
18
-     *
19
-     * Smarty would be nice to use, but it COULD BE smarty that throws the errors.
20
-     * Let's build something ourselves, and hope it works.
21
-     *
22
-     * @param $exception
23
-     *
24
-     * @category Security-Critical - has the potential to leak data when exception is thrown.
25
-     */
26
-    public static function exceptionHandler(Throwable $exception)
27
-    {
28
-        /** @global $siteConfiguration SiteConfiguration */
29
-        global $siteConfiguration;
30
-
31
-        $errorDocument = <<<HTML
16
+	/**
17
+	 * Global exception handler
18
+	 *
19
+	 * Smarty would be nice to use, but it COULD BE smarty that throws the errors.
20
+	 * Let's build something ourselves, and hope it works.
21
+	 *
22
+	 * @param $exception
23
+	 *
24
+	 * @category Security-Critical - has the potential to leak data when exception is thrown.
25
+	 */
26
+	public static function exceptionHandler(Throwable $exception)
27
+	{
28
+		/** @global $siteConfiguration SiteConfiguration */
29
+		global $siteConfiguration;
30
+
31
+		$errorDocument = <<<HTML
32 32
 <!DOCTYPE html>
33 33
 <html lang="en"><head>
34 34
 <meta charset="utf-8">
@@ -48,80 +48,80 @@  discard block
 block discarded – undo
48 48
 </div></body></html>
49 49
 HTML;
50 50
 
51
-        $errorData = self::getExceptionData($exception);
52
-        $errorData['server'] = $_SERVER;
53
-        $errorData['get'] = $_GET;
54
-        $errorData['post'] = $_POST;
55
-
56
-        $state = serialize($errorData);
57
-        $errorId = sha1($state);
58
-
59
-        // Save the error for later analysis
60
-        file_put_contents($siteConfiguration->getErrorLog() . '/' . $errorId . '.log', $state);
61
-
62
-        // clear and discard any content that's been saved to the output buffer
63
-        if (ob_get_level() > 0) {
64
-            ob_end_clean();
65
-        }
66
-
67
-        // push error ID into the document.
68
-        $message = str_replace('$1$', $errorId, $errorDocument);
69
-
70
-        if ($siteConfiguration->getDebuggingTraceEnabled()) {
71
-            ob_start();
72
-            var_dump($errorData);
73
-            $textErrorData = ob_get_contents();
74
-            ob_end_clean();
75
-
76
-            $message = str_replace('$2$', $textErrorData, $message);
77
-        }
78
-        else {
79
-            $message = str_replace('$2$', "", $message);
80
-        }
81
-
82
-        // While we *shouldn't* have sent headers by now due to the output buffering, PHPUnit does weird things.
83
-        // This is "only" needed for the tests, but it's a good idea to wrap this anyway.
84
-        if (!headers_sent()) {
85
-            header('HTTP/1.1 500 Internal Server Error');
86
-        }
87
-
88
-        // output the document
89
-        print $message;
90
-    }
91
-
92
-    /**
93
-     * @param int    $errorSeverity The severity level of the exception.
94
-     * @param string $errorMessage  The Exception message to throw.
95
-     * @param string $errorFile     The filename where the exception is thrown.
96
-     * @param int    $errorLine     The line number where the exception is thrown.
97
-     *
98
-     * @throws ErrorException
99
-     */
100
-    public static function errorHandler($errorSeverity, $errorMessage, $errorFile, $errorLine)
101
-    {
102
-        // call into the main exception handler above
103
-        throw new ErrorException($errorMessage, 0, $errorSeverity, $errorFile, $errorLine);
104
-    }
105
-
106
-    /**
107
-     * @param Throwable $exception
108
-     *
109
-     * @return null|array
110
-     */
111
-    public static function getExceptionData($exception)
112
-    {
113
-        if ($exception == null) {
114
-            return null;
115
-        }
116
-
117
-        $array = array(
118
-            'exception' => get_class($exception),
119
-            'message'   => $exception->getMessage(),
120
-            'stack'     => $exception->getTraceAsString(),
121
-        );
122
-
123
-        $array['previous'] = self::getExceptionData($exception->getPrevious());
124
-
125
-        return $array;
126
-    }
51
+		$errorData = self::getExceptionData($exception);
52
+		$errorData['server'] = $_SERVER;
53
+		$errorData['get'] = $_GET;
54
+		$errorData['post'] = $_POST;
55
+
56
+		$state = serialize($errorData);
57
+		$errorId = sha1($state);
58
+
59
+		// Save the error for later analysis
60
+		file_put_contents($siteConfiguration->getErrorLog() . '/' . $errorId . '.log', $state);
61
+
62
+		// clear and discard any content that's been saved to the output buffer
63
+		if (ob_get_level() > 0) {
64
+			ob_end_clean();
65
+		}
66
+
67
+		// push error ID into the document.
68
+		$message = str_replace('$1$', $errorId, $errorDocument);
69
+
70
+		if ($siteConfiguration->getDebuggingTraceEnabled()) {
71
+			ob_start();
72
+			var_dump($errorData);
73
+			$textErrorData = ob_get_contents();
74
+			ob_end_clean();
75
+
76
+			$message = str_replace('$2$', $textErrorData, $message);
77
+		}
78
+		else {
79
+			$message = str_replace('$2$', "", $message);
80
+		}
81
+
82
+		// While we *shouldn't* have sent headers by now due to the output buffering, PHPUnit does weird things.
83
+		// This is "only" needed for the tests, but it's a good idea to wrap this anyway.
84
+		if (!headers_sent()) {
85
+			header('HTTP/1.1 500 Internal Server Error');
86
+		}
87
+
88
+		// output the document
89
+		print $message;
90
+	}
91
+
92
+	/**
93
+	 * @param int    $errorSeverity The severity level of the exception.
94
+	 * @param string $errorMessage  The Exception message to throw.
95
+	 * @param string $errorFile     The filename where the exception is thrown.
96
+	 * @param int    $errorLine     The line number where the exception is thrown.
97
+	 *
98
+	 * @throws ErrorException
99
+	 */
100
+	public static function errorHandler($errorSeverity, $errorMessage, $errorFile, $errorLine)
101
+	{
102
+		// call into the main exception handler above
103
+		throw new ErrorException($errorMessage, 0, $errorSeverity, $errorFile, $errorLine);
104
+	}
105
+
106
+	/**
107
+	 * @param Throwable $exception
108
+	 *
109
+	 * @return null|array
110
+	 */
111
+	public static function getExceptionData($exception)
112
+	{
113
+		if ($exception == null) {
114
+			return null;
115
+		}
116
+
117
+		$array = array(
118
+			'exception' => get_class($exception),
119
+			'message'   => $exception->getMessage(),
120
+			'stack'     => $exception->getTraceAsString(),
121
+		);
122
+
123
+		$array['previous'] = self::getExceptionData($exception->getPrevious());
124
+
125
+		return $array;
126
+	}
127 127
 }
Please login to merge, or discard this patch.
includes/Validation/RequestValidationHelper.php 2 patches
Indentation   +284 added lines, -284 removed lines patch added patch discarded remove patch
@@ -22,288 +22,288 @@
 block discarded – undo
22 22
  */
23 23
 class RequestValidationHelper
24 24
 {
25
-    /** @var IBanHelper */
26
-    private $banHelper;
27
-    /** @var Request */
28
-    private $request;
29
-    private $emailConfirmation;
30
-    /** @var PdoDatabase */
31
-    private $database;
32
-    /** @var IAntiSpoofProvider */
33
-    private $antiSpoofProvider;
34
-    /** @var IXffTrustProvider */
35
-    private $xffTrustProvider;
36
-    /** @var HttpHelper */
37
-    private $httpHelper;
38
-    /**
39
-     * @var string
40
-     */
41
-    private $mediawikiApiEndpoint;
42
-    private $titleBlacklistEnabled;
43
-    /**
44
-     * @var TorExitProvider
45
-     */
46
-    private $torExitProvider;
47
-
48
-    /**
49
-     * Summary of __construct
50
-     *
51
-     * @param IBanHelper         $banHelper
52
-     * @param Request            $request
53
-     * @param string             $emailConfirmation
54
-     * @param PdoDatabase        $database
55
-     * @param IAntiSpoofProvider $antiSpoofProvider
56
-     * @param IXffTrustProvider  $xffTrustProvider
57
-     * @param HttpHelper         $httpHelper
58
-     * @param string             $mediawikiApiEndpoint
59
-     * @param boolean            $titleBlacklistEnabled
60
-     * @param TorExitProvider    $torExitProvider
61
-     */
62
-    public function __construct(
63
-        IBanHelper $banHelper,
64
-        Request $request,
65
-        $emailConfirmation,
66
-        PdoDatabase $database,
67
-        IAntiSpoofProvider $antiSpoofProvider,
68
-        IXffTrustProvider $xffTrustProvider,
69
-        HttpHelper $httpHelper,
70
-        $mediawikiApiEndpoint,
71
-        $titleBlacklistEnabled,
72
-        TorExitProvider $torExitProvider
73
-    ) {
74
-        $this->banHelper = $banHelper;
75
-        $this->request = $request;
76
-        $this->emailConfirmation = $emailConfirmation;
77
-        $this->database = $database;
78
-        $this->antiSpoofProvider = $antiSpoofProvider;
79
-        $this->xffTrustProvider = $xffTrustProvider;
80
-        $this->httpHelper = $httpHelper;
81
-        $this->mediawikiApiEndpoint = $mediawikiApiEndpoint;
82
-        $this->titleBlacklistEnabled = $titleBlacklistEnabled;
83
-        $this->torExitProvider = $torExitProvider;
84
-    }
85
-
86
-    /**
87
-     * Summary of validateName
88
-     * @return ValidationError[]
89
-     */
90
-    public function validateName()
91
-    {
92
-        $errorList = array();
93
-
94
-        // ERRORS
95
-        // name is empty
96
-        if (trim($this->request->getName()) == "") {
97
-            $errorList[ValidationError::NAME_EMPTY] = new ValidationError(ValidationError::NAME_EMPTY);
98
-        }
99
-
100
-        // name is banned
101
-        $ban = $this->banHelper->nameIsBanned($this->request->getName());
102
-        if ($ban != false) {
103
-            $errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED);
104
-        }
105
-
106
-        // username already exists
107
-        if ($this->userExists()) {
108
-            $errorList[ValidationError::NAME_EXISTS] = new ValidationError(ValidationError::NAME_EXISTS);
109
-        }
110
-
111
-        // username part of SUL account
112
-        if ($this->userSulExists()) {
113
-            // using same error slot as name exists - it's the same sort of error, and we probably only want to show one.
114
-            $errorList[ValidationError::NAME_EXISTS] = new ValidationError(ValidationError::NAME_EXISTS_SUL);
115
-        }
116
-
117
-        // username is numbers
118
-        if (preg_match("/^[0-9]+$/", $this->request->getName()) === 1) {
119
-            $errorList[ValidationError::NAME_NUMONLY] = new ValidationError(ValidationError::NAME_NUMONLY);
120
-        }
121
-
122
-        // username can't contain #@/<>[]|{}
123
-        if (preg_match("/[" . preg_quote("#@/<>[]|{}", "/") . "]/", $this->request->getName()) === 1) {
124
-            $errorList[ValidationError::NAME_INVALIDCHAR] = new ValidationError(ValidationError::NAME_INVALIDCHAR);
125
-        }
126
-
127
-        // existing non-closed request for this name
128
-        if ($this->nameRequestExists()) {
129
-            $errorList[ValidationError::OPEN_REQUEST_NAME] = new ValidationError(ValidationError::OPEN_REQUEST_NAME);
130
-        }
131
-
132
-        return $errorList;
133
-    }
134
-
135
-    /**
136
-     * Summary of validateEmail
137
-     * @return ValidationError[]
138
-     */
139
-    public function validateEmail()
140
-    {
141
-        $errorList = array();
142
-
143
-        // ERRORS
144
-
145
-        // Email is banned
146
-        $ban = $this->banHelper->emailIsBanned($this->request->getEmail());
147
-        if ($ban != false) {
148
-            $errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED);
149
-        }
150
-
151
-        // email addresses must match
152
-        if ($this->request->getEmail() != $this->emailConfirmation) {
153
-            $errorList[ValidationError::EMAIL_MISMATCH] = new ValidationError(ValidationError::EMAIL_MISMATCH);
154
-        }
155
-
156
-        // email address must be validly formed
157
-        if (trim($this->request->getEmail()) == "") {
158
-            $errorList[ValidationError::EMAIL_EMPTY] = new ValidationError(ValidationError::EMAIL_EMPTY);
159
-        }
160
-
161
-        // email address must be validly formed
162
-        if (!filter_var($this->request->getEmail(), FILTER_VALIDATE_EMAIL)) {
163
-            if (trim($this->request->getEmail()) != "") {
164
-                $errorList[ValidationError::EMAIL_INVALID] = new ValidationError(ValidationError::EMAIL_INVALID);
165
-            }
166
-        }
167
-
168
-        // email address can't be wikimedia/wikipedia .com/org
169
-        if (preg_match('/.*@.*wiki(m.dia|p.dia)\.(org|com)/i', $this->request->getEmail()) === 1) {
170
-            $errorList[ValidationError::EMAIL_WIKIMEDIA] = new ValidationError(ValidationError::EMAIL_WIKIMEDIA);
171
-        }
172
-
173
-        // WARNINGS
174
-
175
-        return $errorList;
176
-    }
177
-
178
-    /**
179
-     * Summary of validateOther
180
-     * @return ValidationError[]
181
-     */
182
-    public function validateOther()
183
-    {
184
-        $errorList = array();
185
-
186
-        $trustedIp = $this->xffTrustProvider->getTrustedClientIp($this->request->getIp(),
187
-            $this->request->getForwardedIp());
188
-
189
-        // ERRORS
190
-
191
-        // TOR nodes
192
-        if ($this->torExitProvider->isTorExit($trustedIp)) {
193
-            $errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED_TOR);
194
-        }
195
-
196
-        // IP banned
197
-        $ban = $this->banHelper->ipIsBanned($trustedIp);
198
-        if ($ban != false) {
199
-            $errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED);
200
-        }
201
-
202
-        // WARNINGS
203
-
204
-        // Antispoof check
205
-        $this->checkAntiSpoof();
206
-
207
-        // Blacklist check
208
-        $this->checkTitleBlacklist();
209
-
210
-        return $errorList;
211
-    }
212
-
213
-    private function checkAntiSpoof()
214
-    {
215
-        try {
216
-            if (count($this->antiSpoofProvider->getSpoofs($this->request->getName())) > 0) {
217
-                // If there were spoofs an Admin should handle the request.
218
-                $this->request->setStatus("Flagged users");
219
-            }
220
-        }
221
-        catch (Exception $ex) {
222
-            // logme
223
-        }
224
-    }
225
-
226
-    private function checkTitleBlacklist()
227
-    {
228
-        if ($this->titleBlacklistEnabled == 1) {
229
-            $apiResult = $this->httpHelper->get(
230
-                $this->mediawikiApiEndpoint,
231
-                array(
232
-                    'action'       => 'titleblacklist',
233
-                    'tbtitle'      => $this->request->getName(),
234
-                    'tbaction'     => 'new-account',
235
-                    'tbnooverride' => true,
236
-                    'format'       => 'php',
237
-                )
238
-            );
239
-
240
-            $data = unserialize($apiResult);
241
-
242
-            $requestIsOk = $data['titleblacklist']['result'] == "ok";
243
-
244
-            if (!$requestIsOk) {
245
-                $this->request->setStatus("Flagged users");
246
-            }
247
-        }
248
-    }
249
-
250
-    private function userExists()
251
-    {
252
-        $userExists = $this->httpHelper->get(
253
-            $this->mediawikiApiEndpoint,
254
-            array(
255
-                'action'  => 'query',
256
-                'list'    => 'users',
257
-                'ususers' => $this->request->getName(),
258
-                'format'  => 'php',
259
-            )
260
-        );
261
-
262
-        $ue = unserialize($userExists);
263
-        if (!isset ($ue['query']['users']['0']['missing']) && isset ($ue['query']['users']['0']['userid'])) {
264
-            return true;
265
-        }
266
-
267
-        return false;
268
-    }
269
-
270
-    private function userSulExists()
271
-    {
272
-        $requestName = $this->request->getName();
273
-
274
-        $userExists = $this->httpHelper->get(
275
-            $this->mediawikiApiEndpoint,
276
-            array(
277
-                'action'  => 'query',
278
-                'meta'    => 'globaluserinfo',
279
-                'guiuser' => $requestName,
280
-                'format'  => 'php',
281
-            )
282
-        );
283
-
284
-        $ue = unserialize($userExists);
285
-        if (isset ($ue['query']['globaluserinfo']['id'])) {
286
-            return true;
287
-        }
288
-
289
-        return false;
290
-    }
291
-
292
-    /**
293
-     * Checks if a request with this name is currently open
294
-     *
295
-     * @return bool
296
-     */
297
-    private function nameRequestExists()
298
-    {
299
-        $query = "SELECT COUNT(id) FROM request WHERE status != 'Closed' AND name = :name;";
300
-        $statement = $this->database->prepare($query);
301
-        $statement->execute(array(':name' => $this->request->getName()));
302
-
303
-        if (!$statement) {
304
-            return false;
305
-        }
306
-
307
-        return $statement->fetchColumn() > 0;
308
-    }
25
+	/** @var IBanHelper */
26
+	private $banHelper;
27
+	/** @var Request */
28
+	private $request;
29
+	private $emailConfirmation;
30
+	/** @var PdoDatabase */
31
+	private $database;
32
+	/** @var IAntiSpoofProvider */
33
+	private $antiSpoofProvider;
34
+	/** @var IXffTrustProvider */
35
+	private $xffTrustProvider;
36
+	/** @var HttpHelper */
37
+	private $httpHelper;
38
+	/**
39
+	 * @var string
40
+	 */
41
+	private $mediawikiApiEndpoint;
42
+	private $titleBlacklistEnabled;
43
+	/**
44
+	 * @var TorExitProvider
45
+	 */
46
+	private $torExitProvider;
47
+
48
+	/**
49
+	 * Summary of __construct
50
+	 *
51
+	 * @param IBanHelper         $banHelper
52
+	 * @param Request            $request
53
+	 * @param string             $emailConfirmation
54
+	 * @param PdoDatabase        $database
55
+	 * @param IAntiSpoofProvider $antiSpoofProvider
56
+	 * @param IXffTrustProvider  $xffTrustProvider
57
+	 * @param HttpHelper         $httpHelper
58
+	 * @param string             $mediawikiApiEndpoint
59
+	 * @param boolean            $titleBlacklistEnabled
60
+	 * @param TorExitProvider    $torExitProvider
61
+	 */
62
+	public function __construct(
63
+		IBanHelper $banHelper,
64
+		Request $request,
65
+		$emailConfirmation,
66
+		PdoDatabase $database,
67
+		IAntiSpoofProvider $antiSpoofProvider,
68
+		IXffTrustProvider $xffTrustProvider,
69
+		HttpHelper $httpHelper,
70
+		$mediawikiApiEndpoint,
71
+		$titleBlacklistEnabled,
72
+		TorExitProvider $torExitProvider
73
+	) {
74
+		$this->banHelper = $banHelper;
75
+		$this->request = $request;
76
+		$this->emailConfirmation = $emailConfirmation;
77
+		$this->database = $database;
78
+		$this->antiSpoofProvider = $antiSpoofProvider;
79
+		$this->xffTrustProvider = $xffTrustProvider;
80
+		$this->httpHelper = $httpHelper;
81
+		$this->mediawikiApiEndpoint = $mediawikiApiEndpoint;
82
+		$this->titleBlacklistEnabled = $titleBlacklistEnabled;
83
+		$this->torExitProvider = $torExitProvider;
84
+	}
85
+
86
+	/**
87
+	 * Summary of validateName
88
+	 * @return ValidationError[]
89
+	 */
90
+	public function validateName()
91
+	{
92
+		$errorList = array();
93
+
94
+		// ERRORS
95
+		// name is empty
96
+		if (trim($this->request->getName()) == "") {
97
+			$errorList[ValidationError::NAME_EMPTY] = new ValidationError(ValidationError::NAME_EMPTY);
98
+		}
99
+
100
+		// name is banned
101
+		$ban = $this->banHelper->nameIsBanned($this->request->getName());
102
+		if ($ban != false) {
103
+			$errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED);
104
+		}
105
+
106
+		// username already exists
107
+		if ($this->userExists()) {
108
+			$errorList[ValidationError::NAME_EXISTS] = new ValidationError(ValidationError::NAME_EXISTS);
109
+		}
110
+
111
+		// username part of SUL account
112
+		if ($this->userSulExists()) {
113
+			// using same error slot as name exists - it's the same sort of error, and we probably only want to show one.
114
+			$errorList[ValidationError::NAME_EXISTS] = new ValidationError(ValidationError::NAME_EXISTS_SUL);
115
+		}
116
+
117
+		// username is numbers
118
+		if (preg_match("/^[0-9]+$/", $this->request->getName()) === 1) {
119
+			$errorList[ValidationError::NAME_NUMONLY] = new ValidationError(ValidationError::NAME_NUMONLY);
120
+		}
121
+
122
+		// username can't contain #@/<>[]|{}
123
+		if (preg_match("/[" . preg_quote("#@/<>[]|{}", "/") . "]/", $this->request->getName()) === 1) {
124
+			$errorList[ValidationError::NAME_INVALIDCHAR] = new ValidationError(ValidationError::NAME_INVALIDCHAR);
125
+		}
126
+
127
+		// existing non-closed request for this name
128
+		if ($this->nameRequestExists()) {
129
+			$errorList[ValidationError::OPEN_REQUEST_NAME] = new ValidationError(ValidationError::OPEN_REQUEST_NAME);
130
+		}
131
+
132
+		return $errorList;
133
+	}
134
+
135
+	/**
136
+	 * Summary of validateEmail
137
+	 * @return ValidationError[]
138
+	 */
139
+	public function validateEmail()
140
+	{
141
+		$errorList = array();
142
+
143
+		// ERRORS
144
+
145
+		// Email is banned
146
+		$ban = $this->banHelper->emailIsBanned($this->request->getEmail());
147
+		if ($ban != false) {
148
+			$errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED);
149
+		}
150
+
151
+		// email addresses must match
152
+		if ($this->request->getEmail() != $this->emailConfirmation) {
153
+			$errorList[ValidationError::EMAIL_MISMATCH] = new ValidationError(ValidationError::EMAIL_MISMATCH);
154
+		}
155
+
156
+		// email address must be validly formed
157
+		if (trim($this->request->getEmail()) == "") {
158
+			$errorList[ValidationError::EMAIL_EMPTY] = new ValidationError(ValidationError::EMAIL_EMPTY);
159
+		}
160
+
161
+		// email address must be validly formed
162
+		if (!filter_var($this->request->getEmail(), FILTER_VALIDATE_EMAIL)) {
163
+			if (trim($this->request->getEmail()) != "") {
164
+				$errorList[ValidationError::EMAIL_INVALID] = new ValidationError(ValidationError::EMAIL_INVALID);
165
+			}
166
+		}
167
+
168
+		// email address can't be wikimedia/wikipedia .com/org
169
+		if (preg_match('/.*@.*wiki(m.dia|p.dia)\.(org|com)/i', $this->request->getEmail()) === 1) {
170
+			$errorList[ValidationError::EMAIL_WIKIMEDIA] = new ValidationError(ValidationError::EMAIL_WIKIMEDIA);
171
+		}
172
+
173
+		// WARNINGS
174
+
175
+		return $errorList;
176
+	}
177
+
178
+	/**
179
+	 * Summary of validateOther
180
+	 * @return ValidationError[]
181
+	 */
182
+	public function validateOther()
183
+	{
184
+		$errorList = array();
185
+
186
+		$trustedIp = $this->xffTrustProvider->getTrustedClientIp($this->request->getIp(),
187
+			$this->request->getForwardedIp());
188
+
189
+		// ERRORS
190
+
191
+		// TOR nodes
192
+		if ($this->torExitProvider->isTorExit($trustedIp)) {
193
+			$errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED_TOR);
194
+		}
195
+
196
+		// IP banned
197
+		$ban = $this->banHelper->ipIsBanned($trustedIp);
198
+		if ($ban != false) {
199
+			$errorList[ValidationError::BANNED] = new ValidationError(ValidationError::BANNED);
200
+		}
201
+
202
+		// WARNINGS
203
+
204
+		// Antispoof check
205
+		$this->checkAntiSpoof();
206
+
207
+		// Blacklist check
208
+		$this->checkTitleBlacklist();
209
+
210
+		return $errorList;
211
+	}
212
+
213
+	private function checkAntiSpoof()
214
+	{
215
+		try {
216
+			if (count($this->antiSpoofProvider->getSpoofs($this->request->getName())) > 0) {
217
+				// If there were spoofs an Admin should handle the request.
218
+				$this->request->setStatus("Flagged users");
219
+			}
220
+		}
221
+		catch (Exception $ex) {
222
+			// logme
223
+		}
224
+	}
225
+
226
+	private function checkTitleBlacklist()
227
+	{
228
+		if ($this->titleBlacklistEnabled == 1) {
229
+			$apiResult = $this->httpHelper->get(
230
+				$this->mediawikiApiEndpoint,
231
+				array(
232
+					'action'       => 'titleblacklist',
233
+					'tbtitle'      => $this->request->getName(),
234
+					'tbaction'     => 'new-account',
235
+					'tbnooverride' => true,
236
+					'format'       => 'php',
237
+				)
238
+			);
239
+
240
+			$data = unserialize($apiResult);
241
+
242
+			$requestIsOk = $data['titleblacklist']['result'] == "ok";
243
+
244
+			if (!$requestIsOk) {
245
+				$this->request->setStatus("Flagged users");
246
+			}
247
+		}
248
+	}
249
+
250
+	private function userExists()
251
+	{
252
+		$userExists = $this->httpHelper->get(
253
+			$this->mediawikiApiEndpoint,
254
+			array(
255
+				'action'  => 'query',
256
+				'list'    => 'users',
257
+				'ususers' => $this->request->getName(),
258
+				'format'  => 'php',
259
+			)
260
+		);
261
+
262
+		$ue = unserialize($userExists);
263
+		if (!isset ($ue['query']['users']['0']['missing']) && isset ($ue['query']['users']['0']['userid'])) {
264
+			return true;
265
+		}
266
+
267
+		return false;
268
+	}
269
+
270
+	private function userSulExists()
271
+	{
272
+		$requestName = $this->request->getName();
273
+
274
+		$userExists = $this->httpHelper->get(
275
+			$this->mediawikiApiEndpoint,
276
+			array(
277
+				'action'  => 'query',
278
+				'meta'    => 'globaluserinfo',
279
+				'guiuser' => $requestName,
280
+				'format'  => 'php',
281
+			)
282
+		);
283
+
284
+		$ue = unserialize($userExists);
285
+		if (isset ($ue['query']['globaluserinfo']['id'])) {
286
+			return true;
287
+		}
288
+
289
+		return false;
290
+	}
291
+
292
+	/**
293
+	 * Checks if a request with this name is currently open
294
+	 *
295
+	 * @return bool
296
+	 */
297
+	private function nameRequestExists()
298
+	{
299
+		$query = "SELECT COUNT(id) FROM request WHERE status != 'Closed' AND name = :name;";
300
+		$statement = $this->database->prepare($query);
301
+		$statement->execute(array(':name' => $this->request->getName()));
302
+
303
+		if (!$statement) {
304
+			return false;
305
+		}
306
+
307
+		return $statement->fetchColumn() > 0;
308
+	}
309 309
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -120,7 +120,7 @@
 block discarded – undo
120 120
         }
121 121
 
122 122
         // username can't contain #@/<>[]|{}
123
-        if (preg_match("/[" . preg_quote("#@/<>[]|{}", "/") . "]/", $this->request->getName()) === 1) {
123
+        if (preg_match("/[".preg_quote("#@/<>[]|{}", "/")."]/", $this->request->getName()) === 1) {
124 124
             $errorList[ValidationError::NAME_INVALIDCHAR] = new ValidationError(ValidationError::NAME_INVALIDCHAR);
125 125
         }
126 126
 
Please login to merge, or discard this patch.
includes/Validation/ValidationError.php 1 patch
Indentation   +89 added lines, -89 removed lines patch added patch discarded remove patch
@@ -12,99 +12,99 @@
 block discarded – undo
12 12
 
13 13
 class ValidationError
14 14
 {
15
-    const NAME_EMPTY = "name_empty";
16
-    const NAME_EXISTS = "name_exists";
17
-    const NAME_EXISTS_SUL = "name_exists";
18
-    const NAME_NUMONLY = "name_numonly";
19
-    const NAME_INVALIDCHAR = "name_invalidchar";
20
-    const NAME_SANITISED = "name_sanitised";
21
-    const EMAIL_EMPTY = "email_empty";
22
-    const EMAIL_WIKIMEDIA = "email_wikimedia";
23
-    const EMAIL_INVALID = "email_invalid";
24
-    const EMAIL_MISMATCH = "email_mismatch";
25
-    const OPEN_REQUEST_NAME = "open_request_name";
26
-    const BANNED = "banned";
27
-    const BANNED_TOR = "banned_tor";
28
-    /**
29
-     * @var array Error text for the above
30
-     */
31
-    private static $errorText = array(
32
-        self::NAME_EMPTY        => 'You\'ve not chosen a username!',
33
-        self::NAME_EXISTS       => 'I\'m sorry, but the username you selected is already taken. Please try another. '
34
-            . 'Please note that Wikipedia automatically capitalizes the first letter of any user name, therefore '
35
-            . '[[User:example]] would become [[User:Example]].',
36
-        self::NAME_EXISTS_SUL   => 'I\'m sorry, but the username you selected is already taken. Please try another. '
37
-            . 'Please note that Wikipedia automatically capitalizes the first letter of any user name, therefore '
38
-            . '[[User:example]] would become [[User:Example]].',
39
-        self::NAME_NUMONLY      => 'The username you chose is invalid: it consists entirely of numbers. Please retry '
40
-            . 'with a valid username.',
41
-        self::NAME_INVALIDCHAR  => 'There appears to be an invalid character in your username. Please note that the '
42
-            . 'following characters are not allowed: <code># @ / &lt; &gt; [ ] | { }</code>',
43
-        self::NAME_SANITISED    => 'Your requested username has been automatically adjusted due to technical '
44
-            . 'restrictions. Underscores have been replaced with spaces, and the first character has been capitalised.',
45
-        self::EMAIL_EMPTY       => 'You need to supply an email address.',
46
-        self::EMAIL_WIKIMEDIA   => 'Please provide your email address here.',
47
-        self::EMAIL_INVALID     => 'Invalid E-mail address supplied. Please check you entered it correctly.',
48
-        self::EMAIL_MISMATCH    => 'The email addresses you entered do not match. Please try again.',
49
-        self::OPEN_REQUEST_NAME => 'There is already an open request with this name in this system.',
50
-        self::BANNED            => 'I\'m sorry, but you are currently banned from requesting accounts using this tool. '
51
-            . 'However, you can still send an email to [email protected] to request an account.',
52
-        self::BANNED_TOR        => 'Tor exit nodes are currently banned from using this tool due to excessive abuse. '
53
-            . 'Please note that Tor is also currently banned from editing Wikipedia.',
54
-    );
55
-    /**
56
-     * Summary of $errorCode
57
-     * @var string
58
-     */
59
-    private $errorCode;
60
-    /**
61
-     * Summary of $isError
62
-     * @var bool
63
-     */
64
-    private $isError;
15
+	const NAME_EMPTY = "name_empty";
16
+	const NAME_EXISTS = "name_exists";
17
+	const NAME_EXISTS_SUL = "name_exists";
18
+	const NAME_NUMONLY = "name_numonly";
19
+	const NAME_INVALIDCHAR = "name_invalidchar";
20
+	const NAME_SANITISED = "name_sanitised";
21
+	const EMAIL_EMPTY = "email_empty";
22
+	const EMAIL_WIKIMEDIA = "email_wikimedia";
23
+	const EMAIL_INVALID = "email_invalid";
24
+	const EMAIL_MISMATCH = "email_mismatch";
25
+	const OPEN_REQUEST_NAME = "open_request_name";
26
+	const BANNED = "banned";
27
+	const BANNED_TOR = "banned_tor";
28
+	/**
29
+	 * @var array Error text for the above
30
+	 */
31
+	private static $errorText = array(
32
+		self::NAME_EMPTY        => 'You\'ve not chosen a username!',
33
+		self::NAME_EXISTS       => 'I\'m sorry, but the username you selected is already taken. Please try another. '
34
+			. 'Please note that Wikipedia automatically capitalizes the first letter of any user name, therefore '
35
+			. '[[User:example]] would become [[User:Example]].',
36
+		self::NAME_EXISTS_SUL   => 'I\'m sorry, but the username you selected is already taken. Please try another. '
37
+			. 'Please note that Wikipedia automatically capitalizes the first letter of any user name, therefore '
38
+			. '[[User:example]] would become [[User:Example]].',
39
+		self::NAME_NUMONLY      => 'The username you chose is invalid: it consists entirely of numbers. Please retry '
40
+			. 'with a valid username.',
41
+		self::NAME_INVALIDCHAR  => 'There appears to be an invalid character in your username. Please note that the '
42
+			. 'following characters are not allowed: <code># @ / &lt; &gt; [ ] | { }</code>',
43
+		self::NAME_SANITISED    => 'Your requested username has been automatically adjusted due to technical '
44
+			. 'restrictions. Underscores have been replaced with spaces, and the first character has been capitalised.',
45
+		self::EMAIL_EMPTY       => 'You need to supply an email address.',
46
+		self::EMAIL_WIKIMEDIA   => 'Please provide your email address here.',
47
+		self::EMAIL_INVALID     => 'Invalid E-mail address supplied. Please check you entered it correctly.',
48
+		self::EMAIL_MISMATCH    => 'The email addresses you entered do not match. Please try again.',
49
+		self::OPEN_REQUEST_NAME => 'There is already an open request with this name in this system.',
50
+		self::BANNED            => 'I\'m sorry, but you are currently banned from requesting accounts using this tool. '
51
+			. 'However, you can still send an email to [email protected] to request an account.',
52
+		self::BANNED_TOR        => 'Tor exit nodes are currently banned from using this tool due to excessive abuse. '
53
+			. 'Please note that Tor is also currently banned from editing Wikipedia.',
54
+	);
55
+	/**
56
+	 * Summary of $errorCode
57
+	 * @var string
58
+	 */
59
+	private $errorCode;
60
+	/**
61
+	 * Summary of $isError
62
+	 * @var bool
63
+	 */
64
+	private $isError;
65 65
 
66
-    /**
67
-     * Summary of __construct
68
-     *
69
-     * @param string $errorCode
70
-     * @param bool   $isError
71
-     */
72
-    public function __construct($errorCode, $isError = true)
73
-    {
74
-        $this->errorCode = $errorCode;
75
-        $this->isError = $isError;
76
-    }
66
+	/**
67
+	 * Summary of __construct
68
+	 *
69
+	 * @param string $errorCode
70
+	 * @param bool   $isError
71
+	 */
72
+	public function __construct($errorCode, $isError = true)
73
+	{
74
+		$this->errorCode = $errorCode;
75
+		$this->isError = $isError;
76
+	}
77 77
 
78
-    /**
79
-     * Summary of getErrorCode
80
-     * @return string
81
-     */
82
-    public function getErrorCode()
83
-    {
84
-        return $this->errorCode;
85
-    }
78
+	/**
79
+	 * Summary of getErrorCode
80
+	 * @return string
81
+	 */
82
+	public function getErrorCode()
83
+	{
84
+		return $this->errorCode;
85
+	}
86 86
 
87
-    /**
88
-     * @return string
89
-     * @throws Exception
90
-     */
91
-    public function getErrorMessage()
92
-    {
93
-        $text = self::$errorText[$this->errorCode];
87
+	/**
88
+	 * @return string
89
+	 * @throws Exception
90
+	 */
91
+	public function getErrorMessage()
92
+	{
93
+		$text = self::$errorText[$this->errorCode];
94 94
 
95
-        if ($text == null) {
96
-            throw new Exception('Unknown validation error');
97
-        }
95
+		if ($text == null) {
96
+			throw new Exception('Unknown validation error');
97
+		}
98 98
 
99
-        return $text;
100
-    }
99
+		return $text;
100
+	}
101 101
 
102
-    /**
103
-     * Summary of isError
104
-     * @return bool
105
-     */
106
-    public function isError()
107
-    {
108
-        return $this->isError;
109
-    }
102
+	/**
103
+	 * Summary of isError
104
+	 * @return bool
105
+	 */
106
+	public function isError()
107
+	{
108
+		return $this->isError;
109
+	}
110 110
 }
Please login to merge, or discard this patch.
includes/Security/Token.php 1 patch
Indentation   +69 added lines, -69 removed lines patch added patch discarded remove patch
@@ -12,80 +12,80 @@
 block discarded – undo
12 12
 
13 13
 class Token
14 14
 {
15
-    /** @var string */
16
-    private $tokenData;
17
-    /** @var string */
18
-    private $context;
19
-    /** @var DateTimeImmutable */
20
-    private $generationTimestamp;
21
-    /** @var DateTimeImmutable */
22
-    private $usageTimestamp;
23
-    /** @var bool */
24
-    private $used;
15
+	/** @var string */
16
+	private $tokenData;
17
+	/** @var string */
18
+	private $context;
19
+	/** @var DateTimeImmutable */
20
+	private $generationTimestamp;
21
+	/** @var DateTimeImmutable */
22
+	private $usageTimestamp;
23
+	/** @var bool */
24
+	private $used;
25 25
 
26
-    /**
27
-     * Token constructor.
28
-     *
29
-     * @param string $tokenData
30
-     * @param string $context
31
-     */
32
-    public function __construct($tokenData, $context)
33
-    {
34
-        $this->tokenData = $tokenData;
35
-        $this->context = $context;
36
-        $this->generationTimestamp = new DateTimeImmutable();
37
-        $this->usageTimestamp = null;
38
-        $this->used = false;
39
-    }
26
+	/**
27
+	 * Token constructor.
28
+	 *
29
+	 * @param string $tokenData
30
+	 * @param string $context
31
+	 */
32
+	public function __construct($tokenData, $context)
33
+	{
34
+		$this->tokenData = $tokenData;
35
+		$this->context = $context;
36
+		$this->generationTimestamp = new DateTimeImmutable();
37
+		$this->usageTimestamp = null;
38
+		$this->used = false;
39
+	}
40 40
 
41
-    /**
42
-     * @return DateTimeImmutable
43
-     */
44
-    public function getGenerationTimestamp()
45
-    {
46
-        return $this->generationTimestamp;
47
-    }
41
+	/**
42
+	 * @return DateTimeImmutable
43
+	 */
44
+	public function getGenerationTimestamp()
45
+	{
46
+		return $this->generationTimestamp;
47
+	}
48 48
 
49
-    /**
50
-     * @return string
51
-     */
52
-    public function getContext()
53
-    {
54
-        return $this->context;
55
-    }
49
+	/**
50
+	 * @return string
51
+	 */
52
+	public function getContext()
53
+	{
54
+		return $this->context;
55
+	}
56 56
 
57
-    /**
58
-     * @return string
59
-     */
60
-    public function getTokenData()
61
-    {
62
-        return $this->tokenData;
63
-    }
57
+	/**
58
+	 * @return string
59
+	 */
60
+	public function getTokenData()
61
+	{
62
+		return $this->tokenData;
63
+	}
64 64
 
65
-    /**
66
-     * Returns a value indicating whether the token has already been used or not
67
-     *
68
-     * @return boolean
69
-     */
70
-    public function isUsed()
71
-    {
72
-        return $this->used;
73
-    }
65
+	/**
66
+	 * Returns a value indicating whether the token has already been used or not
67
+	 *
68
+	 * @return boolean
69
+	 */
70
+	public function isUsed()
71
+	{
72
+		return $this->used;
73
+	}
74 74
 
75
-    /**
76
-     * Marks the token as used
77
-     */
78
-    public function markAsUsed()
79
-    {
80
-        $this->used = true;
81
-        $this->usageTimestamp = new DateTimeImmutable();
82
-    }
75
+	/**
76
+	 * Marks the token as used
77
+	 */
78
+	public function markAsUsed()
79
+	{
80
+		$this->used = true;
81
+		$this->usageTimestamp = new DateTimeImmutable();
82
+	}
83 83
 
84
-    /**
85
-     * @return DateTimeImmutable
86
-     */
87
-    public function getUsageTimestamp()
88
-    {
89
-        return $this->usageTimestamp;
90
-    }
84
+	/**
85
+	 * @return DateTimeImmutable
86
+	 */
87
+	public function getUsageTimestamp()
88
+	{
89
+		return $this->usageTimestamp;
90
+	}
91 91
 }
92 92
\ No newline at end of file
Please login to merge, or discard this patch.
includes/Security/TokenManager.php 1 patch
Indentation   +87 added lines, -87 removed lines patch added patch discarded remove patch
@@ -13,91 +13,91 @@
 block discarded – undo
13 13
 
14 14
 class TokenManager
15 15
 {
16
-    /**
17
-     * Validates a CSRF token
18
-     *
19
-     * @param string      $data    The token data string itself
20
-     * @param string|null $context Token context for extra validation
21
-     *
22
-     * @return bool
23
-     */
24
-    public function validateToken($data, $context = null)
25
-    {
26
-        if (!is_string($data) || strlen($data) === 0) {
27
-            // Nothing to validate
28
-            return false;
29
-        }
30
-
31
-        $tokens = WebRequest::getSessionTokenData();
32
-
33
-        // if the token doesn't exist, then it's not valid
34
-        if (!array_key_exists($data, $tokens)) {
35
-            return false;
36
-        }
37
-
38
-        /** @var Token $token */
39
-        $token = unserialize($tokens[$data]);
40
-
41
-        if ($token->getTokenData() !== $data) {
42
-            return false;
43
-        }
44
-
45
-        if ($token->getContext() !== $context) {
46
-            return false;
47
-        }
48
-
49
-        if ($token->isUsed()) {
50
-            return false;
51
-        }
52
-
53
-        // mark the token as used, and save it back to the session
54
-        $token->markAsUsed();
55
-        $this->storeToken($token);
56
-
57
-        return true;
58
-    }
59
-
60
-    /**
61
-     * @param string|null $context An optional context for extra validation
62
-     *
63
-     * @return Token
64
-     */
65
-    public function getNewToken($context = null)
66
-    {
67
-        $token = new Token($this->generateTokenData(), $context);
68
-        $this->storeToken($token);
69
-
70
-        return $token;
71
-    }
72
-
73
-    /**
74
-     * Stores a token in the session data
75
-     *
76
-     * @param Token $token
77
-     */
78
-    private function storeToken(Token $token)
79
-    {
80
-        $tokens = WebRequest::getSessionTokenData();
81
-        $tokens[$token->getTokenData()] = serialize($token);
82
-        WebRequest::setSessionTokenData($tokens);
83
-    }
84
-
85
-    /**
86
-     * Generates a security token
87
-     *
88
-     * @return string
89
-     * @throws Exception
90
-     *
91
-     * @category Security-Critical
92
-     */
93
-    private function generateTokenData()
94
-    {
95
-        $genBytes = openssl_random_pseudo_bytes(33);
96
-
97
-        if ($genBytes !== false) {
98
-            return base64_encode($genBytes);
99
-        }
100
-
101
-        throw new Exception('Unable to generate secure token.');
102
-    }
16
+	/**
17
+	 * Validates a CSRF token
18
+	 *
19
+	 * @param string      $data    The token data string itself
20
+	 * @param string|null $context Token context for extra validation
21
+	 *
22
+	 * @return bool
23
+	 */
24
+	public function validateToken($data, $context = null)
25
+	{
26
+		if (!is_string($data) || strlen($data) === 0) {
27
+			// Nothing to validate
28
+			return false;
29
+		}
30
+
31
+		$tokens = WebRequest::getSessionTokenData();
32
+
33
+		// if the token doesn't exist, then it's not valid
34
+		if (!array_key_exists($data, $tokens)) {
35
+			return false;
36
+		}
37
+
38
+		/** @var Token $token */
39
+		$token = unserialize($tokens[$data]);
40
+
41
+		if ($token->getTokenData() !== $data) {
42
+			return false;
43
+		}
44
+
45
+		if ($token->getContext() !== $context) {
46
+			return false;
47
+		}
48
+
49
+		if ($token->isUsed()) {
50
+			return false;
51
+		}
52
+
53
+		// mark the token as used, and save it back to the session
54
+		$token->markAsUsed();
55
+		$this->storeToken($token);
56
+
57
+		return true;
58
+	}
59
+
60
+	/**
61
+	 * @param string|null $context An optional context for extra validation
62
+	 *
63
+	 * @return Token
64
+	 */
65
+	public function getNewToken($context = null)
66
+	{
67
+		$token = new Token($this->generateTokenData(), $context);
68
+		$this->storeToken($token);
69
+
70
+		return $token;
71
+	}
72
+
73
+	/**
74
+	 * Stores a token in the session data
75
+	 *
76
+	 * @param Token $token
77
+	 */
78
+	private function storeToken(Token $token)
79
+	{
80
+		$tokens = WebRequest::getSessionTokenData();
81
+		$tokens[$token->getTokenData()] = serialize($token);
82
+		WebRequest::setSessionTokenData($tokens);
83
+	}
84
+
85
+	/**
86
+	 * Generates a security token
87
+	 *
88
+	 * @return string
89
+	 * @throws Exception
90
+	 *
91
+	 * @category Security-Critical
92
+	 */
93
+	private function generateTokenData()
94
+	{
95
+		$genBytes = openssl_random_pseudo_bytes(33);
96
+
97
+		if ($genBytes !== false) {
98
+			return base64_encode($genBytes);
99
+		}
100
+
101
+		throw new Exception('Unable to generate secure token.');
102
+	}
103 103
 }
104 104
\ No newline at end of file
Please login to merge, or discard this patch.
includes/Router/RequestRouter.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -358,7 +358,7 @@
 block discarded – undo
358 358
         $routeMap = $this->routePathSegments($classSegment, $requestedAction);
359 359
 
360 360
         if ($routeMap[0] === Page404::class) {
361
-            $routeMap = $this->routeSinglePathSegment($classSegment . '/' . $requestedAction);
361
+            $routeMap = $this->routeSinglePathSegment($classSegment.'/'.$requestedAction);
362 362
         }
363 363
 
364 364
         return $routeMap;
Please login to merge, or discard this patch.
Indentation   +441 added lines, -441 removed lines patch added patch discarded remove patch
@@ -63,445 +63,445 @@
 block discarded – undo
63 63
  */
64 64
 class RequestRouter implements IRequestRouter
65 65
 {
66
-    /**
67
-     * This is the core routing table for the application. The basic idea is:
68
-     *
69
-     *      array(
70
-     *          "foo" =>
71
-     *              array(
72
-     *                  "class"   => PageFoo::class,
73
-     *                  "actions" => array("bar", "other")
74
-     *              ),
75
-     * );
76
-     *
77
-     * Things to note:
78
-     *     - If no page is requested, we go to PageMain. PageMain can't have actions defined.
79
-     *
80
-     *     - If a page is defined and requested, but no action is requested, go to that page's main() method
81
-     *     - If a page is defined and requested, and an action is defined and requested, go to that action's method.
82
-     *     - If a page is defined and requested, and an action NOT defined and requested, go to Page404 and it's main()
83
-     *       method.
84
-     *     - If a page is NOT defined and requested, go to Page404 and it's main() method.
85
-     *
86
-     *     - Query parameters are ignored.
87
-     *
88
-     * The key point here is request routing with validation that this is allowed, before we start hitting the
89
-     * filesystem through the AutoLoader, and opening random files. Also, so that we validate the action requested
90
-     * before we start calling random methods through the web UI.
91
-     *
92
-     * Examples:
93
-     * /internal.php                => returns instance of PageMain, routed to main()
94
-     * /internal.php?query          => returns instance of PageMain, routed to main()
95
-     * /internal.php/foo            => returns instance of PageFoo, routed to main()
96
-     * /internal.php/foo?query      => returns instance of PageFoo, routed to main()
97
-     * /internal.php/foo/bar        => returns instance of PageFoo, routed to bar()
98
-     * /internal.php/foo/bar?query  => returns instance of PageFoo, routed to bar()
99
-     * /internal.php/foo/baz        => returns instance of Page404, routed to main()
100
-     * /internal.php/foo/baz?query  => returns instance of Page404, routed to main()
101
-     * /internal.php/bar            => returns instance of Page404, routed to main()
102
-     * /internal.php/bar?query      => returns instance of Page404, routed to main()
103
-     * /internal.php/bar/baz        => returns instance of Page404, routed to main()
104
-     * /internal.php/bar/baz?query  => returns instance of Page404, routed to main()
105
-     *
106
-     * Take care when changing this - a lot of places rely on the array key for redirects and other links. If you need
107
-     * to change the key, then you'll likely have to update a lot of files.
108
-     *
109
-     * @var array
110
-     */
111
-    private $routeMap = array(
112
-
113
-        //////////////////////////////////////////////////////////////////////////////////////////////////
114
-        // Login and registration
115
-        'logout'                      =>
116
-            array(
117
-                'class'   => PageLogout::class,
118
-                'actions' => array(),
119
-            ),
120
-        'login'                       =>
121
-            array(
122
-                'class'   => PagePasswordLogin::class,
123
-                'actions' => array(),
124
-            ),
125
-        'login/otp'                   =>
126
-            array(
127
-                'class'   => PageOtpLogin::class,
128
-                'actions' => array(),
129
-            ),
130
-        'login/u2f'                   =>
131
-            array(
132
-                'class'   => PageU2FLogin::class,
133
-                'actions' => array(),
134
-            ),
135
-        'forgotPassword'              =>
136
-            array(
137
-                'class'   => PageForgotPassword::class,
138
-                'actions' => array('reset'),
139
-            ),
140
-        'register'                    =>
141
-            array(
142
-                'class'   => PageRegisterOption::class,
143
-                'actions' => array(),
144
-            ),
145
-        'register/standard'           =>
146
-            array(
147
-                'class'   => PageRegisterStandard::class,
148
-                'actions' => array('done'),
149
-            ),
150
-
151
-        //////////////////////////////////////////////////////////////////////////////////////////////////
152
-        // Discovery
153
-        'search'                      =>
154
-            array(
155
-                'class'   => PageSearch::class,
156
-                'actions' => array(),
157
-            ),
158
-        'logs'                        =>
159
-            array(
160
-                'class'   => PageLog::class,
161
-                'actions' => array(),
162
-            ),
163
-
164
-        //////////////////////////////////////////////////////////////////////////////////////////////////
165
-        // Administration
166
-        'bans'                        =>
167
-            array(
168
-                'class'   => PageBan::class,
169
-                'actions' => array('set', 'remove'),
170
-            ),
171
-        'userManagement'              =>
172
-            array(
173
-                'class'   => PageUserManagement::class,
174
-                'actions' => array(
175
-                    'approve',
176
-                    'decline',
177
-                    'rename',
178
-                    'editUser',
179
-                    'suspend',
180
-                    'editRoles',
181
-                ),
182
-            ),
183
-        'siteNotice'                  =>
184
-            array(
185
-                'class'   => PageSiteNotice::class,
186
-                'actions' => array(),
187
-            ),
188
-        'emailManagement'             =>
189
-            array(
190
-                'class'   => PageEmailManagement::class,
191
-                'actions' => array('create', 'edit', 'view'),
192
-            ),
193
-        'jobQueue'                    =>
194
-            array(
195
-                'class'   => PageJobQueue::class,
196
-                'actions' => array('acknowledge', 'requeue', 'view', 'all'),
197
-            ),
198
-
199
-        //////////////////////////////////////////////////////////////////////////////////////////////////
200
-        // Personal preferences
201
-        'preferences'                 =>
202
-            array(
203
-                'class'   => PagePreferences::class,
204
-                'actions' => array(
205
-                    'refreshOAuth'
206
-                ),
207
-            ),
208
-        'changePassword'              =>
209
-            array(
210
-                'class'   => PageChangePassword::class,
211
-                'actions' => array(),
212
-            ),
213
-        'multiFactor'                 =>
214
-            array(
215
-                'class'   => PageMultiFactor::class,
216
-                'actions' => array(
217
-                    'scratch',
218
-                    'enableYubikeyOtp',
219
-                    'disableYubikeyOtp',
220
-                    'enableTotp',
221
-                    'disableTotp',
222
-                    'enableU2F',
223
-                    'disableU2F',
224
-                ),
225
-            ),
226
-        'oauth'                       =>
227
-            array(
228
-                'class'   => PageOAuth::class,
229
-                'actions' => array('detach', 'attach'),
230
-            ),
231
-        'oauth/callback'              =>
232
-            array(
233
-                'class'   => PageOAuthCallback::class,
234
-                'actions' => array('authorise', 'create'),
235
-            ),
236
-
237
-        //////////////////////////////////////////////////////////////////////////////////////////////////
238
-        // Welcomer configuration
239
-        'welcomeTemplates'            =>
240
-            array(
241
-                'class'   => PageWelcomeTemplateManagement::class,
242
-                'actions' => array('select', 'edit', 'delete', 'add', 'view'),
243
-            ),
244
-
245
-        //////////////////////////////////////////////////////////////////////////////////////////////////
246
-        // Statistics
247
-        'statistics'                  =>
248
-            array(
249
-                'class'   => StatsMain::class,
250
-                'actions' => array(),
251
-            ),
252
-        'statistics/fastCloses'       =>
253
-            array(
254
-                'class'   => StatsFastCloses::class,
255
-                'actions' => array(),
256
-            ),
257
-        'statistics/inactiveUsers'    =>
258
-            array(
259
-                'class'   => StatsInactiveUsers::class,
260
-                'actions' => array(),
261
-            ),
262
-        'statistics/monthlyStats'     =>
263
-            array(
264
-                'class'   => StatsMonthlyStats::class,
265
-                'actions' => array(),
266
-            ),
267
-        'statistics/reservedRequests' =>
268
-            array(
269
-                'class'   => StatsReservedRequests::class,
270
-                'actions' => array(),
271
-            ),
272
-        'statistics/templateStats'    =>
273
-            array(
274
-                'class'   => StatsTemplateStats::class,
275
-                'actions' => array(),
276
-            ),
277
-        'statistics/topCreators'      =>
278
-            array(
279
-                'class'   => StatsTopCreators::class,
280
-                'actions' => array(),
281
-            ),
282
-        'statistics/users'            =>
283
-            array(
284
-                'class'   => StatsUsers::class,
285
-                'actions' => array('detail'),
286
-            ),
287
-
288
-        //////////////////////////////////////////////////////////////////////////////////////////////////
289
-        // Zoom page
290
-        'viewRequest'                 =>
291
-            array(
292
-                'class'   => PageViewRequest::class,
293
-                'actions' => array(),
294
-            ),
295
-        'viewRequest/reserve'         =>
296
-            array(
297
-                'class'   => PageReservation::class,
298
-                'actions' => array(),
299
-            ),
300
-        'viewRequest/breakReserve'    =>
301
-            array(
302
-                'class'   => PageBreakReservation::class,
303
-                'actions' => array(),
304
-            ),
305
-        'viewRequest/defer'           =>
306
-            array(
307
-                'class'   => PageDeferRequest::class,
308
-                'actions' => array(),
309
-            ),
310
-        'viewRequest/comment'         =>
311
-            array(
312
-                'class'   => PageComment::class,
313
-                'actions' => array(),
314
-            ),
315
-        'viewRequest/sendToUser'      =>
316
-            array(
317
-                'class'   => PageSendToUser::class,
318
-                'actions' => array(),
319
-            ),
320
-        'viewRequest/close'           =>
321
-            array(
322
-                'class'   => PageCloseRequest::class,
323
-                'actions' => array(),
324
-            ),
325
-        'viewRequest/create'          =>
326
-            array(
327
-                'class'   => PageCreateRequest::class,
328
-                'actions' => array(),
329
-            ),
330
-        'viewRequest/drop'            =>
331
-            array(
332
-                'class'   => PageDropRequest::class,
333
-                'actions' => array(),
334
-            ),
335
-        'viewRequest/custom'          =>
336
-            array(
337
-                'class'   => PageCustomClose::class,
338
-                'actions' => array(),
339
-            ),
340
-        'editComment'                 =>
341
-            array(
342
-                'class'   => PageEditComment::class,
343
-                'actions' => array(),
344
-            ),
345
-
346
-        //////////////////////////////////////////////////////////////////////////////////////////////////
347
-        // Misc stuff
348
-        'team'                        =>
349
-            array(
350
-                'class'   => PageTeam::class,
351
-                'actions' => array(),
352
-            ),
353
-        'requestList'                 =>
354
-            array(
355
-                'class'   => PageExpandedRequestList::class,
356
-                'actions' => array(),
357
-            ),
358
-        'xffdemo'                     =>
359
-            array(
360
-                'class'   => PageXffDemo::class,
361
-                'actions' => array(),
362
-            ),
363
-    );
364
-
365
-    /**
366
-     * @return IRoutedTask
367
-     * @throws Exception
368
-     */
369
-    final public function route()
370
-    {
371
-        $pathInfo = WebRequest::pathInfo();
372
-
373
-        list($pageClass, $action) = $this->getRouteFromPath($pathInfo);
374
-
375
-        /** @var IRoutedTask $page */
376
-        $page = new $pageClass();
377
-
378
-        // Dynamic creation, so we've got to be careful here. We can't use built-in language type protection, so
379
-        // let's use our own.
380
-        if (!($page instanceof IRoutedTask)) {
381
-            throw new Exception('Expected a page, but this is not a page.');
382
-        }
383
-
384
-        // OK, I'm happy at this point that we know we're running a page, and we know it's probably what we want if it
385
-        // inherits PageBase and has been created from the routing map.
386
-        $page->setRoute($action);
387
-
388
-        return $page;
389
-    }
390
-
391
-    /**
392
-     * @param $pathInfo
393
-     *
394
-     * @return array
395
-     */
396
-    protected function getRouteFromPath($pathInfo)
397
-    {
398
-        if (count($pathInfo) === 0) {
399
-            // No pathInfo, so no page to load. Load the main page.
400
-            return $this->getDefaultRoute();
401
-        }
402
-        elseif (count($pathInfo) === 1) {
403
-            // Exactly one path info segment, it's got to be a page.
404
-            $classSegment = $pathInfo[0];
405
-
406
-            return $this->routeSinglePathSegment($classSegment);
407
-        }
408
-
409
-        // OK, we have two or more segments now.
410
-        if (count($pathInfo) > 2) {
411
-            // Let's handle more than two, and collapse it down into two.
412
-            $requestedAction = array_pop($pathInfo);
413
-            $classSegment = implode('/', $pathInfo);
414
-        }
415
-        else {
416
-            // Two path info segments.
417
-            $classSegment = $pathInfo[0];
418
-            $requestedAction = $pathInfo[1];
419
-        }
420
-
421
-        $routeMap = $this->routePathSegments($classSegment, $requestedAction);
422
-
423
-        if ($routeMap[0] === Page404::class) {
424
-            $routeMap = $this->routeSinglePathSegment($classSegment . '/' . $requestedAction);
425
-        }
426
-
427
-        return $routeMap;
428
-    }
429
-
430
-    /**
431
-     * @param $classSegment
432
-     *
433
-     * @return array
434
-     */
435
-    final protected function routeSinglePathSegment($classSegment)
436
-    {
437
-        $routeMap = $this->getRouteMap();
438
-        if (array_key_exists($classSegment, $routeMap)) {
439
-            // Route exists, but we don't have an action in path info, so default to main.
440
-            $pageClass = $routeMap[$classSegment]['class'];
441
-            $action = 'main';
442
-
443
-            return array($pageClass, $action);
444
-        }
445
-        else {
446
-            // Doesn't exist in map. Fall back to 404
447
-            $pageClass = Page404::class;
448
-            $action = "main";
449
-
450
-            return array($pageClass, $action);
451
-        }
452
-    }
453
-
454
-    /**
455
-     * @param $classSegment
456
-     * @param $requestedAction
457
-     *
458
-     * @return array
459
-     */
460
-    final protected function routePathSegments($classSegment, $requestedAction)
461
-    {
462
-        $routeMap = $this->getRouteMap();
463
-        if (array_key_exists($classSegment, $routeMap)) {
464
-            // Route exists, but we don't have an action in path info, so default to main.
465
-
466
-            if (isset($routeMap[$classSegment]['actions'])
467
-                && array_search($requestedAction, $routeMap[$classSegment]['actions']) !== false
468
-            ) {
469
-                // Action exists in allowed action list. Allow both the page and the action
470
-                $pageClass = $routeMap[$classSegment]['class'];
471
-                $action = $requestedAction;
472
-
473
-                return array($pageClass, $action);
474
-            }
475
-            else {
476
-                // Valid page, invalid action. 404 our way out.
477
-                $pageClass = Page404::class;
478
-                $action = 'main';
479
-
480
-                return array($pageClass, $action);
481
-            }
482
-        }
483
-        else {
484
-            // Class doesn't exist in map. Fall back to 404
485
-            $pageClass = Page404::class;
486
-            $action = 'main';
487
-
488
-            return array($pageClass, $action);
489
-        }
490
-    }
491
-
492
-    /**
493
-     * @return array
494
-     */
495
-    protected function getRouteMap()
496
-    {
497
-        return $this->routeMap;
498
-    }
499
-
500
-    /**
501
-     * @return callable
502
-     */
503
-    protected function getDefaultRoute()
504
-    {
505
-        return array(PageMain::class, "main");
506
-    }
66
+	/**
67
+	 * This is the core routing table for the application. The basic idea is:
68
+	 *
69
+	 *      array(
70
+	 *          "foo" =>
71
+	 *              array(
72
+	 *                  "class"   => PageFoo::class,
73
+	 *                  "actions" => array("bar", "other")
74
+	 *              ),
75
+	 * );
76
+	 *
77
+	 * Things to note:
78
+	 *     - If no page is requested, we go to PageMain. PageMain can't have actions defined.
79
+	 *
80
+	 *     - If a page is defined and requested, but no action is requested, go to that page's main() method
81
+	 *     - If a page is defined and requested, and an action is defined and requested, go to that action's method.
82
+	 *     - If a page is defined and requested, and an action NOT defined and requested, go to Page404 and it's main()
83
+	 *       method.
84
+	 *     - If a page is NOT defined and requested, go to Page404 and it's main() method.
85
+	 *
86
+	 *     - Query parameters are ignored.
87
+	 *
88
+	 * The key point here is request routing with validation that this is allowed, before we start hitting the
89
+	 * filesystem through the AutoLoader, and opening random files. Also, so that we validate the action requested
90
+	 * before we start calling random methods through the web UI.
91
+	 *
92
+	 * Examples:
93
+	 * /internal.php                => returns instance of PageMain, routed to main()
94
+	 * /internal.php?query          => returns instance of PageMain, routed to main()
95
+	 * /internal.php/foo            => returns instance of PageFoo, routed to main()
96
+	 * /internal.php/foo?query      => returns instance of PageFoo, routed to main()
97
+	 * /internal.php/foo/bar        => returns instance of PageFoo, routed to bar()
98
+	 * /internal.php/foo/bar?query  => returns instance of PageFoo, routed to bar()
99
+	 * /internal.php/foo/baz        => returns instance of Page404, routed to main()
100
+	 * /internal.php/foo/baz?query  => returns instance of Page404, routed to main()
101
+	 * /internal.php/bar            => returns instance of Page404, routed to main()
102
+	 * /internal.php/bar?query      => returns instance of Page404, routed to main()
103
+	 * /internal.php/bar/baz        => returns instance of Page404, routed to main()
104
+	 * /internal.php/bar/baz?query  => returns instance of Page404, routed to main()
105
+	 *
106
+	 * Take care when changing this - a lot of places rely on the array key for redirects and other links. If you need
107
+	 * to change the key, then you'll likely have to update a lot of files.
108
+	 *
109
+	 * @var array
110
+	 */
111
+	private $routeMap = array(
112
+
113
+		//////////////////////////////////////////////////////////////////////////////////////////////////
114
+		// Login and registration
115
+		'logout'                      =>
116
+			array(
117
+				'class'   => PageLogout::class,
118
+				'actions' => array(),
119
+			),
120
+		'login'                       =>
121
+			array(
122
+				'class'   => PagePasswordLogin::class,
123
+				'actions' => array(),
124
+			),
125
+		'login/otp'                   =>
126
+			array(
127
+				'class'   => PageOtpLogin::class,
128
+				'actions' => array(),
129
+			),
130
+		'login/u2f'                   =>
131
+			array(
132
+				'class'   => PageU2FLogin::class,
133
+				'actions' => array(),
134
+			),
135
+		'forgotPassword'              =>
136
+			array(
137
+				'class'   => PageForgotPassword::class,
138
+				'actions' => array('reset'),
139
+			),
140
+		'register'                    =>
141
+			array(
142
+				'class'   => PageRegisterOption::class,
143
+				'actions' => array(),
144
+			),
145
+		'register/standard'           =>
146
+			array(
147
+				'class'   => PageRegisterStandard::class,
148
+				'actions' => array('done'),
149
+			),
150
+
151
+		//////////////////////////////////////////////////////////////////////////////////////////////////
152
+		// Discovery
153
+		'search'                      =>
154
+			array(
155
+				'class'   => PageSearch::class,
156
+				'actions' => array(),
157
+			),
158
+		'logs'                        =>
159
+			array(
160
+				'class'   => PageLog::class,
161
+				'actions' => array(),
162
+			),
163
+
164
+		//////////////////////////////////////////////////////////////////////////////////////////////////
165
+		// Administration
166
+		'bans'                        =>
167
+			array(
168
+				'class'   => PageBan::class,
169
+				'actions' => array('set', 'remove'),
170
+			),
171
+		'userManagement'              =>
172
+			array(
173
+				'class'   => PageUserManagement::class,
174
+				'actions' => array(
175
+					'approve',
176
+					'decline',
177
+					'rename',
178
+					'editUser',
179
+					'suspend',
180
+					'editRoles',
181
+				),
182
+			),
183
+		'siteNotice'                  =>
184
+			array(
185
+				'class'   => PageSiteNotice::class,
186
+				'actions' => array(),
187
+			),
188
+		'emailManagement'             =>
189
+			array(
190
+				'class'   => PageEmailManagement::class,
191
+				'actions' => array('create', 'edit', 'view'),
192
+			),
193
+		'jobQueue'                    =>
194
+			array(
195
+				'class'   => PageJobQueue::class,
196
+				'actions' => array('acknowledge', 'requeue', 'view', 'all'),
197
+			),
198
+
199
+		//////////////////////////////////////////////////////////////////////////////////////////////////
200
+		// Personal preferences
201
+		'preferences'                 =>
202
+			array(
203
+				'class'   => PagePreferences::class,
204
+				'actions' => array(
205
+					'refreshOAuth'
206
+				),
207
+			),
208
+		'changePassword'              =>
209
+			array(
210
+				'class'   => PageChangePassword::class,
211
+				'actions' => array(),
212
+			),
213
+		'multiFactor'                 =>
214
+			array(
215
+				'class'   => PageMultiFactor::class,
216
+				'actions' => array(
217
+					'scratch',
218
+					'enableYubikeyOtp',
219
+					'disableYubikeyOtp',
220
+					'enableTotp',
221
+					'disableTotp',
222
+					'enableU2F',
223
+					'disableU2F',
224
+				),
225
+			),
226
+		'oauth'                       =>
227
+			array(
228
+				'class'   => PageOAuth::class,
229
+				'actions' => array('detach', 'attach'),
230
+			),
231
+		'oauth/callback'              =>
232
+			array(
233
+				'class'   => PageOAuthCallback::class,
234
+				'actions' => array('authorise', 'create'),
235
+			),
236
+
237
+		//////////////////////////////////////////////////////////////////////////////////////////////////
238
+		// Welcomer configuration
239
+		'welcomeTemplates'            =>
240
+			array(
241
+				'class'   => PageWelcomeTemplateManagement::class,
242
+				'actions' => array('select', 'edit', 'delete', 'add', 'view'),
243
+			),
244
+
245
+		//////////////////////////////////////////////////////////////////////////////////////////////////
246
+		// Statistics
247
+		'statistics'                  =>
248
+			array(
249
+				'class'   => StatsMain::class,
250
+				'actions' => array(),
251
+			),
252
+		'statistics/fastCloses'       =>
253
+			array(
254
+				'class'   => StatsFastCloses::class,
255
+				'actions' => array(),
256
+			),
257
+		'statistics/inactiveUsers'    =>
258
+			array(
259
+				'class'   => StatsInactiveUsers::class,
260
+				'actions' => array(),
261
+			),
262
+		'statistics/monthlyStats'     =>
263
+			array(
264
+				'class'   => StatsMonthlyStats::class,
265
+				'actions' => array(),
266
+			),
267
+		'statistics/reservedRequests' =>
268
+			array(
269
+				'class'   => StatsReservedRequests::class,
270
+				'actions' => array(),
271
+			),
272
+		'statistics/templateStats'    =>
273
+			array(
274
+				'class'   => StatsTemplateStats::class,
275
+				'actions' => array(),
276
+			),
277
+		'statistics/topCreators'      =>
278
+			array(
279
+				'class'   => StatsTopCreators::class,
280
+				'actions' => array(),
281
+			),
282
+		'statistics/users'            =>
283
+			array(
284
+				'class'   => StatsUsers::class,
285
+				'actions' => array('detail'),
286
+			),
287
+
288
+		//////////////////////////////////////////////////////////////////////////////////////////////////
289
+		// Zoom page
290
+		'viewRequest'                 =>
291
+			array(
292
+				'class'   => PageViewRequest::class,
293
+				'actions' => array(),
294
+			),
295
+		'viewRequest/reserve'         =>
296
+			array(
297
+				'class'   => PageReservation::class,
298
+				'actions' => array(),
299
+			),
300
+		'viewRequest/breakReserve'    =>
301
+			array(
302
+				'class'   => PageBreakReservation::class,
303
+				'actions' => array(),
304
+			),
305
+		'viewRequest/defer'           =>
306
+			array(
307
+				'class'   => PageDeferRequest::class,
308
+				'actions' => array(),
309
+			),
310
+		'viewRequest/comment'         =>
311
+			array(
312
+				'class'   => PageComment::class,
313
+				'actions' => array(),
314
+			),
315
+		'viewRequest/sendToUser'      =>
316
+			array(
317
+				'class'   => PageSendToUser::class,
318
+				'actions' => array(),
319
+			),
320
+		'viewRequest/close'           =>
321
+			array(
322
+				'class'   => PageCloseRequest::class,
323
+				'actions' => array(),
324
+			),
325
+		'viewRequest/create'          =>
326
+			array(
327
+				'class'   => PageCreateRequest::class,
328
+				'actions' => array(),
329
+			),
330
+		'viewRequest/drop'            =>
331
+			array(
332
+				'class'   => PageDropRequest::class,
333
+				'actions' => array(),
334
+			),
335
+		'viewRequest/custom'          =>
336
+			array(
337
+				'class'   => PageCustomClose::class,
338
+				'actions' => array(),
339
+			),
340
+		'editComment'                 =>
341
+			array(
342
+				'class'   => PageEditComment::class,
343
+				'actions' => array(),
344
+			),
345
+
346
+		//////////////////////////////////////////////////////////////////////////////////////////////////
347
+		// Misc stuff
348
+		'team'                        =>
349
+			array(
350
+				'class'   => PageTeam::class,
351
+				'actions' => array(),
352
+			),
353
+		'requestList'                 =>
354
+			array(
355
+				'class'   => PageExpandedRequestList::class,
356
+				'actions' => array(),
357
+			),
358
+		'xffdemo'                     =>
359
+			array(
360
+				'class'   => PageXffDemo::class,
361
+				'actions' => array(),
362
+			),
363
+	);
364
+
365
+	/**
366
+	 * @return IRoutedTask
367
+	 * @throws Exception
368
+	 */
369
+	final public function route()
370
+	{
371
+		$pathInfo = WebRequest::pathInfo();
372
+
373
+		list($pageClass, $action) = $this->getRouteFromPath($pathInfo);
374
+
375
+		/** @var IRoutedTask $page */
376
+		$page = new $pageClass();
377
+
378
+		// Dynamic creation, so we've got to be careful here. We can't use built-in language type protection, so
379
+		// let's use our own.
380
+		if (!($page instanceof IRoutedTask)) {
381
+			throw new Exception('Expected a page, but this is not a page.');
382
+		}
383
+
384
+		// OK, I'm happy at this point that we know we're running a page, and we know it's probably what we want if it
385
+		// inherits PageBase and has been created from the routing map.
386
+		$page->setRoute($action);
387
+
388
+		return $page;
389
+	}
390
+
391
+	/**
392
+	 * @param $pathInfo
393
+	 *
394
+	 * @return array
395
+	 */
396
+	protected function getRouteFromPath($pathInfo)
397
+	{
398
+		if (count($pathInfo) === 0) {
399
+			// No pathInfo, so no page to load. Load the main page.
400
+			return $this->getDefaultRoute();
401
+		}
402
+		elseif (count($pathInfo) === 1) {
403
+			// Exactly one path info segment, it's got to be a page.
404
+			$classSegment = $pathInfo[0];
405
+
406
+			return $this->routeSinglePathSegment($classSegment);
407
+		}
408
+
409
+		// OK, we have two or more segments now.
410
+		if (count($pathInfo) > 2) {
411
+			// Let's handle more than two, and collapse it down into two.
412
+			$requestedAction = array_pop($pathInfo);
413
+			$classSegment = implode('/', $pathInfo);
414
+		}
415
+		else {
416
+			// Two path info segments.
417
+			$classSegment = $pathInfo[0];
418
+			$requestedAction = $pathInfo[1];
419
+		}
420
+
421
+		$routeMap = $this->routePathSegments($classSegment, $requestedAction);
422
+
423
+		if ($routeMap[0] === Page404::class) {
424
+			$routeMap = $this->routeSinglePathSegment($classSegment . '/' . $requestedAction);
425
+		}
426
+
427
+		return $routeMap;
428
+	}
429
+
430
+	/**
431
+	 * @param $classSegment
432
+	 *
433
+	 * @return array
434
+	 */
435
+	final protected function routeSinglePathSegment($classSegment)
436
+	{
437
+		$routeMap = $this->getRouteMap();
438
+		if (array_key_exists($classSegment, $routeMap)) {
439
+			// Route exists, but we don't have an action in path info, so default to main.
440
+			$pageClass = $routeMap[$classSegment]['class'];
441
+			$action = 'main';
442
+
443
+			return array($pageClass, $action);
444
+		}
445
+		else {
446
+			// Doesn't exist in map. Fall back to 404
447
+			$pageClass = Page404::class;
448
+			$action = "main";
449
+
450
+			return array($pageClass, $action);
451
+		}
452
+	}
453
+
454
+	/**
455
+	 * @param $classSegment
456
+	 * @param $requestedAction
457
+	 *
458
+	 * @return array
459
+	 */
460
+	final protected function routePathSegments($classSegment, $requestedAction)
461
+	{
462
+		$routeMap = $this->getRouteMap();
463
+		if (array_key_exists($classSegment, $routeMap)) {
464
+			// Route exists, but we don't have an action in path info, so default to main.
465
+
466
+			if (isset($routeMap[$classSegment]['actions'])
467
+				&& array_search($requestedAction, $routeMap[$classSegment]['actions']) !== false
468
+			) {
469
+				// Action exists in allowed action list. Allow both the page and the action
470
+				$pageClass = $routeMap[$classSegment]['class'];
471
+				$action = $requestedAction;
472
+
473
+				return array($pageClass, $action);
474
+			}
475
+			else {
476
+				// Valid page, invalid action. 404 our way out.
477
+				$pageClass = Page404::class;
478
+				$action = 'main';
479
+
480
+				return array($pageClass, $action);
481
+			}
482
+		}
483
+		else {
484
+			// Class doesn't exist in map. Fall back to 404
485
+			$pageClass = Page404::class;
486
+			$action = 'main';
487
+
488
+			return array($pageClass, $action);
489
+		}
490
+	}
491
+
492
+	/**
493
+	 * @return array
494
+	 */
495
+	protected function getRouteMap()
496
+	{
497
+		return $this->routeMap;
498
+	}
499
+
500
+	/**
501
+	 * @return callable
502
+	 */
503
+	protected function getDefaultRoute()
504
+	{
505
+		return array(PageMain::class, "main");
506
+	}
507 507
 }
Please login to merge, or discard this patch.
includes/Router/PublicRequestRouter.php 1 patch
Indentation   +37 added lines, -37 removed lines patch added patch discarded remove patch
@@ -15,42 +15,42 @@
 block discarded – undo
15 15
 
16 16
 class PublicRequestRouter extends RequestRouter
17 17
 {
18
-    /**
19
-     * Gets the route map to be used by this request router.
20
-     *
21
-     * @return array
22
-     */
23
-    protected function getRouteMap()
24
-    {
25
-        return array(
26
-            // Page showing a message stating the request has been submitted to our internal queues
27
-            'requestSubmitted'          =>
28
-                array(
29
-                    'class'   => PageRequestSubmitted::class,
30
-                    'actions' => array(),
31
-                ),
32
-            // Page showing a message stating that email confirmation is required to continue
33
-            'emailConfirmationRequired' =>
34
-                array(
35
-                    'class'   => PageEmailConfirmationRequired::class,
36
-                    'actions' => array(),
37
-                ),
38
-            // Action page which handles email confirmation
39
-            'confirmEmail'              =>
40
-                array(
41
-                    'class'   => PageConfirmEmail::class,
42
-                    'actions' => array(),
43
-                ),
44
-        );
45
-    }
18
+	/**
19
+	 * Gets the route map to be used by this request router.
20
+	 *
21
+	 * @return array
22
+	 */
23
+	protected function getRouteMap()
24
+	{
25
+		return array(
26
+			// Page showing a message stating the request has been submitted to our internal queues
27
+			'requestSubmitted'          =>
28
+				array(
29
+					'class'   => PageRequestSubmitted::class,
30
+					'actions' => array(),
31
+				),
32
+			// Page showing a message stating that email confirmation is required to continue
33
+			'emailConfirmationRequired' =>
34
+				array(
35
+					'class'   => PageEmailConfirmationRequired::class,
36
+					'actions' => array(),
37
+				),
38
+			// Action page which handles email confirmation
39
+			'confirmEmail'              =>
40
+				array(
41
+					'class'   => PageConfirmEmail::class,
42
+					'actions' => array(),
43
+				),
44
+		);
45
+	}
46 46
 
47
-    /**
48
-     * Gets the default route if no explicit route is requested.
49
-     *
50
-     * @return callable
51
-     */
52
-    protected function getDefaultRoute()
53
-    {
54
-        return array(PageRequestAccount::class, 'main');
55
-    }
47
+	/**
48
+	 * Gets the default route if no explicit route is requested.
49
+	 *
50
+	 * @return callable
51
+	 */
52
+	protected function getDefaultRoute()
53
+	{
54
+		return array(PageRequestAccount::class, 'main');
55
+	}
56 56
 }
57 57
\ No newline at end of file
Please login to merge, or discard this patch.