1: <?php
2:
3: namespace Charcoal\User;
4:
5: use InvalidArgumentException;
6: use RuntimeException;
7:
8:
9: use Psr\Log\LoggerAwareInterface;
10: use Psr\Log\LoggerAwareTrait;
11:
12:
13: use Charcoal\Factory\FactoryInterface;
14:
15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:
29: class Authenticator implements LoggerAwareInterface
30: {
31: use LoggerAwareTrait;
32:
33: 34: 35: 36: 37:
38: private $userType;
39:
40: 41: 42: 43: 44:
45: private $userFactory;
46:
47: 48: 49: 50: 51:
52: private $tokenType;
53:
54: 55: 56: 57: 58:
59: private $tokenFactory;
60:
61: 62: 63: 64: 65:
66: public function __construct(array $data)
67: {
68: $this->setLogger($data['logger']);
69: $this->setUserType($data['user_type']);
70: $this->setUserFactory($data['user_factory']);
71: $this->setTokenType($data['token_type']);
72: $this->setTokenFactory($data['token_factory']);
73: }
74:
75: 76: 77: 78: 79: 80: 81:
82: private function setUserType($type)
83: {
84: if (!is_string($type)) {
85: throw new InvalidArgumentException(
86: 'User object type must be a string'
87: );
88: }
89:
90: $this->userType = $type;
91:
92: return $this;
93: }
94:
95: 96: 97: 98: 99:
100: protected function userType()
101: {
102: return $this->userType;
103: }
104:
105: 106: 107: 108: 109: 110:
111: private function setUserFactory(FactoryInterface $factory)
112: {
113: $this->userFactory = $factory;
114:
115: return $this;
116: }
117:
118: 119: 120: 121: 122: 123:
124: protected function userFactory()
125: {
126: if (!isset($this->userFactory)) {
127: throw new RuntimeException(
128: sprintf('User Factory is not defined for "%s"', get_class($this))
129: );
130: }
131:
132: return $this->userFactory;
133: }
134:
135: 136: 137: 138: 139: 140: 141:
142: private function setTokenType($type)
143: {
144: if (!is_string($type)) {
145: throw new InvalidArgumentException(
146: 'Token object type must be a string'
147: );
148: }
149:
150: $this->tokenType = $type;
151:
152: return $this;
153: }
154:
155: 156: 157: 158: 159:
160: protected function tokenType()
161: {
162: return $this->tokenType;
163: }
164:
165: 166: 167: 168: 169: 170:
171: private function setTokenFactory(FactoryInterface $factory)
172: {
173: $this->tokenFactory = $factory;
174:
175: return $this;
176: }
177:
178: 179: 180: 181: 182: 183:
184: protected function tokenFactory()
185: {
186: if (!isset($this->tokenFactory)) {
187: throw new RuntimeException(
188: sprintf('Token Factory is not defined for "%s"', get_class($this))
189: );
190: }
191:
192: return $this->tokenFactory;
193: }
194:
195: 196: 197: 198: 199: 200: 201: 202:
203: public function authenticate()
204: {
205: $u = $this->authenticateBySession();
206: if ($u) {
207: return $u;
208: }
209:
210: $u = $this->authenticateByToken();
211: if ($u) {
212: return $u;
213: }
214:
215: return null;
216: }
217:
218: 219: 220: 221: 222: 223: 224: 225: 226:
227: public function authenticateByPassword($username, $password)
228: {
229: if (!is_string($username) || !is_string($password)) {
230: throw new InvalidArgumentException(
231: 'Username and password must be strings'
232: );
233: }
234:
235: if ($username == '' || $password == '') {
236: throw new InvalidArgumentException(
237: 'Username and password can not be empty.'
238: );
239: }
240:
241: $u = $this->userFactory()->create($this->userType());
242: if (!$u->source()->tableExists()) {
243: $u->source()->createTable();
244: }
245:
246:
247: $username = mb_strtolower($username);
248:
249:
250: $u->load($username);
251:
252: if ($u->username() != $username) {
253: return null;
254: }
255:
256: if ($u->active() === false) {
257: return null;
258: }
259:
260:
261: if (password_verify($password, $u->password())) {
262: if (password_needs_rehash($u->password(), PASSWORD_DEFAULT)) {
263: $this->logger->notice(
264: sprintf('Rehashing password for user "%s" (%s)', $u->username(), $this->userType())
265: );
266: $hash = password_hash($password, PASSWORD_DEFAULT);
267: $u->setPassword($hash);
268: $u->update(['password']);
269: }
270:
271: $u->login();
272:
273: return $u;
274: } else {
275: $this->logger->warning(
276: sprintf('Invalid login attempt for user: invalid password.')
277: );
278:
279: return null;
280: }
281: }
282:
283: 284: 285: 286: 287: 288:
289: private function authenticateBySession()
290: {
291: $u = $this->userFactory()->create($this->userType());
292:
293: $u = call_user_func([get_class($u), 'getAuthenticated'], $this->userFactory());
294:
295: if ($u && $u->id()) {
296: $u->saveToSession();
297:
298: return $u;
299: } else {
300: return null;
301: }
302: }
303:
304: 305: 306: 307: 308: 309:
310: private function authenticateByToken()
311: {
312: $tokenType = $this->tokenType();
313: $authToken = $this->tokenFactory()->create($tokenType);
314:
315: if ($authToken->metadata()->enabled() !== true) {
316: return null;
317: }
318:
319: $tokenData = $authToken->getTokenDataFromCookie();
320: if (!$tokenData) {
321: return null;
322: }
323: $username = $authToken->getUsernameFromToken($tokenData['ident'], $tokenData['token']);
324: if (!$username) {
325: return null;
326: }
327:
328: $u = $this->userFactory()->create($this->userType());
329: $u->load($username);
330:
331: if ($u->id()) {
332: $u->saveToSession();
333: return $u;
334: } else {
335: return null;
336: }
337: }
338: }
339: