File: TotpPfTest.php

package info (click to toggle)
postfixadmin 4.0.1%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,888 kB
  • sloc: php: 12,256; perl: 1,156; sh: 717; python: 142; xml: 63; sql: 3; makefile: 2
file content (175 lines) | stat: -rw-r--r-- 7,612 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
<?php


use PHPUnit\Framework\TestCase;

class TotpPfTest extends TestCase
{
    public function setUp(): void
    {
        global $CONF;
        Config::write('encrypt', 'md5'); // need something stable and non-salted for this test.
        $CONF['encrypt'] = 'md5';

        db_execute("INSERT INTO domain(domain, description, transport) values ('example.com', 'test', 'foo')", [], true);

        db_execute(
            "INSERT INTO mailbox(username, password, name, maildir, local_part, domain, active) VALUES(:username, :password, :name, :maildir, :local_part, :domain, 1)",
            [
                'username' => 'test@example.com',
                'password' => pacrypt('foobar'),
                'name' => 'test user',
                'maildir' => '/foo/bar',
                'local_part' => 'test',
                'domain' => 'example.com',
            ]);

        parent::setUp(); // TODO: Change the autogenerated stub
    }

    public function tearDown(): void
    {
        $this->cleanUp();
        parent::tearDown(); // TODO: Change the autogenerated stub
    }

    private function cleanUp()
    {
        db_query('DELETE FROM alias');
        db_query('DELETE FROM alias_domain');
        db_query('DELETE FROM mailbox');
        db_query('DELETE FROM domain_admins');
        db_query('DELETE FROM domain');

        db_query('DELETE FROM totp_exception_address');
        db_query('DELETE FROM mailbox_app_password');

    }


    public function testBasicTotpStuff()
    {
        $x = new TotpPf('mailbox', new Login('mailbox'));
        $array = $x->generate('test@example.com');

        $this->assertIsArray($array);
        $this->assertIsString($array[0]);
        $this->assertIsString($array[1]);


        $y = $x->checkUserTOTP('test@example.com', '123456');

        $z = $x->checkTOTP('asdf', 'fakecode');

        $this->assertFalse($y);
        $this->assertFalse($z);

        $totp = \OTPHP\TOTP::create('asdf');

        $currentCode = $totp->now();
        $this->assertNotEmpty($totp);
        $this->assertTrue($x->checkTOTP('asdf', $currentCode));

        $this->assertNotEmpty($currentCode);
    }

    public function testDbOperations()
    {
        $x = new TotpPf('mailbox', new Login('mailbox'));

        $this->assertNull($x->getException(1));
        $this->assertTrue($x->addException('test@example.com', 'foobar', '1.2.3.4', 'another.test@example.com', 'test'));
    }

    public function testDovecotQuery()
    {


        $sql = /* @lang SQL */
            <<<EOF
SELECT 
    m.username AS user,
    m.password AS password,
    CASE 
        WHEN m.username IS NOT NULL AND m.password = :password_hash AND (m.totp_secret IS NULL OR te.username IS NOT NULL) THEN 'mailbox_auth'
        WHEN app.username IS NOT NULL AND app.password_hash = :password_hash THEN 'app_password_auth'
        ELSE NULL
    END AS auth_type
FROM 
    (SELECT :username AS search_username, :password_hash AS search_password, :client_ip AS client_ip) AS params
LEFT JOIN 
    mailbox m ON m.username = params.search_username AND m.active = 1
LEFT JOIN 
    mailbox_app_password app ON app.username = params.search_username AND app.password_hash = params.search_password
LEFT JOIN 
    totp_exception_address te ON te.username = params.search_username AND te.ip = params.client_ip
WHERE 
    (
        m.username IS NOT NULL AND 
        m.password = params.search_password AND 
        (m.totp_secret IS NULL OR te.username IS NOT NULL)
    )
    OR (app.username IS NOT NULL AND app.password_hash = params.search_password)
LIMIT 1;

EOF;


        db_execute(
            "INSERT INTO mailbox(username, password, name, maildir, local_part, domain, active) VALUES(:username, :password, :name, :maildir, :local_part, :domain, 1)",
            [
                'username' => 'test2@example.com',
                'password' => pacrypt('foobar2'),
                'name' => 'test2 user',
                'maildir' => '/foo/bar2',
                'local_part' => 'test2',
                'domain' => 'example.com',
            ]);

        db_execute('INSERT INTO mailbox_app_password (username, description, password_hash) VALUES (:username, :description, :password_hash)', ['username' => 'test@example.com', 'description' => 'test foobar', 'password_hash' => pacrypt('foobar-app')]);
        db_execute('INSERT INTO mailbox_app_password (username, description, password_hash) VALUES (:username, :description, :password_hash)', ['username' => 'test@example.com', 'description' => 'test foobar234', 'password_hash' => pacrypt('foobar234')]);

        db_execute('INSERT INTO totp_exception_address (ip, username, description) VALUES (:ip, :username, :description)', ['ip' => '4.3.2.1', 'username' => 'test@example.com', 'description' => 'test 4.3.2.1']);
        db_execute('INSERT INTO totp_exception_address (ip, username, description) VALUES (:ip, :username, :description)', ['ip' => '4.3.2.2', 'username' => 'test@example.com', 'description' => 'test 4.3.2.2']);
        db_execute('INSERT INTO totp_exception_address (ip, username, description) VALUES (:ip, :username, :description)', ['ip' => '4.3.2.2', 'username' => 'test2@example.com', 'description' => 'test2 4.3.2.2']);

        $checkIt = function (string $username, string $plain_password, string $ip, string $auth_type) use ($sql): bool {
            $rows = db_query_all($sql, $params = [
                'username' => $username,
                'password_hash' => pacrypt($plain_password),
                'client_ip' => $ip,
            ]);

            if (count($rows) != 1) {
                //echo "FAIL " . count($rows) . " from " . json_encode($params);
                return false;
            }

            $row = $rows[0];
            $success = $row['user'] == $username && $row['auth_type'] == $auth_type;

            //echo json_encode(['params' => $params, 'result' => $row]) . "\n";

            return $success;
        };

        // ensure totp_secret is empty.
        db_execute('UPDATE mailbox SET totp_secret = :secret WHERE username = :username', ['username' => 'test@example.com', 'secret' => null]);

        $this->assertTrue($checkIt("test@example.com", "foobar", "irrelevant", "mailbox_auth"), "should auth on mailbox ");
        $this->assertTrue($checkIt("test@example.com", "foobar-app", "irrelevant", "app_password_auth"), "should auth on mailbox_app_password");
        $this->assertFalse($checkIt("test@example.com", "foobar-app-invalid", "irrelevant", "app_password_auth"), "should auth on mailbox_app_password");
        $this->assertFalse($checkIt("test3@example.com", "foobar-app-invalid", "irrelevant", "app_password_auth"), "test3@example.com is an invalid user");

        // let's turn 2fa support on for mailbox.
        db_execute('UPDATE mailbox SET totp_secret = :secret WHERE username = :username', ['username' => 'test@example.com', 'secret' => 'something-not-empty']);
        $this->assertTrue($checkIt("test@example.com", "foobar-app", "irrelevant", "app_password_auth"), "should work as 4.3.2.1 has an app password.");
        $this->assertFalse($checkIt("test@example.com", "foobar", "irrelevant", "mailbox_auth"), "user has totp_secret, so should not work on standard auth");

        // now see if the exception addresses work with 2fa.
        $this->assertTrue($checkIt("test@example.com", "foobar", "4.3.2.1", "mailbox_auth"), "user has totp_secret, but client_ip is exceptional");
        $this->assertFalse($checkIt("test@example.com", "foobar", "4.4.4.4", "mailbox_auth"), "user has totp_secret, but client_ip is not exceptional");


    }
}