1<?php
2// must be run within Dokuwiki
3if (!defined('DOKU_INC')) die();
4/**
5 * Two Factor Action Plugin
6 *
7 * @author Mike Wilmes mwilmes@avc.edu
8 * Big thanks to Daniel Popp and his Google 2FA code (authgoogle2fa) as a
9 * starting reference.
10 *
11 * Overview:
12 * The plugin provides for two opportunities to perform two factor
13 * authentication. The first is on the main login page, via a code provided by
14 * an external authenticator. The second is at a separate prompt after the
15 * initial login. By default, all modules will process from the second login,
16 * but a module can subscribe to accepting a password from the main login when
17 * it makes sense, because the user has access to the code in advance.
18 *
19 * If a user only has configured modules that provide for login at the main
20 * screen, the code will only be accepted at the main login screen for
21 * security purposes.
22 *
23 * Modules will be called to render their configuration forms on the profile
24 * page and to verify a user's submitted code. If any module accepts the
25 * submitted code, then the user is granted access.
26 *
27 * Each module may be used to transmit a message to the user that their
28 * account has been logged into. One module may be used as the default
29 * transmit option. These options are handled by the parent module.
30 */
31class action_plugin_autogroup extends DokuWiki_Action_Plugin {
32
33    protected $_disabled = '';      // if disabled set to explanatory string
34    protected $_group_cfg = null;   // The group management rules
35    protected $_add = null;         // A flag to add when any rule matches
36    protected $_remove = null;      // A flag to remove when all rules do not match
37    protected $_in_proc = false;    // A locking flag to keep a group change from triggering this module recursively.
38
39    public function __construct(){
40        $this->setupLocale();
41        $this->_add = (bool)$this->getConf('enable_add');
42        $this->_remove = (bool)$this->getConf('enable_remove');
43        $raw_cfg = array_map(function($x){return explode(',',$x,3);}, explode("\n", $this->getConf('regex')));
44        $this->_group_cfg = array();
45        foreach($raw_cfg as $set) {
46            $key = array_shift($set);
47            $this->_group_cfg[$key][]=$set;
48        }
49        $this->_disabled = ( !$this->_add && !$this->_remove ) || count($this->_group_cfg)==0;
50    }
51
52    /**
53     * Registers the event handlers.
54     */
55    public function register(Doku_Event_Handler $controller)
56    {
57        // In case this was called during setup, call the init again to have a chance to get the authentication module.
58        if (!$this->_disabled) {
59            // Update a single user after their info changes.
60            $controller->register_hook('AUTH_USER_CHANGE', 'AFTER', $this, 'update_group_event');
61
62            // USE THIS IF WE GET THAT CONFIG CHANGE EVENT!!!
63            // Snag this event to update all users if a change was made on a config page.
64            //$controller->register_hook('CONFIG_CHANGE_MADE', 'AFTER', $this, 'check_update_all_event');
65
66            // Snag this event to update all users if a change was made on a config page.
67            $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'check_update_all_event');
68        }
69    }
70
71    /**
72     * Does the group list processing.
73     */
74	public function update_group_event(&$event, $param) {
75        global $INPUT;
76        // If this module processing triggered this event, then abort.
77        if ($this->_in_proc) { return; }
78        // If disabled, then silently return.
79        if ($this->_disabled) {
80            return;
81        }
82        // If we can't read the user accounts or change the groups using the auth module, then abort.
83        global $auth;
84        if (!$auth || !$auth->canDo('getUsers') || !$auth->canDo('modGroups')) {
85            $msg = $this->getLang('nosupport');
86            msg($msg, -1);
87            dbglog($msg);
88            return;
89        }
90
91        // If we have a logged in user, then process.
92        $user = $INPUT->server->str('REMOTE_USER', null);
93        if ($user) { $this->_update_user_groups($user); }
94        return;
95	}
96
97    /**
98     * Does the group list processing.
99     */
100	private function _update_user_groups($user) {
101        global $auth;
102
103        // Set the event lock.
104        $this->_in_proc = true;
105
106        // Get this user's current group data.
107        $oldinfo = $auth->getUserData($user);
108        $oldinfo['user']=$user;
109        $oldgrps = $oldinfo['grps'];
110        $newgrps = array_values($oldgrps);
111
112        // Start validating regex rules.
113        $in = array();
114        $out = array();
115        foreach ($this->_group_cfg as $group=>$lines){
116            $match = false;
117            foreach ($lines as $line) {
118                list($attr, $regex) = $line;
119                $match |= preg_match($regex, $oldinfo[$attr]);
120            }
121            if ($match) {
122                if ($this->_add){
123                    $in[] = $group;
124                    if (!in_array($group,$newgrps)){
125                        $newgrps[] = $group;
126                    }
127                }
128            }
129            else {
130                if ($this->_remove){
131                    $out[] = $group;
132                    if (in_array($group,$newgrps)){
133                        array_splice($newgrps, array_keys($newgrps, $group)[0] , 1);
134                    }
135                }
136            }
137        }
138
139        if ( $newgrps != $oldgrps ) {
140            $changes['grps'] = $newgrps;
141        }
142
143        if ($auth->triggerUserMod('modify', array($user, $changes))) {
144            $msg = sprintf($this->getLang('update_ok'), $user, implode(',',$in), implode(',',$out));
145            dbglog($msg);
146        }
147
148        // Clear the event lock.
149        $this->_in_proc = false;
150    }
151
152    /**
153     * Used to update all groups for all users on the wiki.
154     * Might take a while on large wikis.
155     */
156	public function update_all_groups() {
157        // If disabled, then silently return.
158        if ($this->_disabled) {
159            return;
160        }
161        // If we can't read the user accounts or change the groups using the auth module, then abort.
162        global $auth;
163        if (!$auth || !$auth->canDo('getUsers') || !$auth->canDo('modGroups')) {
164            $msg = $this->getLang('nosupport');
165            msg($msg, -1);
166            dbglog($msg);
167            return;
168        }
169        // Get the user count and the list of users.
170        $userCount = $auth->getUserCount();
171        $userData = $auth->retrieveUsers(0, $userCount, array());
172        // Update groups for each user.
173        foreach ($userData as $user => $userinfo) {
174            $this->_update_user_groups($user);
175        }
176	}
177
178    public function check_update_all_event(&$event, $param){
179        if ($_SESSION['PLUGIN_CONFIG']['state'] == 'updated') {
180            $this->update_all_groups();
181        }
182    }
183}