1<?php
2require_once(__DIR__ . '/../lib/grammar.php');
3require_once(__DIR__ . '/../lib/exceptions.php');
4
5/**
6 * General tests for the ifauthex plugin
7 *
8 * @group plugin_ifauthex
9 * @group plugins
10 */
11class general_plugin_ifauthex_test extends DokuWikiTest
12{
13
14    protected $pluginsEnabled = array('ifauthex');
15
16    const VALID_EXPRESSIONS = array(
17        'user',
18        '"user name"',
19        'user-name',
20        'user.name',
21        '"user \\ name \\" surname"',
22        '!user',
23        '@group',
24        '!@group',
25        '!(!@group && !@group && !@group)',
26        '!(!@group && !user && @group) || !(@group || user || @group)',
27        '!(!@group && @group || !user) && (!user || user && @group)',
28        '!(!@group && user) && !(!@group || @group || user)',
29        '!(!@group || !user && !user) || !(user || @group || !@group)',
30        '!(!@group || @group || !@group) && (@group || @group && !@group)',
31        '!(!@group || user || user) && !(!user || !user || !user)',
32        '!(!user && !@group) || (!@group || !user && @group)',
33        '!(!user && @group && @group) && (user || !@group || user)',
34        '!(!user && user || !user) && !(@group || !@group && user)',
35        '!(!user || !@group || user) || (!user || !@group && !@group)',
36        '!(!user || @group && !user) || !(!@group && user || !user)',
37        '!(!user || user || !@group) && !(user && user && !user)',
38        '!(@group && !@group || !user) || (@group && @group || user)',
39        '!(@group && !user) || !(!user && @group && user)',
40        '!(@group && user && @group) && (!@group && !user)',
41        '!(@group || !@group || !@group) || (user && !user || !user)',
42        '!(@group || !user || user) || !(@group && !user && !user)',
43        '!(@group || user && !user) && (!user && !@group || @group)',
44        '!(user && !@group && user) && !(!@group && !@group && @group)',
45        '!(user && !user || !user) || !(@group)',
46        '!(user && @group) && (!user || user || !user)',
47        '!(user || !@group && @group) && !(!@group || user && !user)',
48        '!(user || !user || !@group) || !(user || @group || user)',
49        '!(user || @group || user) && (@group || @group && user)',
50        '!user || !user || user',
51        '(!@group && !user && @group) || !(@group || !user || !user)',
52        '(!@group && @group || !user) && (!user || !user && !user)',
53        '(!@group && user) && !(!@group || !@group || @group)',
54        '(!@group || !user && !user) || !(user || !@group && user)',
55        '(!@group || @group || !@group) && (@group && user)',
56        '(!@group || user || user) && !(!user && user || !@group)',
57        '(!user && !@group) || (!@group && user && !user)',
58        '(!user && @group && @group) && (user && @group || @group)',
59        '(!user && user || !user) && !(@group && @group && @group)',
60        '(!user || !@group || user) || (!user && !user)',
61        '(!user || @group && !user) || !(!@group && !user || !@group)',
62        '(!user || user || !@group) && !(user && !user && !@group)',
63        '(@group && !@group || !user) || (@group && !@group || @group)',
64        '(@group && !user) || !(!user && !@group && @group)',
65        '(@group && user && @group) && !(user || user)',
66        '(@group || !@group || !@group) || (@group || user || !user)',
67        '(@group || !user || user) || !(!user || user && !user)',
68        '(@group || user && !user) && (!@group || @group || @group)',
69        '(user && !@group && @group) || (user || @group && user)',
70        '(user && !user || !user) || !(@group || !user)',
71        '(user && @group) && (!user || !user || !@group)',
72        '(user || !@group && @group) && !(!@group || !user && !@group)',
73        '(user || !user || !@group) || !(user || !@group || @group)',
74        '(user || @group || user) && (@group || !@group && @group)',
75        'user && user || @group'
76    );
77
78    const VALID_MB_EXPRESSIONS = array(
79        "\u{7D4C}\u{55B6}\u{4F01}\u{753B}\u{672C}\u{90E8}",
80        "\u{7D4C}\u{55B6}\u{4F01} && \u{753B}\u{672C}\u{90E8}",
81        "\u{7D4C}\u{55B6}\u{4F01} && \u{753B} || @\u{672C}\u{90E8}",
82        "!@\u{7D4C} || (\u{55B6}\u{4F01} && \u{753B} || @\u{672C}) && !\u{90E8}"
83    );
84
85    const UNKNOWN_TOKEN_EXPRESSIONS = array(
86        '!(!@group & !@group && !@group)',
87        '!(!@group && !user && @group) | !(@group || user || @group)',
88        '!(!@group && @group || !user] && (!user || user && @group)',
89        '!(!@group && user) && !^!@group || @group || user)',
90        '!(!@group || <inject> !user && !user) || !(user || @group || !@group)',
91        '!(!@group || @group {--} !@group) && (@group || @group && !@group)',
92        '!(!@group || user || user) && !(!user || > !user || !user)',
93        '!(!user && !@group) <|| (!@group || !user && @group)',
94        '!(!user && @group && @group) / && (user || !@group || user)',
95        '!(!user && user || : !user) && !(@group || !@group && user)',
96        '"user name\\"',
97        '"user name\\\\\\"'
98    );
99
100    const STRAY_TOKEN_EXPRESSIONS = array(
101        'usr usr2',
102        'user && @group) && (!user || !user || !@group)',
103        // Closed brackets alone are unmatched:
104        '@group && (usr) @another',
105        '(user || !user || !@group) || !user || !@group || @group)',
106    );
107
108    const UNMATCHED_WRAPPER_EXPRESSIONS = array(
109        '(user || !@group && @group && !(!@group || !user && !@group)',
110        '(user || @group || user) && (@group || !@group && @group'
111    );
112
113    const NOT_ENOUGH_ARGS_EXPRESSIONS = array(
114        '!(@group && user && @group) && (!@group && !)',
115        '!(@group || !@group || !@group) || (user &&)',
116        '!(@group || !user ||) || !(@)',
117        '!(@group || user && !user) && @',
118        '!(@group || user && !user) ||',
119        '@!usr', // @ takes "!", but when ! is parsed, no arg is left
120        '@@group', // @ takes "@", but when ! is parsed, no arg is left
121    );
122
123    const MALFORMED_EXPRESSIONS = array(
124        'usr usr2', // More than one element in root
125        '()', // Subexpression must have one root
126        '(usr usr2)',
127        '@()', // Group takes exactly a literal
128        '@(group)'
129    );
130
131    public static function strip($txt) {
132        return preg_replace('/\s/', '', $txt);
133    }
134
135    public function test_parse()
136    {
137        foreach (self::VALID_EXPRESSIONS as $expr) {
138            $failureMsg = 'Assertion failed at expression "' . $expr . '".';
139            $ast = null;
140            $rebuiltExpr = null;
141            $this->assertNotNull($ast = auth_expr_parse($expr));
142            $this->assertNotNull($rebuiltExpr = $ast->getRepresentation());
143            $this->assertEquals(self::strip($rebuiltExpr), self::strip($expr));
144        }
145        if (\AST\TokenDefinition::supportsMultibyte()) {
146            foreach (self::VALID_MB_EXPRESSIONS as $expr) {
147                $failureMsg = 'Assertion failed at expression "' . $expr . '".';
148                $ast = null;
149                $rebuiltExpr = null;
150                $this->assertNotNull($ast = auth_expr_parse($expr));
151                $this->assertNotNull($rebuiltExpr = $ast->getRepresentation());
152                $this->assertEquals(self::strip($rebuiltExpr), self::strip($expr));
153            }
154        }
155    }
156
157    public function test_unknown_token()
158    {
159        foreach (self::UNKNOWN_TOKEN_EXPRESSIONS as $expr) {
160            $exc = null;
161            try {
162                auth_expr_parse($expr);
163            } catch (Exception $e) {
164                $exc = $e;
165            }
166            $this->assertInstanceOf(\AST\UnknownTokenException::class, $exc);
167        }
168    }
169
170    public function test_unmatched_wrappers()
171    {
172        foreach (self::UNMATCHED_WRAPPER_EXPRESSIONS as $expr) {
173            $exc = null;
174            try {
175                auth_expr_parse($expr);
176            } catch (Exception $e) {
177                $exc = $e;
178            }
179            $this->assertInstanceOf(\AST\UnmatchedWrapperException::class, $exc);
180        }
181    }
182
183
184    public function test_not_enough_args()
185    {
186        foreach (self::NOT_ENOUGH_ARGS_EXPRESSIONS as $expr) {
187            $exc = null;
188            try {
189                auth_expr_parse($expr);
190            } catch (Exception $e) {
191                $exc = $e;
192            }
193            $this->assertInstanceOf(\AST\NotEnoughArgumentsException::class, $exc);
194        }
195    }
196
197    public function test_malformed()
198    {
199        foreach (self::MALFORMED_EXPRESSIONS as $expr) {
200            $exc = null;
201            try {
202                $ast = null;
203                // The expression must parse, but not validate
204                $this->assertNotNull($ast = auth_expr_parse($expr));
205                $ast->ensureWellFormed();
206            } catch (Exception $e) {
207                $exc = $e;
208            }
209            $this->assertInstanceOf(\AST\MalformedExpressionException::class, $exc);
210        }
211    }
212
213    public function test_empty_parentehses() {
214        // This must not throw. It's malformed, but it's parsed correctly.
215        $this->assertNotNull(auth_expr_parse('()'));
216    }
217
218    public function test_empty() {
219        // This must not throw. It's malformed, but it's parsed correctly.
220        $this->assertNotNull(auth_expr_parse(''));
221    }
222
223
224    public function test_depth_limit()
225    {
226        $depthLimit = \AST\parse_config()->EXPR_DEPTH_LIMIT;
227        $this->expectException(RuntimeException::class);
228        auth_expr_parse(str_repeat('(', $depthLimit) . 'a && b' . str_repeat(')', $depthLimit));
229    }
230
231    public function test_output() {
232        $info = array();
233        $instructions = p_get_instructions('<ifauth (nonexistent)>hideme</ifauth><ifauth !(nonexistent)>showme</ifauth>');
234        $xhtml = p_render('xhtml', $instructions, $info);
235        $this->assertTrue(stristr($xhtml, 'showme') !== false);
236        $this->assertTrue(stristr($xhtml, 'hideme') === false);
237
238        auth_expr_evaluation_context()->SIMULATE_USERS = array('nonexistent');
239        $xhtml = p_render('xhtml', $instructions, $info);
240        $this->assertTrue(stristr($xhtml, 'showme') === false);
241        $this->assertTrue(stristr($xhtml, 'hideme') !== false);
242        auth_expr_evaluation_context()->SIMULATE_USERS = null;
243    }
244
245    public function test_operators() {
246        auth_expr_evaluation_context()->SIMULATE_IN_GROUPS = array('group');
247        auth_expr_evaluation_context()->SIMULATE_USERS = array('user');
248
249        $this->assertTrue(auth_expr_parse('user')->evaluate());
250        $this->assertFalse(auth_expr_parse('!user')->evaluate());
251        $this->assertTrue(auth_expr_parse('!user2')->evaluate());
252        $this->assertFalse(auth_expr_parse('user2')->evaluate());
253        $this->assertTrue(auth_expr_parse('@group')->evaluate());
254        $this->assertFalse(auth_expr_parse('!@group')->evaluate());
255        $this->assertTrue(auth_expr_parse('!@group2')->evaluate());
256        $this->assertFalse(auth_expr_parse('@group2')->evaluate());
257
258        $this->assertTrue(auth_expr_parse('user || @group')->evaluate());
259        $this->assertTrue(auth_expr_parse('user || !@group')->evaluate());
260        $this->assertTrue(auth_expr_parse('!user || @group')->evaluate());
261        $this->assertFalse(auth_expr_parse('!user || !@group')->evaluate());
262        $this->assertFalse(auth_expr_parse('user2 || @group2')->evaluate());
263        $this->assertTrue(auth_expr_parse('user || @group2')->evaluate());
264        $this->assertTrue(auth_expr_parse('user2 || @group')->evaluate());
265
266        $this->assertTrue(auth_expr_parse('user && @group')->evaluate());
267        $this->assertFalse(auth_expr_parse('user && !@group')->evaluate());
268        $this->assertFalse(auth_expr_parse('!user && @group')->evaluate());
269        $this->assertFalse(auth_expr_parse('!user && !@group')->evaluate());
270        $this->assertFalse(auth_expr_parse('user2 && @group2')->evaluate());
271        $this->assertFalse(auth_expr_parse('user && @group2')->evaluate());
272        $this->assertFalse(auth_expr_parse('user2 && @group')->evaluate());
273
274        $this->assertTrue(auth_expr_parse('(user || @group)')->evaluate());
275        $this->assertTrue(auth_expr_parse('(user || !@group)')->evaluate());
276        $this->assertTrue(auth_expr_parse('(!user || @group)')->evaluate());
277        $this->assertFalse(auth_expr_parse('(!user || !@group)')->evaluate());
278        $this->assertFalse(auth_expr_parse('(user2 || @group2)')->evaluate());
279        $this->assertTrue(auth_expr_parse('(user || @group2)')->evaluate());
280        $this->assertTrue(auth_expr_parse('(user2 || @group)')->evaluate());
281
282        $this->assertFalse(auth_expr_parse('!(user || @group)')->evaluate());
283        $this->assertFalse(auth_expr_parse('!(user || !@group)')->evaluate());
284        $this->assertFalse(auth_expr_parse('!(!user || @group)')->evaluate());
285        $this->assertTrue(auth_expr_parse('!(!user || !@group)')->evaluate());
286        $this->assertTrue(auth_expr_parse('!(user2 || @group2)')->evaluate());
287        $this->assertFalse(auth_expr_parse('!(user || @group2)')->evaluate());
288        $this->assertFalse(auth_expr_parse('!(user2 || @group)')->evaluate());
289
290        $this->assertTrue(auth_expr_parse('user, @group')->evaluate());
291        $this->assertTrue(auth_expr_parse('user, !@group')->evaluate());
292        $this->assertTrue(auth_expr_parse('!user, @group')->evaluate());
293        $this->assertFalse(auth_expr_parse('!user, !@group')->evaluate());
294        $this->assertFalse(auth_expr_parse('user2, @group2')->evaluate());
295        $this->assertTrue(auth_expr_parse('user, @group2')->evaluate());
296        $this->assertTrue(auth_expr_parse('user2, @group')->evaluate());
297
298        auth_expr_evaluation_context()->SIMULATE_IN_GROUPS = null;
299        auth_expr_evaluation_context()->SIMULATE_USERS = null;
300    }
301
302    /**
303     * Simple test to make sure the plugin.info.txt is in correct format
304     */
305    public function test_plugininfo()
306    {
307        $file = __DIR__ . '/../plugin.info.txt';
308        $this->assertFileExists($file);
309
310        $info = confToHash($file);
311
312        $this->assertArrayHasKey('base', $info);
313        $this->assertArrayHasKey('author', $info);
314        $this->assertArrayHasKey('email', $info);
315        $this->assertArrayHasKey('date', $info);
316        $this->assertArrayHasKey('name', $info);
317        $this->assertArrayHasKey('desc', $info);
318        $this->assertArrayHasKey('url', $info);
319
320        $this->assertEquals('ifauthex', $info['base']);
321        $this->assertRegExp('/^https?:\/\//', $info['url']);
322        $this->assertTrue(mail_isvalid($info['email']));
323        $this->assertRegExp('/^\d\d\d\d-\d\d-\d\d$/', $info['date']);
324        $this->assertTrue(false !== strtotime($info['date']));
325    }
326
327    /**
328     * Test to ensure that every conf['...'] entry in conf/default.php has a corresponding meta['...'] entry in
329     * conf/metadata.php.
330     */
331    public function test_plugin_conf()
332    {
333        $conf_file = __DIR__ . '/../conf/default.php';
334        $meta_file = __DIR__ . '/../conf/metadata.php';
335
336        if (!file_exists($conf_file) && !file_exists($meta_file)) {
337            self::markTestSkipped('No config files exist -> skipping test');
338        }
339
340        if (file_exists($conf_file)) {
341            include($conf_file);
342        }
343        if (file_exists($meta_file)) {
344            include($meta_file);
345        }
346
347        $this->assertEquals(
348            gettype($conf),
349            gettype($meta),
350            'Both ' . DOKU_PLUGIN . 'ifauthex/conf/default.php and ' . DOKU_PLUGIN . 'ifauthex/conf/metadata.php have to exist and contain the same keys.'
351        );
352
353        if ($conf !== null && $meta !== null) {
354            foreach ($conf as $key => $value) {
355                $this->assertArrayHasKey(
356                    $key,
357                    $meta,
358                    'Key $meta[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'ifauthex/conf/metadata.php'
359                );
360            }
361
362            foreach ($meta as $key => $value) {
363                $this->assertArrayHasKey(
364                    $key,
365                    $conf,
366                    'Key $conf[\'' . $key . '\'] missing in ' . DOKU_PLUGIN . 'ifauthex/conf/default.php'
367                );
368            }
369        }
370
371    }
372}
373