1<?php
2/**
3 * DokuWiki Plugin forceuserchange (Action Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Henry Pan <dokuwiki.plugin@phy25.com>
7 */
8
9// must be run within Dokuwiki
10if (!defined('DOKU_INC')) {
11    die();
12}
13
14class action_plugin_forceuserchange extends DokuWiki_Action_Plugin
15{
16    const PROFILE_ACT_NAME = 'profile';
17
18    /**
19     * Registers a callback function for a given event
20     *
21     * @param Doku_Event_Handler $controller DokuWiki's event controller object
22     *
23     * @return void
24     */
25    public function register(Doku_Event_Handler $controller)
26    {
27        $controller->register_hook('AUTH_LOGIN_CHECK', 'AFTER', $this, 'handle_auth_login_check');
28        $controller->register_hook('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_action_act_preprocess');
29        $controller->register_hook('ACTION_HEADERS_SEND', 'AFTER', $this, 'handle_tpl_act_render');
30        $controller->register_hook('AUTH_USER_CHANGE', 'AFTER', $this, 'handle_auth_user_change');
31        if (!$this->getConf('allowsamepw')){
32            $controller->register_hook('AUTH_USER_CHANGE', 'BEFORE', $this, 'prevent_same_password');
33        }
34    }
35
36    public function handle_auth_login_check(Doku_Event $event, $param)
37    {
38        if ($event->result == true) {
39            // logged in
40            if ($this->user_required_to_stop()) {
41                // if slient (e.g. rpc, cookie) then reject the login
42                // since we won't have a chance to redirect user to profile change
43                if ($event->data['silent']) {
44                    auth_logoff();
45                    $event->result = false;
46                    return;
47                }
48            }
49        }
50    }
51
52    public function handle_action_act_preprocess(Doku_Event $event, $param)
53    {
54        global $ID;
55        if ($this->user_required_to_stop()) {
56            if ($event->data != self::PROFILE_ACT_NAME) {
57                // not silent: we need to redirect user to profile page, if they are not there
58                // this is put here to prevent profile page rewriting $ACT after an attempt
59                // but user is still required to change as required
60                // when profile rewrites $ACT this will be called multiple times
61                return send_redirect(wl($ID, array('do' => self::PROFILE_ACT_NAME), true, '&'));
62            }
63        }
64    }
65
66    /**
67     * Inject message to inform user that they need to complete a change
68     */
69    public function handle_tpl_act_render(Doku_Event $event, $param)
70    {
71        global $ACT;
72        if ($this->user_required_to_stop()) {
73            if ($ACT == self::PROFILE_ACT_NAME) {
74                msg(sprintf($this->getLang('msg_forceupdate'), $this->getConf('allowsamepw') ? $this->getLang('msg_sameallowed') : ''));
75            }
76        }
77    }
78
79    protected function get_user_groups($user = null) {
80        global $USERINFO, $auth, $INPUT;
81        $uinfo = $USERINFO;
82        if ($user && $user !== $INPUT->server->str('REMOTE_USER')) {
83            // fetch user info
84            $uinfo = $auth->getUserData($user);
85        }
86        return (array) $uinfo['grps'];
87    }
88
89    protected function user_required_to_stop($user = null) {
90        global $INPUT;
91        $grps = $this->get_user_groups($user);
92        $has_group = array_search($this->getConf('groupname'), $grps) !== false;
93        if ($this->getConf('grouprel') == 'excluding') {
94            return !empty($INPUT->server->str('REMOTE_USER')) && !$has_group; // users not having the group are required to stop
95        }else{
96            return $has_group;
97        }
98    }
99
100    /**
101     * Change user group accordingly if user did the required change
102     */
103    public function handle_auth_user_change(Doku_Event $event, $param)
104    {
105        global $auth;
106        if (
107            $event->data['type'] == 'modify' && isset($event->data['params'][1]['pass']) &&
108            $event->data['modification_result'] && $this->user_required_to_stop($event->data['params'][0])
109            ) {
110            // modify group
111            $user = $event->data['params'][0];
112            $grps = $this->get_user_groups($user);
113            $key = array_search($this->getConf('groupname'), $grps);
114            if ($this->getConf('grouprel') == 'including') {
115                if ($key !== false) {
116                    array_splice($grps, $key, 1);
117                    $auth->triggerUserMod('modify', array($user, array('grps'=>$grps)));
118                }
119            } else {
120                if ($key === false) {
121                    $grps[] = $this->getConf('groupname');
122                    $auth->triggerUserMod('modify', array($user, array('grps'=>$grps)));
123                }
124            }
125        }
126    }
127
128    public function prevent_same_password(Doku_Event $event, $param)
129    {
130        global $auth, $INPUT;
131        if (
132            $event->data['type'] == 'modify' && isset($event->data['params'][1]['pass']) &&
133            $event->data['params'][0] == $INPUT->server->str('REMOTE_USER') &&
134            $this->user_required_to_stop($event->data['params'][0])
135            )
136        {
137            // check password
138            $user = $event->data['params'][0];
139            $pass = $event->data['params'][1]['pass'];
140            if (!$auth->canDo('external') && $auth->checkPass($user, $pass)){
141                // same password
142                $event->preventDefault();
143                msg($this->getLang('msg_errorpw'), -1);
144            }
145        }
146    }
147}
148