Completed
Push — master ( f378ea...7d2147 )
by Robbie
9s
created
src/Extension/LoginAttemptNotifications.php 2 patches
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -53,12 +53,12 @@  discard block
 block discarded – undo
53 53
                 $failures = $meantimeLoginAttempts->filter(['Status' => 'Failure'])->count();
54 54
                 $IPs = array_unique($meantimeLoginAttempts->column('IP'));
55 55
 
56
-                if ($attempts == 1) {
56
+                if ($attempts==1) {
57 57
                     $statusString = $failures ? "a failed" : "a successful";
58 58
                     $message = "In the last $elapsed $statusString login attempt to your account was "
59 59
                         . "registered. The attempt was made from ${IPs[0]}. ";
60 60
                 } else {
61
-                    if ($failures == $attempts) {
61
+                    if ($failures==$attempts) {
62 62
                         $statusString = $failures ? "failed" : "successful";
63 63
                         $message = "In the last $elapsed $attempts $statusString login "
64 64
                             . "attempts to your account were registered. ";
@@ -85,7 +85,7 @@  discard block
 block discarded – undo
85 85
                 $date = $lastLoginAttempt->Created;
86 86
                 $message = "Last login attempt to your account was on $lastLoginAttempt->Created "
87 87
                     . "from $lastLoginAttempt->IP";
88
-                $message .= $lastLoginAttempt->Status == 'Failure' ? " and was successful." : "and has failed.";
88
+                $message .= $lastLoginAttempt->Status=='Failure' ? " and was successful." : "and has failed.";
89 89
             }
90 90
         }
91 91
 
Please login to merge, or discard this patch.
Indentation   +66 added lines, -66 removed lines patch added patch discarded remove patch
@@ -19,81 +19,81 @@
 block discarded – undo
19 19
 class LoginAttemptNotifications extends Extension
20 20
 {
21 21
 
22
-    /**
23
-     *
24
-     * @return mixed null
25
-     */
26
-    public function init()
27
-    {
22
+	/**
23
+	 *
24
+	 * @return mixed null
25
+	 */
26
+	public function init()
27
+	{
28 28
 
29
-        // Exclude default admin.
30
-        $member = Security::getCurrentUser();
31
-        if (!$member || !$member->ID) {
32
-            return;
33
-        }
29
+		// Exclude default admin.
30
+		$member = Security::getCurrentUser();
31
+		if (!$member || !$member->ID) {
32
+			return;
33
+		}
34 34
 
35
-        $message = null;
36
-        $session = $this->owner->getRequest()->getSession();
35
+		$message = null;
36
+		$session = $this->owner->getRequest()->getSession();
37 37
 
38
-        Requirements::javascript('cwp/cwp-core:javascript/LoginAttemptNotifications.js');
39
-        $sessionLastVisited = $session->get('LoginAttemptNotifications.SessionLastVisited');
40
-        if ($sessionLastVisited) {
41
-            // Session already in progress. Show all attempts since the session was last visited.
38
+		Requirements::javascript('cwp/cwp-core:javascript/LoginAttemptNotifications.js');
39
+		$sessionLastVisited = $session->get('LoginAttemptNotifications.SessionLastVisited');
40
+		if ($sessionLastVisited) {
41
+			// Session already in progress. Show all attempts since the session was last visited.
42 42
 
43
-            $meantimeLoginAttempts = LoginAttempt::get()->filter([
44
-                'MemberID' => $member->ID,
45
-                'Created:GreaterThan' => $sessionLastVisited
46
-            ]);
43
+			$meantimeLoginAttempts = LoginAttempt::get()->filter([
44
+				'MemberID' => $member->ID,
45
+				'Created:GreaterThan' => $sessionLastVisited
46
+			]);
47 47
 
48
-            $attempts = $meantimeLoginAttempts->count();
49
-            if ($attempts) {
50
-                $lastVisitedObj = DBDatetime::create();
51
-                $lastVisitedObj->setValue($sessionLastVisited);
52
-                $elapsed = $lastVisitedObj->TimeDiff();
53
-                $failures = $meantimeLoginAttempts->filter(['Status' => 'Failure'])->count();
54
-                $IPs = array_unique($meantimeLoginAttempts->column('IP'));
48
+			$attempts = $meantimeLoginAttempts->count();
49
+			if ($attempts) {
50
+				$lastVisitedObj = DBDatetime::create();
51
+				$lastVisitedObj->setValue($sessionLastVisited);
52
+				$elapsed = $lastVisitedObj->TimeDiff();
53
+				$failures = $meantimeLoginAttempts->filter(['Status' => 'Failure'])->count();
54
+				$IPs = array_unique($meantimeLoginAttempts->column('IP'));
55 55
 
56
-                if ($attempts == 1) {
57
-                    $statusString = $failures ? "a failed" : "a successful";
58
-                    $message = "In the last $elapsed $statusString login attempt to your account was "
59
-                        . "registered. The attempt was made from ${IPs[0]}. ";
60
-                } else {
61
-                    if ($failures == $attempts) {
62
-                        $statusString = $failures ? "failed" : "successful";
63
-                        $message = "In the last $elapsed $attempts $statusString login "
64
-                            . "attempts to your account were registered. ";
65
-                    } else {
66
-                        $message = "In the last $elapsed $attempts login attempts to your "
67
-                            . "account were registered, of which $failures failed. ";
68
-                    }
56
+				if ($attempts == 1) {
57
+					$statusString = $failures ? "a failed" : "a successful";
58
+					$message = "In the last $elapsed $statusString login attempt to your account was "
59
+						. "registered. The attempt was made from ${IPs[0]}. ";
60
+				} else {
61
+					if ($failures == $attempts) {
62
+						$statusString = $failures ? "failed" : "successful";
63
+						$message = "In the last $elapsed $attempts $statusString login "
64
+							. "attempts to your account were registered. ";
65
+					} else {
66
+						$message = "In the last $elapsed $attempts login attempts to your "
67
+							. "account were registered, of which $failures failed. ";
68
+					}
69 69
 
70
-                    $message .= "The attempts were from " . implode(', ', $IPs) . '. ';
70
+					$message .= "The attempts were from " . implode(', ', $IPs) . '. ';
71 71
 
72
-                    // TODO: add this call to action in a way that doesn't break out of the availabel space. Fix CSS?
73
-                    // $message .= "If you suspect somebody else might be trying to access
74
-                    //  . "your account, please contact support.";
75
-                }
76
-            }
77
-        } else {
78
-            // New session - show last login attempt.
79
-            // TODO: this currently does NOT surface to the frontend in any way.
80
-            $lastLoginAttempt = LoginAttempt::get()->filter([
81
-                        'MemberID' => $member->ID
82
-            ])->sort('Created DESC')->First();
72
+					// TODO: add this call to action in a way that doesn't break out of the availabel space. Fix CSS?
73
+					// $message .= "If you suspect somebody else might be trying to access
74
+					//  . "your account, please contact support.";
75
+				}
76
+			}
77
+		} else {
78
+			// New session - show last login attempt.
79
+			// TODO: this currently does NOT surface to the frontend in any way.
80
+			$lastLoginAttempt = LoginAttempt::get()->filter([
81
+						'MemberID' => $member->ID
82
+			])->sort('Created DESC')->First();
83 83
 
84
-            if ($lastLoginAttempt) {
85
-                $date = $lastLoginAttempt->Created;
86
-                $message = "Last login attempt to your account was on $lastLoginAttempt->Created "
87
-                    . "from $lastLoginAttempt->IP";
88
-                $message .= $lastLoginAttempt->Status == 'Failure' ? " and was successful." : "and has failed.";
89
-            }
90
-        }
84
+			if ($lastLoginAttempt) {
85
+				$date = $lastLoginAttempt->Created;
86
+				$message = "Last login attempt to your account was on $lastLoginAttempt->Created "
87
+					. "from $lastLoginAttempt->IP";
88
+				$message .= $lastLoginAttempt->Status == 'Failure' ? " and was successful." : "and has failed.";
89
+			}
90
+		}
91 91
 
92
-        $session->set(
93
-            'LoginAttemptNotifications.SessionLastVisited',
94
-            DBDatetime::now()->Format(DBDatetime::ISO_DATETIME)
95
-        );
92
+		$session->set(
93
+			'LoginAttemptNotifications.SessionLastVisited',
94
+			DBDatetime::now()->Format(DBDatetime::ISO_DATETIME)
95
+		);
96 96
 
97
-        $this->owner->getResponse()->addHeader('X-LoginAttemptNotifications', $message);
98
-    }
97
+		$this->owner->getResponse()->addHeader('X-LoginAttemptNotifications', $message);
98
+	}
99 99
 }
Please login to merge, or discard this patch.
src/Feed/CwpAtomFeed.php 1 patch
Indentation   +49 added lines, -49 removed lines patch added patch discarded remove patch
@@ -18,57 +18,57 @@
 block discarded – undo
18 18
 
19 19
 class CwpAtomFeed extends RSSFeed
20 20
 {
21
-    public function __construct(
22
-        SS_List $entries,
23
-        $link,
24
-        $title,
25
-        $description = null,
26
-        $titleField = "Title",
27
-        $descriptionField = "Content",
28
-        $authorField = null,
29
-        $lastModified = null,
30
-        $etag = null
31
-    ) {
32
-        parent::__construct(
33
-            $entries,
34
-            $link,
35
-            $title,
36
-            $description,
37
-            $titleField,
38
-            $descriptionField,
39
-            $authorField,
40
-            $lastModified
41
-        );
21
+	public function __construct(
22
+		SS_List $entries,
23
+		$link,
24
+		$title,
25
+		$description = null,
26
+		$titleField = "Title",
27
+		$descriptionField = "Content",
28
+		$authorField = null,
29
+		$lastModified = null,
30
+		$etag = null
31
+	) {
32
+		parent::__construct(
33
+			$entries,
34
+			$link,
35
+			$title,
36
+			$description,
37
+			$titleField,
38
+			$descriptionField,
39
+			$authorField,
40
+			$lastModified
41
+		);
42 42
 
43
-        $this->setTemplate(__CLASS__);
44
-    }
43
+		$this->setTemplate(__CLASS__);
44
+	}
45 45
 
46
-    /**
47
-     * Include an link to the feed
48
-     *
49
-     * @param string $url URL of the feed
50
-     * @param string $title Title to show
51
-     */
52
-    public static function linkToFeed($url, $title = null)
53
-    {
54
-        $title = Convert::raw2xml($title);
55
-        Requirements::insertHeadTags(
56
-            '<link rel="alternate" type="application/atom+xml" title="' . $title .
57
-            '" href="' . $url . '" />'
58
-        );
59
-    }
46
+	/**
47
+	 * Include an link to the feed
48
+	 *
49
+	 * @param string $url URL of the feed
50
+	 * @param string $title Title to show
51
+	 */
52
+	public static function linkToFeed($url, $title = null)
53
+	{
54
+		$title = Convert::raw2xml($title);
55
+		Requirements::insertHeadTags(
56
+			'<link rel="alternate" type="application/atom+xml" title="' . $title .
57
+			'" href="' . $url . '" />'
58
+		);
59
+	}
60 60
 
61
-    /**
62
-     * Output the feed to the browser
63
-     *
64
-     * @return DBHTMLText
65
-     */
66
-    public function outputToBrowser()
67
-    {
68
-        $output = parent::outputToBrowser();
69
-        $response = Controller::curr()->getResponse();
70
-        $response->addHeader("Content-Type", "application/atom+xml");
61
+	/**
62
+	 * Output the feed to the browser
63
+	 *
64
+	 * @return DBHTMLText
65
+	 */
66
+	public function outputToBrowser()
67
+	{
68
+		$output = parent::outputToBrowser();
69
+		$response = Controller::curr()->getResponse();
70
+		$response->addHeader("Content-Type", "application/atom+xml");
71 71
 
72
-        return $output;
73
-    }
72
+		return $output;
73
+	}
74 74
 }
Please login to merge, or discard this patch.
src/Extension/CwpControllerExtension.php 2 patches
Spacing   +3 added lines, -3 removed lines patch added patch discarded remove patch
@@ -121,7 +121,7 @@  discard block
 block discarded – undo
121 121
         // First, see if we can get a member to act on, either from a changepassword token or the session
122 122
         if (isset($_REQUEST['m']) && isset($_REQUEST['t'])) {
123 123
             /** @var Member $member */
124
-            $member = Member::get()->filter('ID', (int) $_REQUEST['m'])->first();
124
+            $member = Member::get()->filter('ID', (int)$_REQUEST['m'])->first();
125 125
 
126 126
             if (!$member->validateAutoLoginToken($_REQUEST['t'])) {
127 127
                 $member = null;
@@ -135,7 +135,7 @@  discard block
 block discarded – undo
135 135
         }
136 136
 
137 137
         // Then, if they have the right permissions, check the allowed URLs
138
-        $existingMemberCanAccessUAT = $member && $this->callWithSubsitesDisabled(function () use ($member) {
138
+        $existingMemberCanAccessUAT = $member && $this->callWithSubsitesDisabled(function() use ($member) {
139 139
             return Permission::checkMember($member, 'ACCESS_UAT_SERVER');
140 140
         });
141 141
 
@@ -154,7 +154,7 @@  discard block
 block discarded – undo
154 154
 
155 155
         // Finally if they weren't allowed to bypass Basic Auth, trigger it
156 156
         if (!$allowWithoutAuth) {
157
-            $this->callWithSubsitesDisabled(function () use ($request) {
157
+            $this->callWithSubsitesDisabled(function() use ($request) {
158 158
                 BasicAuth::requireLogin(
159 159
                     $request,
160 160
                     _t(__CLASS__ . '.LoginPrompt', "Please log in with your CMS credentials"),
Please login to merge, or discard this patch.
Indentation   +207 added lines, -207 removed lines patch added patch discarded remove patch
@@ -19,211 +19,211 @@
 block discarded – undo
19 19
 class CwpControllerExtension extends Extension implements PermissionProvider
20 20
 {
21 21
 
22
-    /**
23
-     * Enables SSL redirections - disabling not recommended as it will prevent forcing SSL on admin panel.
24
-     *
25
-     * @config
26
-     * @var bool
27
-     */
28
-    private static $ssl_redirection_enabled = true;
29
-
30
-    /**
31
-     * Specify a domain to redirect the vulnerable areas to.
32
-     *
33
-     * If left as null, live instance will set this to <instance-id>.cwp.govt.nz via CWP_SECURE_DOMAIN in _config.php.
34
-     * This allows us to automatically protect vulnerable areas on live even if the frontend cert is not installed.
35
-     *
36
-     * Set to false to redirect to https protocol on current domain (e.g. if you have frontend cert).
37
-     *
38
-     * Set to a domain string (e.g. 'example.com') to force that domain.
39
-     *
40
-     * @config
41
-     * @var string
42
-     */
43
-    private static $ssl_redirection_force_domain = null;
44
-
45
-    /**
46
-     * Enables the BasicAuth protection on all test environments. Disable with caution - it will open up
47
-     * all your UAT and test environments to the world.
48
-     *
49
-     * @config
50
-     * @var bool
51
-     */
52
-    private static $test_basicauth_enabled = true;
53
-
54
-    /**
55
-     * Enables the BasicAuth protection on all live environments.
56
-     * Useful for securing sites prior to public launch.
57
-     *
58
-     * @config
59
-     * @var bool
60
-     */
61
-    private static $live_basicauth_enabled = false;
62
-
63
-    /**
64
-     * This executes the passed callback with subsite filter disabled,
65
-     * then enabled the filter again before returning the callback result
66
-     * (or throwing the exception the callback raised)
67
-     *
68
-     * @param  callback  $callback - The callback to execute
69
-     * @return mixed     The result of the callback
70
-     * @throws Exception Any exception the callback raised
71
-     */
72
-    protected function callWithSubsitesDisabled($callback)
73
-    {
74
-        $rv = null;
75
-
76
-        try {
77
-            if (class_exists(Subsite::class)) {
78
-                Subsite::disable_subsite_filter(true);
79
-            }
80
-
81
-            $rv = call_user_func($callback);
82
-        } catch (Exception $e) {
83
-            if (class_exists(Subsite::class)) {
84
-                Subsite::disable_subsite_filter(false);
85
-            }
86
-
87
-            throw $e;
88
-        }
89
-
90
-        if (class_exists(Subsite::class)) {
91
-            Subsite::disable_subsite_filter(false);
92
-        }
93
-
94
-        return $rv;
95
-    }
96
-
97
-    /**
98
-     * Trigger Basic Auth protection, except when there's a reason to bypass it
99
-     *  - The source IP address is in the comma-seperated string in the constant CWP_IP_BYPASS_BASICAUTH
100
-     *    (so Pingdom, etc, can access the site)
101
-     *  - There is an identifiable member, that member has the ACCESS_UAT_SERVER permission, and they're trying
102
-     *    to access a white-list of URLs (so people following a reset password link can reset their password)
103
-     */
104
-    protected function triggerBasicAuthProtection()
105
-    {
106
-        $allowWithoutAuth = false;
107
-
108
-        // Allow whitelisting IPs for bypassing the basic auth.
109
-        if (Environment::getEnv('CWP_IP_BYPASS_BASICAUTH')) {
110
-            $remote = $_SERVER['REMOTE_ADDR'];
111
-            $bypass = explode(',', Environment::getEnv('CWP_IP_BYPASS_BASICAUTH'));
112
-
113
-            if (in_array($remote, $bypass)) {
114
-                $allowWithoutAuth = true;
115
-            }
116
-        }
117
-
118
-        /** @var HTTPRequest|null $request */
119
-        $request = $this->getRequest();
120
-
121
-        // First, see if we can get a member to act on, either from a changepassword token or the session
122
-        if (isset($_REQUEST['m']) && isset($_REQUEST['t'])) {
123
-            /** @var Member $member */
124
-            $member = Member::get()->filter('ID', (int) $_REQUEST['m'])->first();
125
-
126
-            if (!$member->validateAutoLoginToken($_REQUEST['t'])) {
127
-                $member = null;
128
-            }
129
-        } elseif ($request && $request->getSession()->get('AutoLoginHash')) {
130
-            $member = Member::member_from_autologinhash(
131
-                $request->getSession()->get('AutoLoginHash')
132
-            );
133
-        } else {
134
-            $member = Security::getCurrentUser();
135
-        }
136
-
137
-        // Then, if they have the right permissions, check the allowed URLs
138
-        $existingMemberCanAccessUAT = $member && $this->callWithSubsitesDisabled(function () use ($member) {
139
-            return Permission::checkMember($member, 'ACCESS_UAT_SERVER');
140
-        });
141
-
142
-        if ($existingMemberCanAccessUAT) {
143
-            $allowed = array(
144
-                '/^Security\/changepassword/',
145
-                '/^Security\/ChangePasswordForm/'
146
-            );
147
-
148
-            $relativeURL = Director::makeRelative(Director::absoluteURL($_SERVER['REQUEST_URI']));
149
-
150
-            foreach ($allowed as $pattern) {
151
-                $allowWithoutAuth = $allowWithoutAuth || preg_match($pattern, $relativeURL);
152
-            }
153
-        }
154
-
155
-        // Finally if they weren't allowed to bypass Basic Auth, trigger it
156
-        if (!$allowWithoutAuth) {
157
-            $this->callWithSubsitesDisabled(function () use ($request) {
158
-                BasicAuth::requireLogin(
159
-                    $request,
160
-                    _t(__CLASS__ . '.LoginPrompt', "Please log in with your CMS credentials"),
161
-                    'ACCESS_UAT_SERVER',
162
-                    true
163
-                );
164
-            });
165
-        }
166
-    }
167
-
168
-    /**
169
-     * Get the current request, either from an Injector service or from the current controller
170
-     *
171
-     * @return HTTPRequest|null
172
-     */
173
-    protected function getRequest()
174
-    {
175
-        if (Injector::inst()->has(HTTPRequest::class)) {
176
-            return Injector::inst()->get(HTTPRequest::class);
177
-        }
178
-        return $this->owner->getRequest();
179
-    }
180
-
181
-    /**
182
-     * @return void
183
-     */
184
-    public function onBeforeInit()
185
-    {
186
-        // Grab global injectable service to allow testing.
187
-        $director = Injector::inst()->get(Director::class);
188
-
189
-        if (Config::inst()->get(__CLASS__, 'ssl_redirection_enabled')) {
190
-            // redirect some vulnerable areas to the secure domain
191
-            if (!$director::is_https()) {
192
-                $forceDomain = Config::inst()->get(__CLASS__, 'ssl_redirection_force_domain');
193
-
194
-                if ($forceDomain) {
195
-                    $director::forceSSL(['/^Security/', '/^api/'], $forceDomain);
196
-                } else {
197
-                    $director::forceSSL(['/^Security/', '/^api/']);
198
-                }
199
-            }
200
-        }
201
-
202
-        if (Config::inst()->get(__CLASS__, 'test_basicauth_enabled')) {
203
-            // Turn on Basic Auth in testing mode
204
-            if ($director::isTest()) {
205
-                $this->triggerBasicAuthProtection();
206
-            }
207
-        }
208
-
209
-        if (Config::inst()->get(__CLASS__, 'live_basicauth_enabled')) {
210
-            // Turn on Basic Auth in live mode
211
-            if ($director::isLive()) {
212
-                $this->triggerBasicAuthProtection();
213
-            }
214
-        }
215
-    }
216
-
217
-    /**
218
-     * @return array
219
-     */
220
-    public function providePermissions()
221
-    {
222
-        return [
223
-            'ACCESS_UAT_SERVER' => _t(
224
-                __CLASS__ . '.UatServerPermission',
225
-                'Allow users to use their accounts to access the UAT server'
226
-            )
227
-        ];
228
-    }
22
+	/**
23
+	 * Enables SSL redirections - disabling not recommended as it will prevent forcing SSL on admin panel.
24
+	 *
25
+	 * @config
26
+	 * @var bool
27
+	 */
28
+	private static $ssl_redirection_enabled = true;
29
+
30
+	/**
31
+	 * Specify a domain to redirect the vulnerable areas to.
32
+	 *
33
+	 * If left as null, live instance will set this to <instance-id>.cwp.govt.nz via CWP_SECURE_DOMAIN in _config.php.
34
+	 * This allows us to automatically protect vulnerable areas on live even if the frontend cert is not installed.
35
+	 *
36
+	 * Set to false to redirect to https protocol on current domain (e.g. if you have frontend cert).
37
+	 *
38
+	 * Set to a domain string (e.g. 'example.com') to force that domain.
39
+	 *
40
+	 * @config
41
+	 * @var string
42
+	 */
43
+	private static $ssl_redirection_force_domain = null;
44
+
45
+	/**
46
+	 * Enables the BasicAuth protection on all test environments. Disable with caution - it will open up
47
+	 * all your UAT and test environments to the world.
48
+	 *
49
+	 * @config
50
+	 * @var bool
51
+	 */
52
+	private static $test_basicauth_enabled = true;
53
+
54
+	/**
55
+	 * Enables the BasicAuth protection on all live environments.
56
+	 * Useful for securing sites prior to public launch.
57
+	 *
58
+	 * @config
59
+	 * @var bool
60
+	 */
61
+	private static $live_basicauth_enabled = false;
62
+
63
+	/**
64
+	 * This executes the passed callback with subsite filter disabled,
65
+	 * then enabled the filter again before returning the callback result
66
+	 * (or throwing the exception the callback raised)
67
+	 *
68
+	 * @param  callback  $callback - The callback to execute
69
+	 * @return mixed     The result of the callback
70
+	 * @throws Exception Any exception the callback raised
71
+	 */
72
+	protected function callWithSubsitesDisabled($callback)
73
+	{
74
+		$rv = null;
75
+
76
+		try {
77
+			if (class_exists(Subsite::class)) {
78
+				Subsite::disable_subsite_filter(true);
79
+			}
80
+
81
+			$rv = call_user_func($callback);
82
+		} catch (Exception $e) {
83
+			if (class_exists(Subsite::class)) {
84
+				Subsite::disable_subsite_filter(false);
85
+			}
86
+
87
+			throw $e;
88
+		}
89
+
90
+		if (class_exists(Subsite::class)) {
91
+			Subsite::disable_subsite_filter(false);
92
+		}
93
+
94
+		return $rv;
95
+	}
96
+
97
+	/**
98
+	 * Trigger Basic Auth protection, except when there's a reason to bypass it
99
+	 *  - The source IP address is in the comma-seperated string in the constant CWP_IP_BYPASS_BASICAUTH
100
+	 *    (so Pingdom, etc, can access the site)
101
+	 *  - There is an identifiable member, that member has the ACCESS_UAT_SERVER permission, and they're trying
102
+	 *    to access a white-list of URLs (so people following a reset password link can reset their password)
103
+	 */
104
+	protected function triggerBasicAuthProtection()
105
+	{
106
+		$allowWithoutAuth = false;
107
+
108
+		// Allow whitelisting IPs for bypassing the basic auth.
109
+		if (Environment::getEnv('CWP_IP_BYPASS_BASICAUTH')) {
110
+			$remote = $_SERVER['REMOTE_ADDR'];
111
+			$bypass = explode(',', Environment::getEnv('CWP_IP_BYPASS_BASICAUTH'));
112
+
113
+			if (in_array($remote, $bypass)) {
114
+				$allowWithoutAuth = true;
115
+			}
116
+		}
117
+
118
+		/** @var HTTPRequest|null $request */
119
+		$request = $this->getRequest();
120
+
121
+		// First, see if we can get a member to act on, either from a changepassword token or the session
122
+		if (isset($_REQUEST['m']) && isset($_REQUEST['t'])) {
123
+			/** @var Member $member */
124
+			$member = Member::get()->filter('ID', (int) $_REQUEST['m'])->first();
125
+
126
+			if (!$member->validateAutoLoginToken($_REQUEST['t'])) {
127
+				$member = null;
128
+			}
129
+		} elseif ($request && $request->getSession()->get('AutoLoginHash')) {
130
+			$member = Member::member_from_autologinhash(
131
+				$request->getSession()->get('AutoLoginHash')
132
+			);
133
+		} else {
134
+			$member = Security::getCurrentUser();
135
+		}
136
+
137
+		// Then, if they have the right permissions, check the allowed URLs
138
+		$existingMemberCanAccessUAT = $member && $this->callWithSubsitesDisabled(function () use ($member) {
139
+			return Permission::checkMember($member, 'ACCESS_UAT_SERVER');
140
+		});
141
+
142
+		if ($existingMemberCanAccessUAT) {
143
+			$allowed = array(
144
+				'/^Security\/changepassword/',
145
+				'/^Security\/ChangePasswordForm/'
146
+			);
147
+
148
+			$relativeURL = Director::makeRelative(Director::absoluteURL($_SERVER['REQUEST_URI']));
149
+
150
+			foreach ($allowed as $pattern) {
151
+				$allowWithoutAuth = $allowWithoutAuth || preg_match($pattern, $relativeURL);
152
+			}
153
+		}
154
+
155
+		// Finally if they weren't allowed to bypass Basic Auth, trigger it
156
+		if (!$allowWithoutAuth) {
157
+			$this->callWithSubsitesDisabled(function () use ($request) {
158
+				BasicAuth::requireLogin(
159
+					$request,
160
+					_t(__CLASS__ . '.LoginPrompt', "Please log in with your CMS credentials"),
161
+					'ACCESS_UAT_SERVER',
162
+					true
163
+				);
164
+			});
165
+		}
166
+	}
167
+
168
+	/**
169
+	 * Get the current request, either from an Injector service or from the current controller
170
+	 *
171
+	 * @return HTTPRequest|null
172
+	 */
173
+	protected function getRequest()
174
+	{
175
+		if (Injector::inst()->has(HTTPRequest::class)) {
176
+			return Injector::inst()->get(HTTPRequest::class);
177
+		}
178
+		return $this->owner->getRequest();
179
+	}
180
+
181
+	/**
182
+	 * @return void
183
+	 */
184
+	public function onBeforeInit()
185
+	{
186
+		// Grab global injectable service to allow testing.
187
+		$director = Injector::inst()->get(Director::class);
188
+
189
+		if (Config::inst()->get(__CLASS__, 'ssl_redirection_enabled')) {
190
+			// redirect some vulnerable areas to the secure domain
191
+			if (!$director::is_https()) {
192
+				$forceDomain = Config::inst()->get(__CLASS__, 'ssl_redirection_force_domain');
193
+
194
+				if ($forceDomain) {
195
+					$director::forceSSL(['/^Security/', '/^api/'], $forceDomain);
196
+				} else {
197
+					$director::forceSSL(['/^Security/', '/^api/']);
198
+				}
199
+			}
200
+		}
201
+
202
+		if (Config::inst()->get(__CLASS__, 'test_basicauth_enabled')) {
203
+			// Turn on Basic Auth in testing mode
204
+			if ($director::isTest()) {
205
+				$this->triggerBasicAuthProtection();
206
+			}
207
+		}
208
+
209
+		if (Config::inst()->get(__CLASS__, 'live_basicauth_enabled')) {
210
+			// Turn on Basic Auth in live mode
211
+			if ($director::isLive()) {
212
+				$this->triggerBasicAuthProtection();
213
+			}
214
+		}
215
+	}
216
+
217
+	/**
218
+	 * @return array
219
+	 */
220
+	public function providePermissions()
221
+	{
222
+		return [
223
+			'ACCESS_UAT_SERVER' => _t(
224
+				__CLASS__ . '.UatServerPermission',
225
+				'Allow users to use their accounts to access the UAT server'
226
+			)
227
+		];
228
+	}
229 229
 }
Please login to merge, or discard this patch.
src/Control/InitialisationMiddleware.php 1 patch
Indentation   +92 added lines, -92 removed lines patch added patch discarded remove patch
@@ -12,97 +12,97 @@
 block discarded – undo
12 12
  */
13 13
 class InitialisationMiddleware implements HTTPMiddleware
14 14
 {
15
-    use Configurable;
16
-
17
-    /**
18
-     * Disable the automatically added 'X-XSS-Protection' header that is added to all responses. This should be left
19
-     * alone in most circumstances to include the header. Refer to Mozilla Developer Network for more information:
20
-     * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
21
-     *
22
-     * @config
23
-     * @var bool
24
-     */
25
-    private static $xss_protection_disabled = false;
26
-
27
-    /**
28
-     * Enable egress proxy. This works on the principle of setting http(s)_proxy environment variables,
29
-     *  which will be automatically picked up by curl. This means RestfulService and raw curl
30
-     *  requests should work out of the box. Stream-based requests need extra manual configuration.
31
-     *  Refer to https://www.cwp.govt.nz/guides/core-technical-documentation/common-web-platform-core/en/how-tos/external_http_requests_with_proxy
32
-     *
33
-     * @config
34
-     * @var bool
35
-     */
36
-    private static $egress_proxy_default_enabled = true;
37
-
38
-    /**
39
-     * Configure the list of domains to bypass proxy by setting the NO_PROXY environment variable.
40
-     * 'services.cwp.govt.nz' needs to be present for Solr and Docvert internal CWP integration.
41
-     * 'localhost' is necessary for accessing services on the same instance such as tika-server for text extraction.
42
-     *
43
-     * @config
44
-     * @var string[]
45
-     */
46
-    private static $egress_proxy_exclude_domains = [
47
-        'services.cwp.govt.nz',
48
-        'localhost',
49
-    ];
50
-
51
-    public function process(HTTPRequest $request, callable $delegate)
52
-    {
53
-        $response = $delegate($request);
54
-
55
-        if ($this->config()->get('egress_proxy_default_enabled')) {
56
-            $this->configureEgressProxy();
57
-        }
15
+	use Configurable;
16
+
17
+	/**
18
+	 * Disable the automatically added 'X-XSS-Protection' header that is added to all responses. This should be left
19
+	 * alone in most circumstances to include the header. Refer to Mozilla Developer Network for more information:
20
+	 * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
21
+	 *
22
+	 * @config
23
+	 * @var bool
24
+	 */
25
+	private static $xss_protection_disabled = false;
26
+
27
+	/**
28
+	 * Enable egress proxy. This works on the principle of setting http(s)_proxy environment variables,
29
+	 *  which will be automatically picked up by curl. This means RestfulService and raw curl
30
+	 *  requests should work out of the box. Stream-based requests need extra manual configuration.
31
+	 *  Refer to https://www.cwp.govt.nz/guides/core-technical-documentation/common-web-platform-core/en/how-tos/external_http_requests_with_proxy
32
+	 *
33
+	 * @config
34
+	 * @var bool
35
+	 */
36
+	private static $egress_proxy_default_enabled = true;
37
+
38
+	/**
39
+	 * Configure the list of domains to bypass proxy by setting the NO_PROXY environment variable.
40
+	 * 'services.cwp.govt.nz' needs to be present for Solr and Docvert internal CWP integration.
41
+	 * 'localhost' is necessary for accessing services on the same instance such as tika-server for text extraction.
42
+	 *
43
+	 * @config
44
+	 * @var string[]
45
+	 */
46
+	private static $egress_proxy_exclude_domains = [
47
+		'services.cwp.govt.nz',
48
+		'localhost',
49
+	];
50
+
51
+	public function process(HTTPRequest $request, callable $delegate)
52
+	{
53
+		$response = $delegate($request);
54
+
55
+		if ($this->config()->get('egress_proxy_default_enabled')) {
56
+			$this->configureEgressProxy();
57
+		}
58 58
         
59
-        $this->configureProxyDomainExclusions();
60
-
61
-        if (!$this->config()->get('xss_protection_disabled') && $response) {
62
-            $response->addHeader('X-XSS-Protection', '1; mode=block');
63
-        }
64
-
65
-        return $response;
66
-    }
67
-
68
-    /**
69
-     * If the outbound egress proxy details have been defined in environment variables, configure the proxy
70
-     * variables that are used to configure it.
71
-     */
72
-    protected function configureEgressProxy()
73
-    {
74
-        if (!Environment::getEnv('SS_OUTBOUND_PROXY')
75
-            || !Environment::getEnv('SS_OUTBOUND_PROXY_PORT')
76
-        ) {
77
-            return;
78
-        }
79
-
80
-        $proxy = Environment::getEnv('SS_OUTBOUND_PROXY');
81
-        $proxyPort = Environment::getEnv('SS_OUTBOUND_PROXY_PORT');
82
-
83
-        Environment::setEnv('http_proxy', $proxy . ':' . $proxyPort);
84
-        Environment::setEnv('https_proxy', $proxy . ':' . $proxyPort);
85
-    }
86
-
87
-    /**
88
-     * Configure any domains that should be excluded from egress proxy rules and provide them to the environment
89
-     */
90
-    protected function configureProxyDomainExclusions()
91
-    {
92
-        $noProxy = $this->config()->get('egress_proxy_exclude_domains');
93
-        if (empty($noProxy)) {
94
-            return;
95
-        }
96
-
97
-        if (!is_array($noProxy)) {
98
-            $noProxy = [$noProxy];
99
-        }
100
-
101
-        // Merge with exsiting if needed.
102
-        if (Environment::getEnv('NO_PROXY')) {
103
-            $noProxy = array_merge(explode(',', Environment::getEnv('NO_PROXY')), $noProxy);
104
-        }
105
-
106
-        Environment::setEnv('NO_PROXY', implode(',', array_unique($noProxy)));
107
-    }
59
+		$this->configureProxyDomainExclusions();
60
+
61
+		if (!$this->config()->get('xss_protection_disabled') && $response) {
62
+			$response->addHeader('X-XSS-Protection', '1; mode=block');
63
+		}
64
+
65
+		return $response;
66
+	}
67
+
68
+	/**
69
+	 * If the outbound egress proxy details have been defined in environment variables, configure the proxy
70
+	 * variables that are used to configure it.
71
+	 */
72
+	protected function configureEgressProxy()
73
+	{
74
+		if (!Environment::getEnv('SS_OUTBOUND_PROXY')
75
+			|| !Environment::getEnv('SS_OUTBOUND_PROXY_PORT')
76
+		) {
77
+			return;
78
+		}
79
+
80
+		$proxy = Environment::getEnv('SS_OUTBOUND_PROXY');
81
+		$proxyPort = Environment::getEnv('SS_OUTBOUND_PROXY_PORT');
82
+
83
+		Environment::setEnv('http_proxy', $proxy . ':' . $proxyPort);
84
+		Environment::setEnv('https_proxy', $proxy . ':' . $proxyPort);
85
+	}
86
+
87
+	/**
88
+	 * Configure any domains that should be excluded from egress proxy rules and provide them to the environment
89
+	 */
90
+	protected function configureProxyDomainExclusions()
91
+	{
92
+		$noProxy = $this->config()->get('egress_proxy_exclude_domains');
93
+		if (empty($noProxy)) {
94
+			return;
95
+		}
96
+
97
+		if (!is_array($noProxy)) {
98
+			$noProxy = [$noProxy];
99
+		}
100
+
101
+		// Merge with exsiting if needed.
102
+		if (Environment::getEnv('NO_PROXY')) {
103
+			$noProxy = array_merge(explode(',', Environment::getEnv('NO_PROXY')), $noProxy);
104
+		}
105
+
106
+		Environment::setEnv('NO_PROXY', implode(',', array_unique($noProxy)));
107
+	}
108 108
 }
Please login to merge, or discard this patch.
src/Extension/RichLinksExtension.php 1 patch
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -17,48 +17,48 @@
 block discarded – undo
17 17
 class RichLinksExtension extends Extension
18 18
 {
19 19
 
20
-    /**
21
-     * @var array
22
-     */
23
-    private static $casting = [
24
-        'RichLinks' => 'HTMLText'
25
-    ];
20
+	/**
21
+	 * @var array
22
+	 */
23
+	private static $casting = [
24
+		'RichLinks' => 'HTMLText'
25
+	];
26 26
 
27
-    /**
28
-     * @return string
29
-     */
30
-    public function RichLinks()
31
-    {
32
-        // Note:
33
-        // Assume we can use Regexes because the link will always be formatted
34
-        // in the same way coming from the CMS.
27
+	/**
28
+	 * @return string
29
+	 */
30
+	public function RichLinks()
31
+	{
32
+		// Note:
33
+		// Assume we can use Regexes because the link will always be formatted
34
+		// in the same way coming from the CMS.
35 35
 
36
-        $content = $this->owner->value;
36
+		$content = $this->owner->value;
37 37
 
38
-        // Find all file links for processing.
39
-        preg_match_all('/<a.*href="\[file_link,id=([0-9]+)\].*".*>.*<\/a>/U', $content, $matches);
38
+		// Find all file links for processing.
39
+		preg_match_all('/<a.*href="\[file_link,id=([0-9]+)\].*".*>.*<\/a>/U', $content, $matches);
40 40
 
41
-        // Attach the file type and size to each of the links.
42
-        for ($i = 0; $i < count($matches[0]); $i++) {
43
-            $file = DataObject::get_by_id(File::class, $matches[1][$i]);
44
-            if ($file) {
45
-                $size = $file->getSize();
46
-                $ext = strtoupper($file->getExtension());
47
-                // Replace the closing </a> tag with the size span (and reattach the closing tag).
48
-                $newLink = substr($matches[0][$i], 0, strlen($matches[0][$i]) - 4)
49
-                    . "<span class='fileExt'> [$ext, $size]</span></a>";
50
-                $content = str_replace($matches[0][$i], $newLink, $content);
51
-            }
52
-        }
41
+		// Attach the file type and size to each of the links.
42
+		for ($i = 0; $i < count($matches[0]); $i++) {
43
+			$file = DataObject::get_by_id(File::class, $matches[1][$i]);
44
+			if ($file) {
45
+				$size = $file->getSize();
46
+				$ext = strtoupper($file->getExtension());
47
+				// Replace the closing </a> tag with the size span (and reattach the closing tag).
48
+				$newLink = substr($matches[0][$i], 0, strlen($matches[0][$i]) - 4)
49
+					. "<span class='fileExt'> [$ext, $size]</span></a>";
50
+				$content = str_replace($matches[0][$i], $newLink, $content);
51
+			}
52
+		}
53 53
 
54
-        // Inject extra attributes into the external links.
55
-        $pattern = '/(<a.*)(href=\"https?:\/\/[^\"]*\"[^>]*>.*)(<\/a>)/iU';
56
-        $replacement = sprintf(
57
-            '$1class="external" rel="external" title="%s" $2<span class="nonvisual-indicator">(external link)</span>$3',
58
-            _t(__CLASS__ . '.OpenLinkTitle', 'Open external link')
59
-        );
60
-        $content = preg_replace($pattern, $replacement, $content, -1);
54
+		// Inject extra attributes into the external links.
55
+		$pattern = '/(<a.*)(href=\"https?:\/\/[^\"]*\"[^>]*>.*)(<\/a>)/iU';
56
+		$replacement = sprintf(
57
+			'$1class="external" rel="external" title="%s" $2<span class="nonvisual-indicator">(external link)</span>$3',
58
+			_t(__CLASS__ . '.OpenLinkTitle', 'Open external link')
59
+		);
60
+		$content = preg_replace($pattern, $replacement, $content, -1);
61 61
 
62
-        return $content;
63
-    }
62
+		return $content;
63
+	}
64 64
 }
Please login to merge, or discard this patch.