Skip to content

Commit 92b23cd

Browse files
committed
Update for tags + authentication chapters
1 parent 55c9f39 commit 92b23cd

10 files changed

Lines changed: 120 additions & 144 deletions

File tree

‎composer.json‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@
55
"type": "project",
66
"license": "MIT",
77
"require": {
8-
"php": ">=5.6",
9-
"cakephp/cakephp": "3.5.*",
10-
"mobiledetect/mobiledetectlib": "2.*",
11-
"cakephp/migrations": "~1.0",
8+
"php": ">=7.2",
9+
"cakephp/cakephp": "^4.0",
10+
"cakephp/migrations": "~3.0",
11+
"cakephp/authorization": "~2.0",
12+
"cakephp/authentication": "~2.0",
1213
"cakephp/plugin-installer": "~1.0",
1314
"josegonzalez/dotenv": "2.*"
1415
},
1516
"require-dev": {
1617
"psy/psysh": "@stable",
17-
"cakephp/debug_kit": "~3.2",
18-
"cakephp/bake": "~1.1",
19-
"cakephp/cakephp-codesniffer": "^3.0"
18+
"cakephp/debug_kit": "^4.0",
19+
"cakephp/bake": "^2.0",
20+
"cakephp/cakephp-codesniffer": "^4.0"
2021
},
2122
"suggest": {
2223
"markstory/asset_compress": "An asset compression plugin which provides file concatenation and a flexible filter system for preprocessing and minification.",
@@ -43,9 +44,11 @@
4344
"@test",
4445
"@cs-check"
4546
],
46-
"cs-check": "phpcs --colors -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests",
47-
"cs-fix": "phpcbf --colors --standard=vendor/cakephp/cakephp-codesniffer/CakePHP ./src ./tests",
47+
"cs-check": "phpcs --colors -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/",
48+
"cs-fix": "phpcbf --colors --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/",
49+
"stan": "phpstan analyse src/",
4850
"test": "phpunit --colors=always"
4951
},
52+
"minimum-stability": "dev",
5053
"prefer-stable": true
5154
}

‎config/routes.php‎

Lines changed: 14 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* @license https://opensource.org/licenses/mit-license.php MIT License
1919
*/
2020

21-
use Cake\Core\Plugin;
21+
use Cake\Http\Middleware\CsrfProtectionMiddleware;
2222
use Cake\Routing\RouteBuilder;
2323
use Cake\Routing\Router;
2424
use Cake\Routing\Route\DashedRoute;
@@ -43,51 +43,25 @@
4343
*/
4444
Router::defaultRouteClass(DashedRoute::class);
4545

46-
// New route we're adding for our tagged action.
47-
// The trailing `*` tells CakePHP that this action has
48-
// passed parameters.
49-
Router::scope(
50-
'/articles',
51-
['controller' => 'Articles'],
52-
function ($routes) {
53-
$routes->connect('/tagged/*', ['action' => 'tags']);
54-
}
55-
);
56-
5746
Router::scope('/', function (RouteBuilder $routes) {
58-
/**
59-
* Here, we are connecting '/' (base path) to a controller called 'Pages',
60-
* its action called 'display', and we pass a param to select the view file
61-
* to use (in this case, src/Template/Pages/home.ctp)...
62-
*/
47+
// Register scoped middleware for in scopes.
48+
$routes->registerMiddleware('csrf', new CsrfProtectionMiddleware([
49+
'httpOnly' => true
50+
]));
51+
$routes->applyMiddleware('csrf');
6352
$routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
64-
65-
/**
66-
* ...and connect the rest of 'Pages' controller's URLs.
67-
*/
6853
$routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);
6954

55+
56+
// New route we're adding for our tagged action.
57+
// The trailing `*` tells CakePHP that this action has
58+
// passed parameters.
59+
$routes->scope('/articles', function ($routes) {
60+
$routes->connect('/tagged/*', ['controller' => 'Articles', 'action' => 'tags']);
61+
});
62+
7063
/**
7164
* Connect catchall routes for all controllers.
72-
*
73-
* Using the argument `DashedRoute`, the `fallbacks` method is a shortcut for
74-
* `$routes->connect('/:controller', ['action' => 'index'], ['routeClass' => 'DashedRoute']);`
75-
* `$routes->connect('/:controller/:action/*', [], ['routeClass' => 'DashedRoute']);`
76-
*
77-
* Any route class can be used with this method, such as:
78-
* - DashedRoute
79-
* - InflectedRoute
80-
* - Route
81-
* - Or your own route class
82-
*
83-
* You can remove these routes once you've connected the
84-
* routes you want in your application.
8565
*/
8666
$routes->fallbacks(DashedRoute::class);
8767
});
88-
89-
/**
90-
* Load all plugin routes. See the Plugin documentation on
91-
* how to customize the loading of plugin routes.
92-
*/
93-
Plugin::routes();

‎src/Application.php‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,42 @@ public function middleware($middlewareQueue): \Cake\Http\MiddlewareQueue
4545
->add(AssetMiddleware::class)
4646

4747
// Add routing middleware.
48-
->add(new RoutingMiddleware($this));
48+
->add(new RoutingMiddleware($this))
49+
50+
// add Authentication after RoutingMiddleware
51+
->add(new \Authentication\Middleware\AuthenticationMiddleware($this->configAuth()));
52+
53+
return $middlewareQueue;
4954

5055
return $middlewareQueue;
5156
}
57+
58+
protected function configAuth(): AuthenticationService
59+
{
60+
$authenticationService = new AuthenticationService([
61+
'unauthenticatedRedirect' => '/users/login',
62+
'queryParam' => 'redirect',
63+
]);
64+
65+
// Load identifiers, ensure we check email and password fields
66+
$authenticationService->loadIdentifier('Authentication.Password', [
67+
'fields' => [
68+
'username' => 'email',
69+
'password' => 'password',
70+
]
71+
]);
72+
73+
// Load the authenticators, you want session first
74+
$authenticationService->loadAuthenticator('Authentication.Session');
75+
// Configure form data check to pick email and password
76+
$authenticationService->loadAuthenticator('Authentication.Form', [
77+
'fields' => [
78+
'username' => 'email',
79+
'password' => 'password',
80+
],
81+
'loginUrl' => '/users/login',
82+
]);
83+
84+
return $authenticationService;
85+
}
5286
}

‎src/Controller/AppController.php‎

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
namespace App\Controller;
1616

1717
use Cake\Controller\Controller;
18-
use Cake\Event\Event;
18+
use Cake\Event\EventInterface;
1919

2020
/**
2121
* Application Controller
@@ -42,51 +42,15 @@ public function initialize(): void
4242
parent::initialize();
4343
$this->loadComponent('RequestHandler');
4444
$this->loadComponent('Flash');
45-
46-
$this->loadComponent('Auth', [
47-
'authorize'=> 'Controller',
48-
'authenticate' => [
49-
'Form' => [
50-
'fields' => [
51-
'username' => 'email',
52-
'password' => 'password'
53-
]
54-
]
55-
],
56-
'loginAction' => [
57-
'controller' => 'Users',
58-
'action' => 'login'
59-
],
60-
// If unauthorized, return them to page they were just on
61-
'unauthorizedRedirect' => $this->referer()
62-
]);
63-
64-
// Allow the display action so our PagesController
65-
// continues to work. Also enable the read only actions.
66-
$this->Auth->allow(['display', 'view', 'index']);
67-
}
68-
69-
/**
70-
* Before render callback.
71-
*
72-
* @param \Cake\Event\Event $event The beforeRender event.
73-
* @return \Cake\Http\Response|null|void
74-
*/
75-
public function beforeRender(\Cake\Event\EventInterface $event)
76-
{
77-
// Note: These defaults are just to get started quickly with development
78-
// and should not be used in production. You should instead set "_serialize"
79-
// in each action as required.
80-
if (!array_key_exists('_serialize', $this->viewVars) &&
81-
in_array($this->response->type(), ['application/json', 'application/xml'])
82-
) {
83-
$this->set('_serialize', true);
84-
}
45+
$this->loadComponent('Authorization.Authorization');
46+
$this->loadComponent('Authentication.Authentication');
8547
}
8648

87-
public function isAuthorized($user)
49+
public function beforeFilter(EventInterface $event)
8850
{
89-
// By default deny access.
90-
return false;
51+
parent::beforeFilter($event);
52+
// for all controllers in our application, make index and view
53+
// actions public, skipping the authentication check
54+
$this->Authentication->addUnauthenticatedActions(['index', 'view']);
9155
}
9256
}

‎src/Controller/ArticlesController.php‎

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -103,24 +103,4 @@ public function tags()
103103
'tags' => $tags
104104
]);
105105
}
106-
107-
public function isAuthorized($user)
108-
{
109-
$action = $this->request->getParam('action');
110-
// The add and tags actions are always allowed to logged in users.
111-
if (in_array($action, ['add', 'tags'])) {
112-
return true;
113-
}
114-
115-
// All other actions require a slug.
116-
$slug = $this->request->getParam('pass.0');
117-
if (!$slug) {
118-
return false;
119-
}
120-
121-
// Check that the article belongs to the current user.
122-
$article = $this->Articles->findBySlug($slug)->first();
123-
124-
return $article->user_id === $user['id'];
125-
}
126106
}

‎src/Controller/UsersController.php‎

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
namespace App\Controller;
33

44
use App\Controller\AppController;
5+
use Cake\Event\EventInterface;
56

67
/**
78
* Users Controller
@@ -12,10 +13,11 @@
1213
*/
1314
class UsersController extends AppController
1415
{
15-
public function initialize(): void
16+
public function beforeFilter(EventInterface $event)
1617
{
17-
parent::initialize();
18-
$this->Auth->allow(['logout', 'add']);
18+
parent::beforeFilter($event);
19+
// Allow login and add
20+
$this->Authentication->addUnauthenticatedActions(['login', 'add']);
1921
}
2022

2123
/**
@@ -31,22 +33,34 @@ public function index()
3133
$this->set('_serialize', ['users']);
3234
}
3335

34-
public function login()
35-
{
36-
if ($this->request->is('post')) {
37-
$user = $this->Auth->identify();
38-
if ($user) {
39-
$this->Auth->setUser($user);
40-
return $this->redirect($this->Auth->redirectUrl());
41-
}
42-
$this->Flash->error('Your username or password is incorrect.');
36+
public function login() {
37+
$this->request->allowMethod(['get', 'post']);
38+
$result = $this->Authentication->getResult();
39+
// regardless of POST or GET, redirect if user is logged in
40+
if ($result->isValid()) {
41+
// redirect to /pages/home after login success
42+
$redirect = $this->request->getQuery('redirect', [
43+
'controller' => 'Pages',
44+
'action' => 'display',
45+
'home',
46+
]);
47+
48+
return $this->redirect($redirect);
49+
}
50+
// display error if user submitted and authentication failed
51+
if ($this->request->is('post') && !$result->isValid()) {
52+
$this->Flash->error(__('Invalid username or password'));
4353
}
4454
}
4555

4656
public function logout()
4757
{
48-
$this->Flash->success('You are now logged out.');
49-
return $this->redirect($this->Auth->logout());
58+
$result = $this->Authentication->getResult();
59+
// regardless of POST or GET, redirect if user is logged in
60+
if ($result->isValid()) {
61+
$this->Authentication->logout();
62+
return $this->redirect(['controller' => 'Users', 'action' => 'login']);
63+
}
5064
}
5165

5266
/**

‎src/Model/Entity/User.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
namespace App\Model\Entity;
33

4-
use Cake\Auth\DefaultPasswordHasher;
4+
use Authentication\PasswordHasher\DefaultPasswordHasher;
55
use Cake\ORM\Entity;
66

77
/**

‎src/Model/Table/ArticlesTable.php‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// src/Model/Table/ArticlesTable.php
33
namespace App\Model\Table;
44

5+
use Cake\Event\EventInterface;
56
use Cake\ORM\Table;
67
use Cake\ORM\Query;
78
use Cake\Utility\Text;
@@ -12,22 +13,20 @@ class ArticlesTable extends Table
1213
public function initialize(array $config): void
1314
{
1415
$this->addBehavior('Timestamp');
15-
$this->belongsToMany('Tags');
16+
$this->belongsToMany('Tags', [
17+
'joinTable' => 'articles_tags',
18+
'dependent' => true
19+
]);
1620
}
1721

18-
public function beforeSave(\Cake\Event\EventInterface $event, $entity, $options)
22+
public function beforeSave(EventInterface $event, $entity, $options)
1923
{
2024
if ($entity->isNew() && !$entity->slug) {
2125
$sluggedTitle = Text::slug($entity->title);
2226
// trim slug to maximum length defined in schema
2327
$entity->slug = substr($sluggedTitle, 0, 191);
2428
}
2529

26-
// This is temporary, and will be removed later
27-
// when we build authentication out.
28-
if (!$entity->user_id) {
29-
$entity->user_id = 1;
30-
}
3130

3231
if ($entity->tag_string) {
3332
$entity->tags = $this->_buildTags($entity->tag_string);
@@ -54,7 +53,6 @@ protected function _buildTags($tagString)
5453
unset($newTags[$index]);
5554
}
5655
}
57-
debug($newTags);
5856
// Add existing tags.
5957
foreach ($query as $tag) {
6058
$out[] = $tag;

‎templates/Articles/view.php‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22

33
<h1><?= h($article->title) ?></h1>
44
<p><?= h($article->body) ?></p>
5+
<p><b>Tags:</b> <?= h($article->tag_string) ?></p>
56
<p><small>Created: <?= $article->created->format(DATE_RFC850) ?></small></p>
6-
<p><?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?></p>
7+
<p><?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?></p>

0 commit comments

Comments
 (0)