1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Tests for oauth2 apis (\core\oauth2\*).
19 *
20 * @package    core
21 * @copyright  2017 Damyon Wiese
22 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
23 */
24
25defined('MOODLE_INTERNAL') || die();
26
27/**
28 * Tests for oauth2 apis (\core\oauth2\*).
29 *
30 * @package    core
31 * @copyright  2017 Damyon Wiese
32 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
33 */
34class core_oauth2_testcase extends advanced_testcase {
35
36    /**
37     * Tests the crud operations on oauth2 issuers.
38     */
39    public function test_create_and_delete_standard_issuers() {
40        $this->resetAfterTest();
41        $this->setAdminUser();
42        \core\oauth2\api::create_standard_issuer('google');
43        \core\oauth2\api::create_standard_issuer('facebook');
44        \core\oauth2\api::create_standard_issuer('microsoft');
45        \core\oauth2\api::create_standard_issuer('nextcloud', 'https://dummy.local/nextcloud/');
46
47        $issuers = \core\oauth2\api::get_all_issuers();
48
49        $this->assertEquals($issuers[0]->get('name'), 'Google');
50        $this->assertEquals($issuers[1]->get('name'), 'Facebook');
51        $this->assertEquals($issuers[2]->get('name'), 'Microsoft');
52        $this->assertEquals($issuers[3]->get('name'), 'Nextcloud');
53
54        \core\oauth2\api::move_down_issuer($issuers[0]->get('id'));
55
56        $issuers = \core\oauth2\api::get_all_issuers();
57
58        $this->assertEquals($issuers[0]->get('name'), 'Facebook');
59        $this->assertEquals($issuers[1]->get('name'), 'Google');
60        $this->assertEquals($issuers[2]->get('name'), 'Microsoft');
61        $this->assertEquals($issuers[3]->get('name'), 'Nextcloud');
62
63        \core\oauth2\api::delete_issuer($issuers[1]->get('id'));
64
65        $issuers = \core\oauth2\api::get_all_issuers();
66
67        $this->assertEquals($issuers[0]->get('name'), 'Facebook');
68        $this->assertEquals($issuers[1]->get('name'), 'Microsoft');
69        $this->assertEquals($issuers[2]->get('name'), 'Nextcloud');
70    }
71
72    /**
73     * Tests the crud operations on oauth2 issuers.
74     */
75    public function test_create_nextcloud_without_url() {
76        $this->resetAfterTest();
77        $this->setAdminUser();
78
79        $this->expectException(\moodle_exception::class);
80        \core\oauth2\api::create_standard_issuer('nextcloud');
81    }
82
83    /**
84     * Tests we can list and delete each of the persistents related to an issuer.
85     */
86    public function test_getters() {
87        $this->resetAfterTest();
88        $this->setAdminUser();
89        $issuer = \core\oauth2\api::create_standard_issuer('microsoft');
90
91        $same = \core\oauth2\api::get_issuer($issuer->get('id'));
92
93        foreach ($same->properties_definition() as $name => $def) {
94            $this->assertTrue($issuer->get($name) == $same->get($name));
95        }
96
97        $endpoints = \core\oauth2\api::get_endpoints($issuer);
98        $same = \core\oauth2\api::get_endpoint($endpoints[0]->get('id'));
99        $this->assertEquals($endpoints[0]->get('id'), $same->get('id'));
100        $this->assertEquals($endpoints[0]->get('name'), $same->get('name'));
101
102        $todelete = $endpoints[0];
103        \core\oauth2\api::delete_endpoint($todelete->get('id'));
104        $endpoints = \core\oauth2\api::get_endpoints($issuer);
105        $this->assertNotEquals($endpoints[0]->get('id'), $todelete->get('id'));
106
107        $userfields = \core\oauth2\api::get_user_field_mappings($issuer);
108        $same = \core\oauth2\api::get_user_field_mapping($userfields[0]->get('id'));
109        $this->assertEquals($userfields[0]->get('id'), $same->get('id'));
110
111        $todelete = $userfields[0];
112        \core\oauth2\api::delete_user_field_mapping($todelete->get('id'));
113        $userfields = \core\oauth2\api::get_user_field_mappings($issuer);
114        $this->assertNotEquals($userfields[0]->get('id'), $todelete->get('id'));
115    }
116
117    /**
118     * Data provider for \core_oauth2_testcase::test_get_system_oauth_client().
119     *
120     * @return array
121     */
122    public function system_oauth_client_provider() {
123        return [
124            [
125                (object) [
126                    'access_token' => 'fdas...',
127                    'token_type' => 'Bearer',
128                    'expires_in' => '3600',
129                    'id_token' => 'llfsd..',
130                ], HOURSECS - 10
131            ],
132            [
133                (object) [
134                    'access_token' => 'fdas...',
135                    'token_type' => 'Bearer',
136                    'id_token' => 'llfsd..',
137                ], WEEKSECS
138            ],
139        ];
140    }
141
142    /**
143     * Tests we can get a logged in oauth client for a system account.
144     *
145     * @dataProvider system_oauth_client_provider
146     * @param stdClass $responsedata The response data to be mocked.
147     * @param int $expiresin The expected expiration time.
148     */
149    public function test_get_system_oauth_client($responsedata, $expiresin) {
150        $this->resetAfterTest();
151        $this->setAdminUser();
152
153        $issuer = \core\oauth2\api::create_standard_issuer('microsoft');
154
155        $requiredscopes = \core\oauth2\api::get_system_scopes_for_issuer($issuer);
156        // Fake a system account.
157        $data = (object) [
158            'issuerid' => $issuer->get('id'),
159            'refreshtoken' => 'abc',
160            'grantedscopes' => $requiredscopes,
161            'email' => 'sys@example.com',
162            'username' => 'sys'
163        ];
164        $sys = new \core\oauth2\system_account(0, $data);
165        $sys->create();
166
167        // Fake a response with an access token.
168        $response = json_encode($responsedata);
169        curl::mock_response($response);
170        $client = \core\oauth2\api::get_system_oauth_client($issuer);
171        $this->assertTrue($client->is_logged_in());
172
173        // Check token expiry.
174        $accesstoken = \core\oauth2\access_token::get_record(['issuerid' => $issuer->get('id')]);
175
176        // Get the difference between the actual and expected expiry times.
177        // They might differ by a couple of seconds depending on the timing when the token gets actually processed.
178        $expiresdifference = time() + $expiresin - $accesstoken->get('expires');
179
180        // Assert that the actual token expiration is more or less the same as the expected.
181        $this->assertGreaterThanOrEqual(0, $expiresdifference);
182        $this->assertLessThanOrEqual(3, $expiresdifference);
183    }
184
185    /**
186     * Tests we can enable and disable an issuer.
187     */
188    public function test_enable_disable_issuer() {
189        $this->resetAfterTest();
190        $this->setAdminUser();
191
192        $issuer = \core\oauth2\api::create_standard_issuer('microsoft');
193
194        $issuerid = $issuer->get('id');
195
196        \core\oauth2\api::enable_issuer($issuerid);
197        $check = \core\oauth2\api::get_issuer($issuer->get('id'));
198        $this->assertTrue((boolean)$check->get('enabled'));
199
200        \core\oauth2\api::enable_issuer($issuerid);
201        $check = \core\oauth2\api::get_issuer($issuer->get('id'));
202        $this->assertTrue((boolean)$check->get('enabled'));
203
204        \core\oauth2\api::disable_issuer($issuerid);
205        $check = \core\oauth2\api::get_issuer($issuer->get('id'));
206        $this->assertFalse((boolean)$check->get('enabled'));
207
208        \core\oauth2\api::enable_issuer($issuerid);
209        $check = \core\oauth2\api::get_issuer($issuer->get('id'));
210        $this->assertTrue((boolean)$check->get('enabled'));
211    }
212
213    /**
214     * Test the alloweddomains for an issuer.
215     */
216    public function test_issuer_alloweddomains() {
217        $this->resetAfterTest();
218        $this->setAdminUser();
219
220        $issuer = \core\oauth2\api::create_standard_issuer('microsoft');
221
222        $issuer->set('alloweddomains', '');
223
224        // Anything is allowed when domain is empty.
225        $this->assertTrue($issuer->is_valid_login_domain(''));
226        $this->assertTrue($issuer->is_valid_login_domain('a@b'));
227        $this->assertTrue($issuer->is_valid_login_domain('longer.example@example.com'));
228
229        $issuer->set('alloweddomains', 'example.com');
230
231        // One domain - must match exactly - no substrings etc.
232        $this->assertFalse($issuer->is_valid_login_domain(''));
233        $this->assertFalse($issuer->is_valid_login_domain('a@b'));
234        $this->assertFalse($issuer->is_valid_login_domain('longer.example@example'));
235        $this->assertTrue($issuer->is_valid_login_domain('longer.example@example.com'));
236
237        $issuer->set('alloweddomains', 'example.com,example.net');
238        // Multiple domains - must match any exactly - no substrings etc.
239        $this->assertFalse($issuer->is_valid_login_domain(''));
240        $this->assertFalse($issuer->is_valid_login_domain('a@b'));
241        $this->assertFalse($issuer->is_valid_login_domain('longer.example@example'));
242        $this->assertFalse($issuer->is_valid_login_domain('invalid@email@example.net'));
243        $this->assertTrue($issuer->is_valid_login_domain('longer.example@example.net'));
244        $this->assertTrue($issuer->is_valid_login_domain('longer.example@example.com'));
245
246        $issuer->set('alloweddomains', '*.example.com');
247        // Wildcard.
248        $this->assertFalse($issuer->is_valid_login_domain(''));
249        $this->assertFalse($issuer->is_valid_login_domain('a@b'));
250        $this->assertFalse($issuer->is_valid_login_domain('longer.example@example'));
251        $this->assertFalse($issuer->is_valid_login_domain('longer.example@example.com'));
252        $this->assertTrue($issuer->is_valid_login_domain('longer.example@sub.example.com'));
253    }
254
255}
256