Overview

Namespaces

  • Charcoal
    • Object
    • User
      • Acl

Classes

  • Charcoal\Object\Content
  • Charcoal\Object\ObjectRevision
  • Charcoal\Object\ObjectRoute
  • Charcoal\Object\ObjectSchedule
  • Charcoal\Object\UserData
  • Charcoal\User\AbstractUser
  • Charcoal\User\Acl\Manager
  • Charcoal\User\Acl\Permission
  • Charcoal\User\Acl\PermissionCategory
  • Charcoal\User\Acl\Role
  • Charcoal\User\Authenticator
  • Charcoal\User\Authorizer
  • Charcoal\User\AuthToken
  • Charcoal\User\AuthTokenMetadata
  • Charcoal\User\GenericUser

Interfaces

  • Charcoal\Object\ArchivableInterface
  • Charcoal\Object\CategorizableInterface
  • Charcoal\Object\CategorizableMultipleInterface
  • Charcoal\Object\CategoryInterface
  • Charcoal\Object\ContentInterface
  • Charcoal\Object\HierarchicalInterface
  • Charcoal\Object\ObjectRevisionInterface
  • Charcoal\Object\ObjectRouteInterface
  • Charcoal\Object\ObjectScheduleInterface
  • Charcoal\Object\PublishableInterface
  • Charcoal\Object\RevisionableInterface
  • Charcoal\Object\RoutableInterface
  • Charcoal\Object\UserDataInterface
  • Charcoal\User\UserInterface

Traits

  • Charcoal\Object\ArchivableTrait
  • Charcoal\Object\CategorizableMultipleTrait
  • Charcoal\Object\CategorizableTrait
  • Charcoal\Object\CategoryTrait
  • Charcoal\Object\HierarchicalTrait
  • Charcoal\Object\PublishableTrait
  • Charcoal\Object\RevisionableTrait
  • Charcoal\Object\RoutableTrait
  • Overview
  • Namespace
  • Class
  1: <?php
  2: 
  3: namespace Charcoal\User;
  4: 
  5: use InvalidArgumentException;
  6: use RuntimeException;
  7: 
  8: // Dependencies from PSR-3 (Logging)
  9: use Psr\Log\LoggerAwareInterface;
 10: use Psr\Log\LoggerAwareTrait;
 11: 
 12: // Dependency from 'charcoal-factory'
 13: use Charcoal\Factory\FactoryInterface;
 14: 
 15: /**
 16:  * The Authenticator service helps with user authentication / login.
 17:  *
 18:  * ## Constructor dependencies
 19:  *
 20:  * Constructor dependencies are passed as an array of `key=>value` pair.
 21:  * The required dependencies are:
 22:  *
 23:  * - `logger` A PSR3 logger instance
 24:  * - `user_type` The user object type (FQN or ident)
 25:  * - `user_factory` The Factory used to instanciate new users.
 26:  * - `token_type` The auth token object type (FQN or ident)
 27:  * - `token_factory` The Factory used to instanciate new auth tokens.
 28:  */
 29: class Authenticator implements LoggerAwareInterface
 30: {
 31:     use LoggerAwareTrait;
 32: 
 33:     /**
 34:      * The user object type.
 35:      *
 36:      * @var string
 37:      */
 38:     private $userType;
 39: 
 40:     /**
 41:      * Store the user model factory instance for the current class.
 42:      *
 43:      * @var FactoryInterface
 44:      */
 45:     private $userFactory;
 46: 
 47:     /**
 48:      * The auth-token object type.
 49:      *
 50:      * @var string
 51:      */
 52:     private $tokenType;
 53: 
 54:     /**
 55:      * Store the auth-token model factory instance for the current class.
 56:      *
 57:      * @var FactoryInterface
 58:      */
 59:     private $tokenFactory;
 60: 
 61:     /**
 62:      * Returns a new Authoricator object.
 63:      *
 64:      * @param array $data Class dependencies.
 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:      * Set the user object type (model).
 77:      *
 78:      * @param string $type The user object type.
 79:      * @throws InvalidArgumentException If the user object type parameter is not a string.
 80:      * @return AdminAuthenticator Chainable
 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:      * Retrieve the user object type.
 97:      *
 98:      * @return string
 99:      */
100:     protected function userType()
101:     {
102:         return $this->userType;
103:     }
104: 
105:     /**
106:      * Set a user model factory.
107:      *
108:      * @param FactoryInterface $factory The factory used to create new user instances.
109:      * @return AdminAuthenticator Chainable
110:      */
111:     private function setUserFactory(FactoryInterface $factory)
112:     {
113:         $this->userFactory = $factory;
114: 
115:         return $this;
116:     }
117: 
118:     /**
119:      * Retrieve the user model factory.
120:      *
121:      * @throws RuntimeException If the model factory was not previously set.
122:      * @return FactoryInterface
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:      * Set the authorization token type (model).
137:      *
138:      * @param string $type The auth-token object type.
139:      * @throws InvalidArgumentException If the token object type parameter is not a string.
140:      * @return AdminAuthenticator Chainable
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:      * Retrieve the auth-token object type.
157:      *
158:      * @return string
159:      */
160:     protected function tokenType()
161:     {
162:         return $this->tokenType;
163:     }
164: 
165:     /**
166:      * Set a model factory for token-based authentication.
167:      *
168:      * @param FactoryInterface $factory The factory used to create new auth-token instances.
169:      * @return AdminAuthenticator Chainable
170:      */
171:     private function setTokenFactory(FactoryInterface $factory)
172:     {
173:         $this->tokenFactory = $factory;
174: 
175:         return $this;
176:     }
177: 
178:     /**
179:      * Retrieve the auth-token model factory.
180:      *
181:      * @throws RuntimeException If the token factory was not previously set.
182:      * @return FactoryInterface
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:      * Determine if the current user is authenticated.
197:      *
198:      * The user is authenticated via _session ID_ or _auth token_.
199:      *
200:      * @return \Charcoal\User\UserInterface|null Returns the authenticated user object
201:      *     or NULL if not authenticated.
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:      * Attempt to authenticate a user using the given credentials.
220:      *
221:      * @param string $username Username, part of necessery credentials.
222:      * @param string $password Password, part of necessary credentials.
223:      * @throws InvalidArgumentException If username or password are invalid or empty.
224:      * @return \Charcoal\User\UserInterface|null Returns the authenticated user object
225:      *     or NULL if not authenticated.
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:         // Force lowercase
247:         $username = mb_strtolower($username);
248: 
249:         // Load the user by username
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:         // Validate password
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:      * Attempt to authenticate a user using their session ID.
285:      *
286:      * @return \Charcoal\User\UserInterface|null Returns the authenticated user object
287:      *     or NULL if not authenticated.
288:      */
289:     private function authenticateBySession()
290:     {
291:         $u = $this->userFactory()->create($this->userType());
292:         // Call static method on user
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:      * Attempt to authenticate a user using their auth token.
306:      *
307:      * @return \Charcoal\User\UserInterface|null Returns the authenticated user object
308:      *     or NULL if not authenticated.
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: 
API documentation generated by ApiGen