Add support for API key and profile checks
Jeremy D

Jeremy D commited on 2022-03-26 15:35:13
Showing 8 changed files, with 336 additions and 6 deletions.


Should fix #1
This hasn't been tested with 2.0 yet.
... ...
@@ -250,7 +250,9 @@ class SFSA
250 250
 					1 => $this->SFSclass->txt('sfs_tor_check_ignore'),
251 251
 					2 => $this->SFSclass->txt('sfs_tor_check_bad'),
252 252
 				)),
253
-
253
+			'',
254
+				array('check', 'sfs_enablesubmission'),
255
+				array('text', 'sfs_apikey'),
254 256
 			'',
255 257
 				array('title', 'sfsverftitle', 'label' => $this->SFSclass->txt('sfs_verification_title')),
256 258
 				array('desc', 'sfsverfdesc', 'label' => $this->SFSclass->txt('sfs_verification_desc')),
... ...
@@ -185,6 +185,152 @@ class SFS
185 185
 		return $this->checkVerificationTestExtra($thisVerification);
186 186
 	}
187 187
 
188
+	/**
189
+	 * The hook to setup profile menu.
190
+	 *
191
+	 * @param array $profile_areas All the profile areas.
192
+	 *
193
+	 * @api
194
+	 * @CalledIn SMF 2.1
195
+	 * @See SFS::setupProfileMenu
196
+	 * @version 1.1
197
+	 * @since 1.1
198
+	 * @uses integrate_pre_profile_areas - Hook SMF2.1
199
+	 * @return void the passed $profile_areas is modified.
200
+	 */
201
+	public static function hook_pre_profile_areas(array &$profile_areas): void
202
+	{
203
+		global $smcFunc;
204
+		$smcFunc['classSFS']->setupProfileMenu($profile_areas);
205
+	}
206
+
207
+	/**
208
+	 * The hook to setup profile menu.
209
+	 *
210
+	 * @param array $profile_areas All the profile areas.
211
+	 *
212
+	 * @api
213
+	 * @CalledIn SMF 2.1
214
+	 * @version 1.1
215
+	 * @since 1.1
216
+	 * @uses integrate_pre_profile_areas - Hook SMF2.1
217
+	 * @return void the passed $profile_areas is modified.
218
+	 */
219
+	public function setupProfileMenu(array &$profile_areas): void
220
+	{
221
+		$profile_areas['info']['areas']['sfs'] = [
222
+			'label' => $this->txt('sfs_profile'),
223
+			'file' => 'SFS.php',
224
+			'function' => 'SFS::ProfileTrackSFS',
225
+			'permission' => [
226
+				'own' => ['moderate_forum'],
227
+				'any' => ['moderate_forum'],
228
+			],
229
+		];
230
+	}
231
+
232
+	/**
233
+	 * The caller for a profile check.
234
+	 *
235
+	 * @param int $memID The id of the member we are checking.
236
+	 *
237
+	 * @api
238
+	 * @CalledIn SMF 2.1
239
+	 * @version 1.1
240
+	 * @since 1.1
241
+	 * @return void the passed $profile_areas is modified.
242
+	 */
243
+	public static function ProfileTrackSFS(int $memID): void
244
+	{
245
+		global $smcFunc;
246
+		$smcFunc['classSFS']->TrackSFS($memID);
247
+	}
248
+
249
+	/**
250
+	 * The caller for a profile check.
251
+	 *
252
+	 * @param int $memID The id of the member we are checking.
253
+	 *
254
+	 * @api
255
+	 * @CalledIn SMF 2.1
256
+	 * @version 1.1
257
+	 * @since 1.1
258
+	 * @return void the passed $profile_areas is modified.
259
+	 */
260
+	public function TrackSFS(int $memID): void
261
+	{
262
+		global $user_profile, $context, $smcFunc, $scripturl, $modSettings;
263
+
264
+		isAllowedTo('moderate_forum');
265
+
266
+		// We need this stuff.
267
+		$context['sfs_allow_submit'] = !empty($modSettings['sfs_enablesubmission']) && !empty($modSettings['sfs_apikey']);
268
+		$context['token_check'] = 'sfs_submit-' . $memID;
269
+		$cache_key = 'sfs_check_member-' . $memID;
270
+
271
+		// Are we submitting this?
272
+		if ($context['sfs_allow_submit'] && (isset($_POST['sfs_submit']) || isset($_POST['sfs_submitban'])))
273
+		{
274
+			checkSession();
275
+			if (!$this->versionCheck('2.0', 'smf'))
276
+				validateToken($context['token_check'], 'post');
277
+
278
+			$data = [
279
+				'username' => $user_profile[$memID]['real_name'],
280
+				'email' => $user_profile[$memID]['email_address'],
281
+				'ip_addr' => $user_profile[$memID]['member_ip'],
282
+				'api_key' => $modSettings['sfs_apikey']
283
+			];
284
+			$post_data = http_build_query($data, '', '&');
285
+
286
+			// SMF 2.0 has the fetch_web_data in the Subs-Packages, 2.1 it is in Subs.php.
287
+			if ($this->versionCheck('2.0', 'smf'))
288
+				require_once($sourcedir . '/Subs-Package.php');
289
+
290
+			// Now we have a URL, lets go get it.
291
+			$result = fetch_web_data('https://www.stopforumspam.com/add', $post_data);
292
+
293
+			if (strpos($result, 'data submitted successfully') === false)
294
+				$context['submission_failed'] = $this->txt('sfs_submission_error');
295
+			else if (isset($_POST['sfs_submitban']))
296
+				redirectexit($scripturl . '?action=admin;area=ban;sa=add;u=' . $memID);
297
+			else
298
+				$context['submission_success'] = $this->txt('sfs_submission_success');
299
+		}
300
+	
301
+		// CHeck if we have this info.
302
+		if (($cache = cache_get_data($cache_key)) === null || ($response = $smcFunc['json_decode']($cache, true)) === null)
303
+		{
304
+			$checks = [
305
+				['username' => $user_profile[$memID]['real_name']],
306
+				['email' => $user_profile[$memID]['email_address']],
307
+				['ip' => $user_profile[$memID]['member_ip']],
308
+				['ip' => $user_profile[$memID]['member_ip2']],
309
+			];
310
+
311
+			$requestURL = $this->buildServerURL();
312
+			$this->buildCheckPath($requestURL, $checks, 'profile');
313
+			$response = (array) $this->sendSFSCheck($requestURL, $checks, 'profile');
314
+		
315
+			cache_put_data($cache_key, $smcFunc['json_encode']($response), 600);
316
+		}
317
+
318
+		// Prepare for the template.
319
+		$context['sfs_overall'] = (bool) $response['success'];
320
+		$context['sfs_checks'] = $response;
321
+		unset($context['sfs_checks']['success']);
322
+
323
+		if ($context['sfs_allow_submit'])
324
+		{
325
+			$context['sfs_submit_url'] = $scripturl . '?action=profile;area=sfs;u=' . $memID;
326
+			if (!$this->versionCheck('2.0', 'smf'))
327
+				createToken($context['token_check'], 'post');
328
+		}
329
+
330
+		loadTemplate('StopForumSpam');
331
+		$context['sub_template'] = 'profile_tracksfs';
332
+	}
333
+
188 334
 	/**
189 335
 	 * Test for a standard post.
190 336
 	 *
... ...
@@ -0,0 +1,105 @@
1
+<?php
2
+
3
+function template_profile_tracksfs()
4
+{
5
+	global $txt, $context, $scripturl;
6
+
7
+	if (!empty($context['submission_success']))
8
+		echo '
9
+	<div class="infobox">', $context['submission_success'] , '</div>';
10
+	elseif (!empty($context['submission_failed']))
11
+		echo '
12
+	<div class="errorbox">', $context['submission_failed'], '</div>';
13
+
14
+	if (!empty($context['sfs_allow_submit']))
15
+		echo '
16
+	<form action="', $context['sfs_submit_url'], '" method="post">';
17
+
18
+	echo '
19
+		<div class="tborder">
20
+			<div class="cat_bar">
21
+				<h3 class="catbg">', $txt['sfs_profile'], '</h3>
22
+			</div>
23
+
24
+			<table class="table_grid">
25
+				<thead>
26
+					<tr class="title_bar">
27
+						<th class="lefttext half_table">', $txt['sfs_check'], '</th>
28
+						<th class="lefttext half_table">', $txt['sfs_result'], '</th>
29
+					</tr>
30
+				</thead>
31
+				<tbody>';
32
+
33
+	foreach ($context['sfs_checks'] as $id_check => $checkGrp)
34
+	{
35
+		foreach ($checkGrp as $check)
36
+		{
37
+			echo '
38
+					<tr class="windowbg">
39
+						<td title="sfs_check_', $id_check, '">
40
+							', $txt['sfs_check_' . $id_check], '
41
+						</td>
42
+						<td class="smalltext">
43
+							', (!empty($check->appears) ? $txt['yes'] : $txt['no']);
44
+
45
+			// Some checks will show the last seen, convert it and show it.
46
+			if (!empty($check->lastseen))
47
+				echo '<br>' . $txt['sfs_last_seen'] . ': ' . timeformat(strtotime($check->lastseen));
48
+
49
+			if (!empty($check->confidence))
50
+				echo '<br>' . $txt['sfs_confidence'] . ': ' . $check->confidence;
51
+
52
+			if (!empty($check->frequency))
53
+				echo '<br>' . $txt['sfs_frequency'] . ': ' . $check->frequency;
54
+
55
+			// IP address may be normalized
56
+			if (!empty($check->torexit))
57
+				echo '<br>' . $txt['sfs_torexit'];
58
+
59
+			// IP address may be normalized
60
+			if (!empty($check->normalized) && !empty($check->asn))
61
+				echo '<br><a href="', $scripturl, '?action=profile;area=tracking;sa=ip;searchip=' . urlencode($check->normalized) . '">', $txt['trackIP'], '</a>';
62
+
63
+			echo '
64
+						</td>
65
+					</tr>';
66
+		}
67
+	}
68
+
69
+	echo '
70
+				</tbody>
71
+			</table>';
72
+
73
+	if (!empty($context['sfs_allow_submit']))
74
+		echo '
75
+			<br>
76
+			<div>
77
+				<div class="cat_bar">
78
+					<h3 class="catbg">', $txt['sfs_submit_title'], '</h3>
79
+				</div>
80
+				<div class="roundframe">
81
+					<div>', $txt['sfs_evidence'], '</div>
82
+					<textarea name="reason" rows="4"></textarea>
83
+					<div class="righttext">
84
+						<input id="notify_submit" type="submit" name="sfs_submit" value="', $txt['sfs_submit'], '" class="button">
85
+						<input id="notify_submit" type="submit" name="sfs_submitban" value="', $txt['sfs_submit_ban'], '" class="button">
86
+					</div>
87
+				</div>
88
+			</div>';
89
+
90
+	echo '
91
+		</div><!-- .tborder -->';
92
+
93
+	if (!empty($context['sfs_allow_submit']))
94
+	{
95
+		echo '
96
+		<input type="hidden" name="', $context['session_var'], '" value="', $context['session_id'], '">';
97
+
98
+		if (!empty($context['token_check']))
99
+			echo '
100
+		<input type="hidden" name="' . $context[$context['token_check'] . '_token_var'] . '" value="' . $context[$context['token_check'] . '_token'] . '">
101
+
102
+		echo '
103
+	</form>';
104
+	}
105
+}
0 106
\ No newline at end of file
... ...
@@ -16,6 +16,8 @@ $txt['sfs_ipcheck_autoban'] = 'Automatically ban IPs that are blacklisted?';
16 16
 $txt['sfs_usernamecheck'] = 'Check Username?';
17 17
 $txt['sfs_username_confidence'] = 'Confidence level for usernames on registration';
18 18
 $txt['sfs_emailcheck'] = 'Check Email? (Recommended)';
19
+$txt['sfs_enablesubmission'] = 'Enable Submissions';
20
+$txt['sfs_apikey'] = '<a href="https://www.stopforumspam.com/keys">API Key</a>';
19 21
 
20 22
 /* Admin section: Region Config */
21 23
 $txt['sfs_region'] = 'Geographic Access Region';
... ...
@@ -89,3 +91,23 @@ $txt['sfs_log_confidence'] = 'Confidence Level: %1$s';
89 91
 $txt['sfs_ban_group_name'] = 'SFS Automatic IP Bans';
90 92
 $txt['sfs_ban_group_reason'] = 'Your IP has triggered the automatic ban for poor reputation and has been blacklisted';
91 93
 $txt['sfs_ban_group_notes'] = 'This Group is automatically created by the Stop Forum Spam Customization and automatically will add IPs that are blacklisted to this group';
94
+
95
+// Profile menu
96
+$txt['sfs_profile'] = 'Track Stop Forum Spam';
97
+$txt['sfs_check'] = 'Check';
98
+$txt['sfs_result'] = 'Result';
99
+$txt['sfs_check_username'] = 'Username';
100
+$txt['sfs_check_email'] = 'Email';
101
+$txt['sfs_check_ip'] = 'IP Address';
102
+$txt['sfs_last_seen'] = 'Last Seen';
103
+$txt['sfs_confidence'] = 'Confidence';
104
+$txt['sfs_frequency'] = 'Frequency';
105
+$txt['sfs_torexit'] = 'TOR Exit Node';
106
+
107
+// Profile section Submission
108
+$txt['sfs_submit_title'] = 'Stop Forum Spam Submission';
109
+$txt['sfs_submit'] = 'Submit to Stop Forum Spam';
110
+$txt['sfs_submit_ban'] = 'Submit to Stop Forum Spam and Start ban process';
111
+$txt['sfs_evidence'] = 'Evidence';
112
+$txt['sfs_submission_error'] = 'Submission Error';
113
+$txt['sfs_submission_success'] = 'Submission Success';
92 114
\ No newline at end of file
... ...
@@ -3,7 +3,7 @@
3 3
 <package-info xmlns="http://www.simplemachines.org/xml/package-info" xmlns:smf="http://www.simplemachines.org/">
4 4
 	<id>SleePy:StopForumSpam</id>
5 5
 	<name>Stop Forum Spam</name>
6
-	<version>1.0</version>
6
+	<version>1.1</version>
7 7
 	<type>modification</type>
8 8
 
9 9
 	<install for="1.1.*">
... ...
@@ -17,8 +17,17 @@
17 17
 		<code type="file">sfs_hooks_install.php</code>
18 18
 		<modification>install_smf20.xml</modification>
19 19
 
20
+		<require-file name="SFS.php" destination="$sourcedir" />
21
+		<require-file name="SFS-Subs-Admin.php" destination="$sourcedir" />
22
+		<require-file name="SFS-Subs-Logs.php" destination="$sourcedir" />
23
+		<require-file name="StopForumSpam.template.php" destination="$themedir" />
24
+
25
+		<!-- this dir may not exist in SMF -->
26
+		<create-dir name="admin" destination="$themedir/images" />
27
+		<require-file name="sfs.webp" destination="$themedir/images/admin" />
28
+
29
+		<!-- language files -->
20 30
 		<require-file name="language/StopForumSpam.english.php" destination="$themes_dir/default/languages" />
21
-		<require-file name="StopForumSpam.php" destination="$sourcedir" />
22 31
 
23 32
 		<redirect url="?action=admin;area=modsettings;sa=sfs" />
24 33
 	</install>
... ...
@@ -34,17 +43,26 @@
34 43
 		<remove-dir name="$themes_dir/default/languages/StopForumSpam.english.php" />
35 44
 
36 45
 		<!-- source files, removed -->
37
-		<remove-dir name="$sourcedir/StopForumSpam.php" />
46
+		<remove-file name="$sourcedir/SFS.php" />
47
+		<remove-file name="$sourcedir/SFS-Subs-Admin.php" />
48
+		<remove-file name="$sourcedir/SFS-Subs-Logs.php" />
49
+		<remove-file name="$themedir/StopForumSpam.template.php" />
50
+		<remove-file name="$themedir/images/admin/sfs.webp" />
38 51
 	</uninstall>
39 52
 
40 53
 	<install for="2.1.*">
41 54
 		<readme type="file" parsebbc="true">README.bbc</readme>
42 55
 		<database>install_sfs.php</database>
43 56
 
44
-		<require-file name="language/StopForumSpam.english.php" destination="$themes_dir/default/languages" />
45 57
 		<require-file name="SFS.php" destination="$sourcedir" />
46 58
 		<require-file name="SFS-Subs-Admin.php" destination="$sourcedir" />
47 59
 		<require-file name="SFS-Subs-Logs.php" destination="$sourcedir" />
60
+		<require-file name="StopForumSpam.template.php" destination="$themedir" />
61
+		<require-file name="language/StopForumSpam.english.php" destination="$themes_dir/default/languages" />
62
+
63
+		<!-- this dir may not exist in SMF -->
64
+		<create-dir name="admin" destination="$themedir/images" />
65
+		<require-file name="sfs.webp" destination="$themedir/images/admin" />
48 66
 
49 67
 		<!-- All the hooks -->
50 68
 			<!-- Main Section -->
... ...
@@ -59,6 +77,9 @@
59 77
 			<hook hook="integrate_modify_modifications" function="SFSA::hook_modify_modifications" />
60 78
 			<hook hook="integrate_manage_logs" function="SFSA::hook_manage_logs" />
61 79
 
80
+			<!-- Profile Section -->
81
+			<hook hook="integrate_pre_profile_areas" function="SFSA::hook_pre_profile_areas" />
82
+
62 83
 		<redirect url="?action=admin;area=modsettings;sa=sfs" />
63 84
 	</install>
64 85
 
... ...
@@ -79,6 +100,9 @@
79 100
 			<hook hook="integrate_modify_modifications" function="SFSA::hook_modify_modifications" reverse="true" />
80 101
 			<hook hook="integrate_manage_logs" function="SFSA::hook_manage_logs" reverse="true" />
81 102
 
103
+			<!-- Profile Section -->
104
+			<hook hook="integrate_pre_profile_areas" function="SFSA::hook_pre_profile_areas" reverse="true" />
105
+
82 106
 		<!-- language files, removed -->
83 107
 		<remove-file name="$themes_dir/default/languages/StopForumSpam.english.php" />
84 108
 
... ...
@@ -86,6 +110,31 @@
86 110
 		<remove-file name="$sourcedir/SFS.php" />
87 111
 		<remove-file name="$sourcedir/SFS-Subs-Admin.php" />
88 112
 		<remove-file name="$sourcedir/SFS-Subs-Logs.php" />
113
+		<remove-file name="$themedir/StopForumSpam.template.php" />
114
+		<remove-file name="$themedir/images/admin/sfs.webp" />
89 115
 	</uninstall>
90 116
 
117
+	<upgrade from="1.0" for="2.1.*">
118
+		<require-file name="language/StopForumSpam.english.php" destination="$themes_dir/default/languages" />
119
+		<require-file name="StopForumSpam.template.php" destination="$themedir" />
120
+		<require-file name="SFS.php" destination="$sourcedir" />
121
+		<require-file name="SFS-Subs-Admin.php" destination="$sourcedir" />
122
+		<hook hook="integrate_pre_profile_areas" function="SFSA::hook_pre_profile_areas" />
123
+
124
+		<!-- this dir may not exist in SMF -->
125
+		<create-dir name="admin" destination="$themedir/images" />
126
+		<require-file name="sfs.webp" destination="$themedir/images/admin" />
127
+	</upgrade>	
128
+
129
+	<upgrade from="1.0" for="2.0.*">
130
+		<code type="file">sfs_hooks_install.php</code>
131
+		<require-file name="language/StopForumSpam.english.php" destination="$themes_dir/default/languages" />
132
+		<require-file name="StopForumSpam.template.php" destination="$themedir" />
133
+		<require-file name="SFS.php" destination="$sourcedir" />
134
+		<require-file name="SFS-Subs-Admin.php" destination="$sourcedir" />
135
+
136
+		<!-- this dir may not exist in SMF -->
137
+		<create-dir name="admin" destination="$themedir/images" />
138
+		<require-file name="sfs.webp" destination="$themedir/images/admin" />
139
+	</upgrade>	
91 140
 </package-info>
92 141
\ No newline at end of file
... ...
@@ -28,7 +28,10 @@ $hooks = array(
28 28
 	'integrate_admin_include' => '$sourcedir/SFS-Subs-Admin.php',
29 29
 	'integrate_admin_areas' => 'SFSA::hook_admin_areas',
30 30
 	'integrate_modify_modifications' => 'SFSA::hook_modify_modifications',
31
-	'integrate_manage_logs' => 'SFSA::hook_manage_logs'
31
+	'integrate_manage_logs' => 'SFSA::hook_manage_logs',
32
+
33
+	// Profile Section.
34
+	'integrate_profile_areas' => 'SFS::hook_pre_profile_areas'
32 35
 );
33 36
 
34 37
 foreach ($hooks as $hook => $func)
... ...
@@ -29,6 +29,9 @@ $hooks = array(
29 29
 	'integrate_admin_areas' => 'SFSA::hook_admin_areas',
30 30
 	'integrate_modify_modifications' => 'SFSA::hook_modify_modifications',
31 31
 	'integrate_manage_logs' => 'SFSA::hook_manage_logs'
32
+
33
+	// Profile Section.
34
+	'integrate_profile_areas' => 'SFS::hook_pre_profile_areas'
32 35
 );
33 36
 
34 37
 foreach ($hooks as $hook => $func)
35 38