1: <?php
2: /*
3: * SimpleID
4: *
5: * Copyright (C) Kelvin Mo 2014-2026
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\LoginFormBuildEvent;
28: use SimpleID\Auth\NonInteractiveAuthEvent;
29: use SimpleID\Crypt\Random;
30: use SimpleID\Crypt\SecurityToken;
31: use SimpleID\Store\StoreManager;
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 LoginFormBuildEvent $event
105: * @return void
106: */
107: public function onLoginFormBuild(LoginFormBuildEvent $event) {
108: $form_state = $event->getFormState();
109:
110: if ($form_state['mode'] == AuthManager::MODE_IDENTIFY_USER) {
111: $tpl = Template::instance();
112: $event->addBlock('auth_rememberme', $tpl->render('auth_rememberme.html', false), 10, [ 'region' => LoginFormBuildEvent::OPTIONS_REGION ]);
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 (in_array($form_state['mode'], [ AuthManager::MODE_IDENTIFY_USER, AuthManager::MODE_CREDENTIALS ])) {
127: if ($this->f3->exists('POST.rememberme') === true) {
128: $form_state['rememberme'] = $this->f3->get('POST.rememberme');
129: } elseif ($form_state->exists('rememberme')) {
130: if (!$this->f3->exists('POST.rememberme'))
131: $form_state->unset('rememberme');
132: }
133: }
134: }
135:
136: /**
137: * Completes the login process by issuing a auto login cookie (if
138: * so selected by the user).
139: *
140: * @see LoginEvent
141: * @return void
142: */
143: public function onLoginEvent(LoginEvent $event) {
144: $level = $event->getAuthLevel();
145: $form_state = $event->getFormState();
146:
147: if (($level == AuthManager::MODE_CREDENTIALS) && isset($form_state['rememberme']) && ($form_state['rememberme'] == 1)) {
148: $this->createCookie();
149: }
150: }
151:
152: /**
153: * @return void
154: */
155: public function onLogoutEvent(LogoutEvent $event) {
156: $this->removeCookie();
157: }
158:
159: /**
160: * @return void
161: */
162: public function onCredentialEvent(CredentialEvent $event) {
163: if ($event->getAuthModuleName() != self::class) {
164: $this->removeCookie();
165: }
166: }
167:
168: /**
169: * Removes the auto login cookie from the user agent.
170: *
171: * @return void
172: */
173: public function removeCookie() {
174: if ($this->f3->exists('COOKIE.' . $this->cookie_name)) {
175: $cookie = $this->f3->clear('COOKIE.' . $this->cookie_name);
176: }
177: }
178:
179: /**
180: * Creates a auto login cookie. The login cookie will be based on the
181: * current log in user.
182: *
183: * @param string $id the ID of the series of auto login cookies, Cookies
184: * belonging to the same user and computer have the same ID. If none is specified,
185: * one will be generated
186: * @param int $expires the time at which the cookie will expire. If none is specified
187: * the time specified in {@link SIMPLEID_REMEMBERME_EXPIRES_IN} will be
188: * used
189: * @return void
190: */
191: protected function createCookie($id = NULL, $expires = NULL) {
192: $user = $this->auth->getUser();
193:
194: $rand = new Random();
195:
196: if ($expires == NULL) {
197: $this->logger->log(LogLevel::DEBUG, 'Automatic login token created for ' . $user['uid']);
198: } else {
199: $this->logger->log(LogLevel::DEBUG, 'Automatic login token renewed for ' . $user['uid']);
200: }
201:
202: if ($id == NULL) $id = $rand->id();
203:
204: if ($expires == NULL) $expires = time() + SIMPLEID_LONG_TOKEN_EXPIRES_IN;
205:
206: $data = [
207: 'typ' => 'rememberme',
208: 'id' => $id,
209: 'uid' => $user['uid'],
210: 'exp' => $expires,
211: 'uaid' => $this->auth->assignUAID(),
212: ];
213:
214: $token = new SecurityToken();
215: $cookie = $token->generate($data);
216:
217: $this->f3->set('COOKIE.' . $this->cookie_name, $cookie, SIMPLEID_LONG_TOKEN_EXPIRES_IN);
218: }
219: }
220: ?>