Passed
Push — master ( 703d03...e16894 )
by Darko
10:45
created

ValidEmailDomain::validate()   B

Complexity

Conditions 11
Paths 11

Size

Total Lines 70
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 41
c 1
b 0
f 0
dl 0
loc 70
rs 7.3166
cc 11
nc 11
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace App\Rules;
4
5
use Closure;
6
use Illuminate\Contracts\Validation\ValidationRule;
7
use Illuminate\Support\Facades\Log;
8
use Propaganistas\LaravelDisposableEmail\Facades\DisposableDomains;
9
10
class ValidEmailDomain implements ValidationRule
11
{
12
    /**
13
     * Common temporary/disposable email patterns
14
     */
15
    private const SUSPICIOUS_PATTERNS = [
16
        'temp',
17
        'temporary',
18
        'disposable',
19
        'throwaway',
20
        'fake',
21
        'trash',
22
        'spam',
23
        'guerrilla',
24
        '10minute',
25
        'minutemail',
26
        'tempmail',
27
        'guerrillamail',
28
        'mailinator',
29
        'yopmail',
30
        'maildrop',
31
        'trashmail',
32
        'getnada',
33
        'throwawaymail',
34
        'sharklasers',
35
        'guerrillamail',
36
        'grr.la',
37
    ];
38
39
    /**
40
     * Additional hardcoded disposable domains list
41
     */
42
    private const BLOCKED_DOMAINS = [
43
        '10minutemail.com',
44
        'guerrillamail.com',
45
        'mailinator.com',
46
        'maildrop.cc',
47
        'temp-mail.org',
48
        'tempmail.com',
49
        'throwawaymail.com',
50
        'yopmail.com',
51
        'getnada.com',
52
        'sharklasers.com',
53
        'guerrillamail.info',
54
        'guerrillamail.biz',
55
        'guerrillamail.de',
56
        'grr.la',
57
        'guerrillamail.net',
58
        'guerrillamail.org',
59
        'guerrillamail.com',
60
        'spam4.me',
61
        'trashmail.com',
62
        'dispostable.com',
63
        'fakeinbox.com',
64
        'tmpeml.info',
65
        'tempinbox.com',
66
        'mytemp.email',
67
        'mohmal.com',
68
        'emailondeck.com',
69
        'throwam.com',
70
        'mintemail.com',
71
        'spamgourmet.com',
72
        'mailnesia.com',
73
        'trashmail.net',
74
        'trash-mail.com',
75
        'emailtemporanea.com',
76
        'anonbox.net',
77
        'anonymbox.com',
78
        'discard.email',
79
        'spambox.us',
80
        'trbvm.com',
81
        'emailna.co',
82
        'my10minutemail.com',
83
    ];
84
85
    /**
86
     * Validate the email domain
87
     */
88
    public function validate(string $attribute, mixed $value, Closure $fail): void
89
    {
90
        if (empty($value) || ! is_string($value)) {
91
            $fail('The :attribute must be a valid email address.');
92
            return;
93
        }
94
95
        // Extract domain from email
96
        $parts = explode('@', $value);
97
        if (count($parts) !== 2) {
98
            $fail('The :attribute must be a valid email address.');
99
            return;
100
        }
101
102
        $localPart = trim($parts[0]);
103
        $domain = strtolower(trim($parts[1]));
104
105
        // Validate local part is not empty
106
        if (empty($localPart)) {
107
            $fail('The :attribute must be a valid email address.');
108
            return;
109
        }
110
111
        // Validate domain is not empty
112
        if (empty($domain)) {
113
            $fail('The :attribute must be a valid email address.');
114
            return;
115
        }
116
117
        // Check 1: Use the disposable email package
118
        if (! DisposableDomains::isNotDisposable($value)) {
119
            Log::warning('Disposable email attempt blocked (package detection)', [
120
                'email' => $value,
121
                'domain' => $domain,
122
            ]);
123
            $fail('Temporary or disposable email addresses are not allowed.');
124
            return;
125
        }
126
127
        // Check 2: Check against our hardcoded blacklist
128
        if (in_array($domain, self::BLOCKED_DOMAINS, true)) {
129
            Log::warning('Disposable email attempt blocked (hardcoded blacklist)', [
130
                'email' => $value,
131
                'domain' => $domain,
132
            ]);
133
            $fail('Temporary or disposable email addresses are not allowed.');
134
            return;
135
        }
136
137
        // Check 3: Check for suspicious patterns in domain name
138
        foreach (self::SUSPICIOUS_PATTERNS as $pattern) {
139
            if (str_contains($domain, $pattern)) {
140
                Log::warning('Disposable email attempt blocked (pattern match)', [
141
                    'email' => $value,
142
                    'domain' => $domain,
143
                    'pattern' => $pattern,
144
                ]);
145
                $fail('Temporary or disposable email addresses are not allowed.');
146
                return;
147
            }
148
        }
149
150
        // Check 4: Validate domain has valid DNS records (MX or A record)
151
        if (! $this->validateDnsRecords($domain)) {
152
            Log::warning('Email domain has no valid DNS records', [
153
                'email' => $value,
154
                'domain' => $domain,
155
            ]);
156
            $fail('The email domain does not appear to be valid or reachable.');
157
            return;
158
        }
159
160
        // Check 5: Block common free email services with plus addressing abuse
161
        // (optional - you may want to comment this out if you want to allow Gmail, etc.)
162
        // if ($this->hasSuspiciousPlusAddressing($value)) {
163
        //     Log::warning('Suspicious plus addressing detected', [
164
        //         'email' => $value,
165
        //         'domain' => $domain,
166
        //     ]);
167
        //     $fail('This email format is not allowed.');
168
        //     return;
169
        // }
170
    }
171
172
    /**
173
     * Validate that the domain has proper DNS records
174
     */
175
    private function validateDnsRecords(string $domain): bool
176
    {
177
        // Check for MX records (primary email validation)
178
        if (@checkdnsrr($domain, 'MX')) {
179
            return true;
180
        }
181
182
        // Fall back to A record check (some domains use A records for email)
183
        if (@checkdnsrr($domain, 'A')) {
184
            return true;
185
        }
186
187
        return false;
188
    }
189
190
    /**
191
     * Check for suspicious plus addressing patterns
192
     * Some users abuse plus addressing to create multiple accounts
193
     */
194
    private function hasSuspiciousPlusAddressing(string $email): bool
0 ignored issues
show
Unused Code introduced by
The method hasSuspiciousPlusAddressing() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
195
    {
196
        $parts = explode('@', $email);
197
        $localPart = $parts[0];
198
199
        // Check if contains + with suspicious patterns
200
        if (str_contains($localPart, '+')) {
201
            // You can add more sophisticated checks here
202
            // For now, just log it but don't block
203
            return false;
204
        }
205
206
        return false;
207
    }
208
}
209
210