1: <?php
2: /*
3: * SimpleID
4: *
5: * Copyright (C) Kelvin Mo 2014-2025
6: *
7: * This program is free software; you can redistribute it and/or
8: * modify it under the terms of the GNU General Public
9: * License as published by the Free Software Foundation; either
10: * version 2 of the License, or (at your option) any later version.
11: *
12: * This program is distributed in the hope that it will be useful,
13: * but WITHOUT ANY WARRANTY; without even the implied warranty of
14: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15: * General Public License for more details.
16: *
17: * You should have received a copy of the GNU General Public
18: * License along with this program; if not, write to the Free
19: * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20: *
21: */
22:
23: namespace SimpleID\Auth;
24:
25: use Psr\Log\LogLevel;
26: use SimpleID\Auth\CredentialEvent;
27: use SimpleID\Auth\NonInteractiveAuthEvent;
28: use SimpleID\Crypt\Random;
29: use SimpleID\Crypt\SecurityToken;
30: use SimpleID\Store\StoreManager;
31: use SimpleID\Util\Forms\FormBuildEvent;
32: use SimpleID\Util\UI\Template;
33:
34: /**
35: * An authentication scheme that provides automatic authentication
36: * via a cookie stored in the user agent.
37: */
38: class RememberMeAuthSchemeModule extends AuthSchemeModule {
39: /**
40: * The name of the cookie
41: *
42: * @var string
43: */
44: protected $cookie_name;
45:
46: public function __construct() {
47: parent::__construct();
48: $this->cookie_name = $this->auth->getCookieName('auth');
49: }
50:
51: /**
52: * Attempts to automatically login using the auto login cookie
53: *
54: * @see NonInteractiveAuthEvent
55: * @return void
56: */
57: public function onNonInteractiveAuthEvent(NonInteractiveAuthEvent $event) {
58: if (!$this->f3->exists('COOKIE.' . $this->cookie_name)) return;
59:
60: $cookie = $this->f3->get('COOKIE.' . $this->cookie_name);
61:
62: $token = new SecurityToken();
63: $data = $token->getPayload($cookie);
64:
65: if ($data === null) {
66: $this->logger->log(LogLevel::NOTICE, 'Automatic login: Invalid token - clearing');
67: $this->removeCookie();
68: return;
69: }
70:
71: if ($data['typ'] != 'rememberme') return;
72:
73: $this->logger->log(LogLevel::DEBUG, 'Automatic login token detected', $data);
74:
75: if ($data['exp'] < time()) { // Cookie expired
76: $this->logger->log(LogLevel::NOTICE, 'Automatic login: Expired - clearing');
77: $this->removeCookie();
78: return;
79: }
80:
81: if ($data['uaid'] != $this->auth->assignUAID()) {
82: $this->logger->log(LogLevel::WARNING, 'Automatic login: User agent ID does not match - clearing');
83: $this->removeCookie();
84: return;
85: }
86:
87: $store = StoreManager::instance();
88:
89: // Load the user, tag it as an auto log in
90: $test_user = $store->loadUser($data['uid']);
91:
92: if ($test_user != NULL) {
93: $this->logger->log(LogLevel::INFO, 'Automatic login token accepted for ' . $data['uid']);
94: $event->setUser($test_user, static::class);
95: $event->setAuthLevel(AuthManager::AUTH_LEVEL_NON_INTERACTIVE);
96: } else {
97: $this->logger->log(LogLevel::WARNING, 'Automatic login token accepted for ' . $data['uid'] . ', but no such user exists');
98: }
99: }
100:
101: /**
102: * Displays the login form, with a remember-me checkbox.
103: *
104: * @param FormBuildEvent $event
105: * @return void
106: */
107: public function onLoginFormBuild(FormBuildEvent $event) {
108: $form_state = $event->getFormState();
109:
110: if ($form_state['mode'] == AuthManager::MODE_CREDENTIALS) {
111: $tpl = Template::instance();
112: $event->addBlock('auth_rememberme', $tpl->render('auth_rememberme.html', false), 10);
113: }
114: }
115:
116: /**
117: * Processes the login form by storing the user's remember-me setting
118: * in the form state.
119: *
120: * @param LoginFormSubmitEvent $event
121: * @return void
122: */
123: public function onLoginFormSubmit(LoginFormSubmitEvent $event) {
124: $form_state = $event->getFormState();
125:
126: if ($form_state['mode'] == AuthManager::MODE_CREDENTIALS) {
127: if ($this->f3->exists('POST.rememberme') === true) {
128: $form_state['rememberme'] = $this->f3->get('POST.rememberme');
129: }
130: }
131: }
132:
133: /**
134: * Completes the login process by issuing a auto login cookie (if
135: * so selected by the user).
136: *
137: * @see LoginEvent
138: * @return void
139: */
140: public function onLoginEvent(LoginEvent $event) {
141: $level = $event->getAuthLevel();
142: $form_state = $event->getFormState();
143:
144: if (($level == AuthManager::MODE_CREDENTIALS) && isset($form_state['rememberme']) && ($form_state['rememberme'] == 1)) {
145: $this->createCookie();
146: }
147: }
148:
149: /**
150: * @return void
151: */
152: public function onLogoutEvent(LogoutEvent $event) {
153: $this->removeCookie();
154: }
155:
156: /**
157: * @return void
158: */
159: public function onCredentialEvent(CredentialEvent $event) {
160: if ($event->getAuthModuleName() != self::class) {
161: $this->removeCookie();
162: }
163: }
164:
165: /**
166: * Removes the auto login cookie from the user agent.
167: *
168: * @return void
169: */
170: public function removeCookie() {
171: if ($this->f3->exists('COOKIE.' . $this->cookie_name)) {
172: $cookie = $this->f3->clear('COOKIE.' . $this->cookie_name);
173: }
174: }
175:
176: /**
177: * Creates a auto login cookie. The login cookie will be based on the
178: * current log in user.
179: *
180: * @param string $id the ID of the series of auto login cookies, Cookies
181: * belonging to the same user and computer have the same ID. If none is specified,
182: * one will be generated
183: * @param int $expires the time at which the cookie will expire. If none is specified
184: * the time specified in {@link SIMPLEID_REMEMBERME_EXPIRES_IN} will be
185: * used
186: * @return void
187: */
188: protected function createCookie($id = NULL, $expires = NULL) {
189: $user = $this->auth->getUser();
190:
191: $rand = new Random();
192:
193: if ($expires == NULL) {
194: $this->logger->log(LogLevel::DEBUG, 'Automatic login token created for ' . $user['uid']);
195: } else {
196: $this->logger->log(LogLevel::DEBUG, 'Automatic login token renewed for ' . $user['uid']);
197: }
198:
199: if ($id == NULL) $id = $rand->id();
200:
201: if ($expires == NULL) $expires = time() + SIMPLEID_LONG_TOKEN_EXPIRES_IN;
202:
203: $data = [
204: 'typ' => 'rememberme',
205: 'id' => $id,
206: 'uid' => $user['uid'],
207: 'exp' => $expires,
208: 'uaid' => $this->auth->assignUAID(),
209: ];
210:
211: $token = new SecurityToken();
212: $cookie = $token->generate($data);
213:
214: $this->f3->set('COOKIE.' . $this->cookie_name, $cookie, SIMPLEID_LONG_TOKEN_EXPIRES_IN);
215: }
216: }
217: ?>