├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── composer.json
├── composer.lock
├── phpunit.xml
├── provides.json
├── src
├── Efficiently
│ └── AuthorityController
│ │ ├── Authority.php
│ │ ├── AuthorityControllerServiceProvider.php
│ │ ├── ControllerAdditions.php
│ │ ├── ControllerResource.php
│ │ ├── Exceptions
│ │ └── AccessDenied.php
│ │ ├── Facades
│ │ ├── Authority.php
│ │ └── Params.php
│ │ ├── Parameters.php
│ │ ├── Rule.php
│ │ └── helpers.php
├── config
│ └── config.php
├── lang
│ ├── en
│ │ └── messages.php
│ └── fr
│ │ └── messages.php
├── migrations
│ ├── 2015_02_23_095033_create_roles_table.php
│ ├── 2015_02_23_095107_create_permissions_table.php
│ └── 2015_02_23_095152_create_role_user_table.php
└── translations
│ ├── en
│ └── messages.php
│ └── fr
│ └── messages.php
└── tests
├── AcAuthorityTest.php
├── AcControllerAdditionsTest.php
├── AcControllerResourceTest.php
├── AcExceptionsHandler.php
├── AcHelpersTest.php
├── AcParametersTest.php
├── AcProjectsControllerTest.php
├── AcTasksControllerTest.php
├── AcTestCase.php
├── fixtures
├── AcProduct.php
├── controllers
│ ├── AcBaseController.php
│ ├── AcCategoriesController.php
│ ├── AcProjectsController.php
│ └── AcTasksController.php
├── models
│ ├── AcCategory.php
│ ├── AcProject.php
│ ├── AcRole.php
│ ├── AcTask.php
│ └── AcUser.php
└── views
│ ├── ac_projects
│ ├── create.blade.php
│ ├── edit.blade.php
│ ├── index.blade.php
│ └── show.blade.php
│ └── ac_tasks
│ ├── create.blade.php
│ ├── edit.blade.php
│ ├── index.blade.php
│ └── show.blade.php
└── helpers
└── AuthorityControllerHelpers.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.phar
3 | .DS_Store
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.6
5 | - 7.0
6 | - hhvm
7 |
8 | matrix:
9 | allow_failures:
10 | - php: hhvm
11 |
12 | sudo: false
13 |
14 | before_install:
15 | - travis_retry composer self-update
16 |
17 | install:
18 | - travis_retry composer install --no-interaction --prefer-dist
19 |
20 | script: vendor/bin/phpunit tests
21 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Tortue Torche
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | AuthorityController [](http://travis-ci.org/efficiently/authority-controller)
2 | ===================
3 |
4 | AuthorityController is an PHP authorization library for [Laravel 5.3](http://laravel.com) which restricts what resources a given user is allowed to access.
5 |
6 | All permissions are defined in a single location:
7 |
8 | config/authority-controller.php
9 |
10 | and not duplicated across controllers, routes, views, and database queries.
11 |
12 | For [**Laravel 5.2**](http://laravel.com/docs/5.2) supports see [AuthorityController 2.2 branch](https://github.com/efficiently/authority-controller/tree/2.2)
13 |
14 | For [Laravel 5.0 or 5.1](http://laravel.com/docs/5.1) supports see [AuthorityController 2.1 branch](https://github.com/efficiently/authority-controller/tree/2.1)
15 |
16 | For [Laravel 4.1 or 4.2](http://laravel.com/docs/4.2) supports see [AuthorityController 1.2 branch](https://github.com/efficiently/authority-controller/tree/1.2)
17 |
18 | #### Demo application
19 |
20 | You can see in action this package with this Laravel 5.3 [**demo application**](https://github.com/efficiently/laravel_authority-controller_app#readme).
21 |
22 | #### Origins and Inspirations
23 |
24 | It's an extension of the [`authority-laravel`](https://github.com/authority-php/authority-laravel) package.
25 |
26 | And a port of the best [Ruby](https://ruby-lang.org) authorization library: [CanCan](https://github.com/ryanb/cancan).
27 |
28 | [Authority](https://github.com/authority-php/authority) ports some features of CanCan and this package ports [_almost_](https://github.com/efficiently/authority-controller/blob/master/README.md#missing-features) all the other features.
29 |
30 | Installation
31 | ---------------------------
32 |
33 | #### With [Composer](https://getcomposer.org/)
34 |
35 | 1. Add `authority-controller` package to your `composer.json` file to require AuthorityController:
36 |
37 | ```bash
38 | composer require efficiently/authority-controller:dev-master
39 | ```
40 |
41 | 2. Add the service provider to `config/app.php`:
42 |
43 | ```php
44 | Efficiently\AuthorityController\AuthorityControllerServiceProvider::class,
45 | ```
46 |
47 | 3. Add the aliases (facades) to your Laravel app config file:
48 |
49 | ```php
50 | 'Params' => Efficiently\AuthorityController\Facades\Params::class,
51 | 'Authority' => Efficiently\AuthorityController\Facades\Authority::class,
52 | ```
53 |
54 | 4. This will allow you to access the Authority class through the static interface you are used to with Laravel components.
55 |
56 | ```php
57 | Authority::can('update', SomeModel::class);
58 | ```
59 |
60 | Configuration
61 | -------------
62 | ##### Create Roles and Permissions Tables
63 |
64 | We have provided a basic table structure to get you started in creating your roles and permissions.
65 |
66 | Publish them to your migrations directory or copy them directly.
67 |
68 | ```bash
69 | php artisan vendor:publish --provider="Efficiently\AuthorityController\AuthorityControllerServiceProvider" --tag="migrations"
70 | ```
71 |
72 | Run the migrations
73 |
74 | ```bash
75 | php artisan migrate
76 | ```
77 |
78 | This will create the following tables
79 |
80 | - roles
81 | - role_user
82 | - permissions
83 |
84 | To utilize these tables, you can add the following methods to your `User` model. You will also need to create Role and Permission Model stubs (replacing `App\Authority\` with you own namespace)..
85 |
86 | ```php
87 | //app/User.php
88 | public function roles()
89 | {
90 | return $this->belongsToMany(Authority\Role::class)->withTimestamps();
91 | }
92 |
93 | public function permissions()
94 | {
95 | return $this->hasMany(Authority\Permission::class);
96 | }
97 |
98 | public function hasRole($key)
99 | {
100 | $hasRole = false;
101 | foreach ($this->roles as $role) {
102 | if ($role->name === $key) {
103 | $hasRole = true;
104 | break;
105 | }
106 | }
107 |
108 | return $hasRole;
109 | }
110 | ```
111 |
112 | ```php
113 | //app/Authority/Role.php
114 | user()` return the current authenticated user. Now, by default Laravel 5 handles [this](https://laravel.com/docs/5.3/authentication#retrieving-the-authenticated-user).
177 |
178 | ##### Defining Authority rules
179 |
180 | User permissions are defined in an AuthorityController configuration file.
181 |
182 | You can publish the AuthorityController default configuration file with the command below:
183 |
184 | ```bash
185 | php artisan vendor:publish --provider="Efficiently\AuthorityController\AuthorityControllerServiceProvider" --tag="config"
186 | ```
187 |
188 | This will place a copy of the configuration file at `config/authority-controller.php`. The config file includes an `initialize` function, which is a great place to setup your rules and aliases.
189 |
190 | ```php
191 | //config/authority-controller.php
192 | $serializer->serialize(function ($authority) {
197 | $user = auth()->guest() ? new App\User : $authority->getCurrentUser();
198 |
199 | // Action aliases. For example:
200 | $authority->addAlias('moderate', ['read', 'update', 'delete']);
201 |
202 | // Define abilities for the passed in user here. For example:
203 | if ($user->hasRole('admin')) {
204 | $authority->allow('manage', 'all');
205 | } else {
206 | $authority->allow('read', 'all');
207 | }
208 | })
209 | ];
210 | ```
211 |
212 | See [Defining Authority rules](https://github.com/efficiently/authority-controller/wiki/Defining-Authority-rules) for details.
213 |
214 | ##### Check Authority rules & Authorization
215 |
216 | The current user's permissions can then be checked using the `Authority::can()` and `Authority::cannot()` methods in the view and controller.
217 |
218 | ```
219 | @if (Authority::can('update', $article))
220 | {{ link_to_route("articles.edit", "Edit", $article->id) }}
221 | @endif
222 | ```
223 |
224 | See [Checking Authority rules](https://github.com/efficiently/authority-controller/wiki/Checking-Authority-rules) for more information
225 |
226 | The `authorize()` method in the controller will throw an exception if the user is not able to perform the given action.
227 |
228 | ```php
229 | public function show($id)
230 | {
231 | $this->article = App\Article::find($id);
232 | $this->authorize('read', $this->article);
233 | }
234 | ```
235 |
236 | Setting this for every action can be tedious, therefore the `loadAndAuthorizeResource()` method is provided to automatically authorize all actions in a RESTful style resource controller. It will use a before filter to load the resource into an instance variable and authorize it for every action.
237 |
238 | ```php
239 | loadAndAuthorizeResource();
249 | }
250 |
251 | public function show($id)
252 | {
253 | // $this->article is already loaded and authorized
254 | }
255 | }
256 | ```
257 |
258 | See [Authorizing Controller Actions](https://github.com/efficiently/authority-controller/wiki/authorizing-controller-actions) for more information.
259 |
260 | ##### Exception Handling
261 |
262 | The `Efficiently\AuthorityController\Exceptions\AccessDenied` exception is thrown when calling `authorize()` in the controller and the user is not able to perform the given action. A message can optionally be provided.
263 |
264 | ```php
265 | Authority::authorize('read', 'App\Product', 'Unable to read this product.');
266 | ```
267 |
268 | You can catch the exception and modify its behavior in the `render()` method of the `app/Exceptions/Handler.php` file. For example here we set the error message to a flash and redirect to the home page.
269 |
270 | ```php
271 | //app/Exceptions/Handler.php
272 |
273 | /**
274 | * Render an exception into an HTTP response.
275 | *
276 | * @param \Illuminate\Http\Request $request
277 | * @param \Exception $e
278 | * @return \Illuminate\Http\Response
279 | */
280 | public function render($request, Exception $e)
281 | {
282 | //code...
283 | if ($e instanceof \Efficiently\AuthorityController\Exceptions\AccessDenied) {
284 | $msg = $e->getMessage();
285 | \Log::error('Access denied! '.$msg);
286 |
287 | return redirect('/home')->with('flash_alert', $msg);
288 | }
289 |
290 | return parent::render($request, $e);
291 | }
292 |
293 | //code...
294 | ```
295 |
296 | See [Exception Handling](https://github.com/efficiently/authority-controller/wiki/Exception-Handling) for more information.
297 |
298 | Documentations
299 | --------------
300 | ##### Wiki Docs
301 |
302 | * [Defining Authority rules](https://github.com/efficiently/authority-controller/wiki/Defining-Authority-rules)
303 | * [Checking Authority rules](https://github.com/efficiently/authority-controller/wiki/Checking-Authority-rules)
304 | * [Authorizing Controller Actions](https://github.com/efficiently/authority-controller/wiki/Authorizing-Controller-Actions)
305 | * [Exception Handling](https://github.com/efficiently/authority-controller/wiki/Exception-Handling)
306 | * [See more](https://github.com/efficiently/authority-controller/wiki)
307 |
308 | ##### Authority Docs
309 |
310 | Authority [introduction](https://github.com/authority-php/authority/blob/2.2.2/readme.md#introduction).
311 |
312 | Authority-Laravel [general usage](https://github.com/authority-php/authority-laravel/blob/2.4.3/README.md#general-usage).
313 |
314 | ##### CanCan Wiki Docs
315 |
316 | Because AuthorityController is a CanCan port, you can also read the Wiki docs of CanCan [here](https://github.com/ryanb/cancan/wiki).
317 |
318 | Controller additions
319 | --------------------
320 | Your controllers have now a `$params` property:
321 |
322 | ```php
323 | params['id'] == $id;//-> true
334 | $this->params['product'];//-> ["name" => "Best movie"]
335 | $this->params['controller'];//-> 'products'
336 | $this->params['action'];//-> 'update'
337 | //code...
338 | }
339 |
340 | //code...
341 | }
342 | ```
343 |
344 | Changelog
345 | ---------
346 | #### 2.3.0-dev
347 | * Laravel 5.3 support!
348 |
349 | #### 2.2.0
350 | * Laravel 5.2 support!
351 |
352 | #### 2.1.1
353 | * Update installation instructions for Laravel >= 5.1.11
354 |
355 | #### 2.1.0
356 | * Laravel 5.1 support!
357 |
358 | #### 2.0.1
359 | * Replace the deprecated package [`illuminate/html`](https://github.com/illuminate/html) package by the [`laravelcollective/html`](https://github.com/LaravelCollective/html) package
360 | * Autoloading migrations class is useless, see issue [#30](https://github.com/efficiently/authority-controller/issues/30) (reported by @Fnatte)
361 | * Autoloading class from `tests` directory are now only available in Composer's dev mode to avoid conflicts
362 |
363 | #### 2.0.0
364 | * Laravel 5.0 support!
365 | * Use your Laravel Aliases to resolve your models namespace name.
366 | * Or auto guessing them, e.g. `User` => `App\User`
367 | * Add a new config option `controllerClass` which is by default `Illuminate\Routing\Controller`
368 | * Support Route Model Binding in the Parameters class.
369 | See: http://laravel.com/docs/5.0/routing#route-model-binding and issue [#21](https://github.com/efficiently/authority-controller/issues/21)
370 | * Use [authority-laravel](https://github.com/authority-php/authority-laravel) package instead of [authority-l4](https://github.com/machuga/authority-l4).
371 | * Upgrade Notes (if you used previously this package with Laravel 4):
372 | * Move your `authory-controller` config file from `app/config/packages/efficiently/authority-controller/config.php` to `config/authority-controller.php`
373 | * Publish the `authory-controller` migrations files (see the section [Create Roles and Permissions Tables](https://github.com/efficiently/authority-controller/blob/2.0/README.md#create-roles-and-permissions-tables) of this README)
374 |
375 | #### 1.2.4
376 | * Add `BaseController::flushAuthorityEvents()` static method.
377 | Useful for functional tests with Codeception (see issue [#14](https://github.com/efficiently/authority-controller/issues/14) and [this Wiki page](https://github.com/efficiently/authority-controller/wiki/Testing-Authority-rules#functional-tests-with-codeception) for more explanations).
378 | * Fix User::hasRoles() method to avoid duplicate roles.
379 |
380 | #### 1.2.3
381 | * Follow [PSR-2](http://www.php-fig.org) coding style
382 |
383 | #### 1.2.2
384 | * Run tests with Laravel 4.2
385 |
386 | #### 1.2.1
387 | * Fix `composer.json` file.
388 |
389 | #### 1.2.0
390 | * Security fix: conditional callback was never evaluated when an actual instance object was present.
391 | * Non backwards compatible: Deny rules override prior rules and Allow rules don't override prior rules but instead are logically or'ed (fix [#5](https://github.com/efficiently/authority-controller/issues/5)).
392 | Match more CanCan default behavior unlike `authority-php\authority` package.
393 | Read the Wiki doc for more information: [Authority-Precedence](https://github.com/efficiently/authority-controller/wiki/Authority-Precedence).
394 | * Support PHP 5.4, 5.5, 5.6 and HipHop Virtual Machine (hhvm).
395 | * Update [`Parameters`](https://github.com/efficiently/authority-controller/blob/18c2ad7788385da4e0309708772ea40cc8be0f53/src/Efficiently/AuthorityController/Parameters.php#L46) class to allow custom routes with `id` and `parent_id` routes's parameters (fix [#6](https://github.com/efficiently/authority-controller/issues/6)).
396 |
397 | #### 1.1.3
398 | * Upgrade Authority-L4 package to fix Laravel 4.1 support.
399 |
400 | #### 1.1.2
401 | * Tweak the mock system who simulates Eloquent's constructor method.
402 |
403 | #### 1.1.1
404 | * Less intrusive parameters injection in the controllers
405 | * Check if the current resolved controller responds to paramsBeforeFilter method. Otherwise the application crash.
406 | * Use the Controller alias of the current Laravel application instead of a hardcoded class name.
407 |
408 | #### 1.1.0
409 | * First beta release for Laravel **4.1** compatibility.
410 | * Non backwards compatible with Laravel **4.0**.
411 |
412 | #### 1.0.0
413 | * First stable release, only compatible with Laravel **4.0**.
414 | * For Laravel **4.1** supports, see [AuthorityController 1.1 branch](https://github.com/efficiently/authority-controller/tree/1.1).
415 | * Fix AccessDenied class, the exception message didn't fallback to the default message if it was empty.
416 |
417 | #### 0.10.0
418 | * Non backwards compatible: `Params::get('controller')` behaviour is now like Rails. It returns controller name in snake_case and in plural.
419 |
420 | #### 0.9.0
421 | * First beta release
422 |
423 | Missing features
424 | ----------------
425 | 1. In `ControllerResource` class, the [`#load_collection`](https://github.com/ryanb/cancan/blob/1.6.10/lib/cancan/controller_resource.rb#L80) method, who uses in the `User` model [`#accessible_by`](https://github.com/ryanb/cancan/blob/1.6.10/lib/cancan/model_additions.rb#L22) method. Looks complicated.
426 | Instead, use specific query scopes with `collectionScope` option to filtering your data in your collection (e.g. `index`) controller actions.
427 | Because you'll allowing/denying access by roles or check user's authorizations on each record of the collection.
428 | 2. In `Ability` class, the [`#attributes_for`](https://github.com/ryanb/cancan/blob/1.6.10/lib/cancan/ability.rb#L221) method.
429 | Looks useless with `Authority` because rules conditions are only possible by `Closure` not by associative array. And CanCan handles `#attribute_for` only for `Hash` (associative array) conditions.
430 | 3. `#skip_*` methods in `ControllerAdditions`.
431 | 4. For `allow()` and `deny()` methods of `Authority`, the third argument isn't an optional hash (associative array) of conditions but an anonymous function (Closure):
432 |
433 | ```php
434 | $authority->allow('update', 'App\Product', function ($self, $product) {
435 | return $product->available === true;
436 | });
437 | ```
438 |
439 | Good to know
440 | ------------
441 | #### Compatibility
442 | It's **only** compatible with **PHP >= 5.6** and **Laravel >= 5.3** framework.
443 |
444 | #### Differences between CanCan and AuthorityController
445 | See Wiki page [Differences between CanCan and AuthorityController](https://github.com/efficiently/authority-controller/wiki/Differences-between-CanCan-and-AuthorityController)
446 |
447 | #### Philosophy
448 | It's following the D.R.W.J.P.I. principle:
449 |
450 | > Don't Reinvent the Wheel, Just Port It !
451 | > -- (c) 2013 A.D.
452 |
453 | Questions or Problems?
454 | ----------------------
455 | If you have any issues with AuthorityController, please add an [issue on GitHub](https://github.com/efficiently/authority-controller/issues) or fork the project and send a pull request.
456 |
457 | To get the tests running you should install PHPUnit and run `phpunit tests`.
458 |
459 |
460 | Special Thanks
461 | --------------
462 | AuthorityController was _heavily_ inspired by [CanCan](https://github.com/ryanb/cancan) and uses [Authority-Laravel](https://github.com/authority-php/authority-laravel).
463 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "efficiently/authority-controller",
3 | "description": "AuthorityController is an PHP authorization library for Laravel 5 which restricts what resources a given user is allowed to access.",
4 | "keywords": [
5 | "cancan",
6 | "can",
7 | "authority",
8 | "acl",
9 | "role",
10 | "permission",
11 | "ability",
12 | "allow",
13 | "deny",
14 | "rbac",
15 | "authorization",
16 | "authentication",
17 | "security",
18 | "laravel",
19 | "laravel 5",
20 | "laravel 5.1",
21 | "laravel 5.2",
22 | "laravel 5.3",
23 | "rails"
24 | ],
25 | "license": "MIT",
26 | "authors": [{
27 | "name": "TortueTorche",
28 | "email": "tortuetorche@spam.me"
29 | }],
30 | "require": {
31 | "php": ">=5.6.4",
32 | "authority-php/authority-laravel": "~2.4.3"
33 | },
34 | "require-dev": {
35 | "phpunit/phpunit": "~4.5",
36 | "mockery/mockery": "0.9.*",
37 | "orchestra/testbench": "~3.3.0",
38 | "laravelcollective/html": "5.3.*"
39 | },
40 | "autoload": {
41 | "files": [
42 | "src/Efficiently/AuthorityController/helpers.php"
43 | ],
44 | "psr-0": {
45 | "Efficiently\\AuthorityController": "src/"
46 | }
47 | },
48 | "autoload-dev": {
49 | "classmap": [
50 | "tests/AcExceptionsHandler.php", "tests/AcTestCase.php", "tests/helpers", "tests/fixtures/",
51 | "tests/fixtures/models", "tests/fixtures/controllers"
52 | ]
53 | },
54 | "extra": {
55 | "branch-alias": {
56 | "dev-1.0": "1.0.x-dev",
57 | "dev-1.1": "1.1.x-dev",
58 | "dev-1.2": "1.2.x-dev",
59 | "dev-2.0": "2.0.x-dev",
60 | "dev-2.1": "2.1.x-dev",
61 | "dev-2.2": "2.2.x-dev",
62 | "dev-master": "2.3.x-dev"
63 | }
64 | },
65 | "config": {
66 | "preferred-install": "dist"
67 | },
68 | "prefer-stable": true,
69 | "minimum-stability": "dev"
70 | }
71 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 | create
' and 'store
' actions will set
378 | * the initial attributes based on these conditions. This way these actions will satisfy
379 | * the authority restrictions.
380 | *
381 | * Call this method directly on the controller class.
382 | *
383 | * class BooksController extends Controller
384 | * {
385 | * public function __construct()
386 | * {
387 | * $this->loadAndAuthorizeResource();
388 | * }
389 | * }
390 | *
391 | * A resource is not loaded if the instance variable is already set. This makes it easy to override
392 | * the behavior through a beforeFilter() on certain actions.
393 | *
394 | * class BooksController extends Controller
395 | * {
396 | * public function __construct()
397 | * {
398 | * $this->beforeFilter('findBookByPermalink', ['only' => 'show']);
399 | * $this->loadAndAuthorizeResource();
400 | * }
401 | *
402 | * protected function findBookByPermalink()
403 | * {
404 | * $this->book = Book::where('permalink', $this->params['id'])->firstOrFail();
405 | * }
406 | * }
407 | *
408 | * If a name is provided which does not match the controller it assumes it is a parent resource. Child
409 | * resources can then be loaded through it.
410 | *
411 | * class BooksController extends Controller
412 | * {
413 | * public function __construct()
414 | * {
415 | * $this->loadResource('author');
416 | * $this->loadResource('book', ['through' => 'author']);
417 | * }
418 | * }
419 | *
420 | * Here the author resource will be loaded before each action using $this->params['author_id']. The book resource
421 | * will then be loaded through the $this->author instance variable.
422 | *
423 | * That first argument is optional and will default to the singular name of the controller.
424 | * A hash of options (see below) can also be passed to this method to further customize it.
425 | *
426 | * See loadAndAuthorizeResource() to automatically authorize the resource too.
427 | *
428 | * Options:
429 | * ['only
']
430 | * Only applies before filter to given actions.
431 | *
432 | * ['except
']
433 | * Does not apply before filter to given actions.
434 | *
435 | * ['through
']
436 | * Load this resource through another one. This should match the name of the parent instance variable or method.
437 | *
438 | * ['throughAssociation
']
439 | * The name of the association to fetch the child records through the parent resource.
440 | * This is normally not needed because it defaults to the pluralized resource name.
441 | *
442 | * ['shallow
']
443 | * Pass true
to allow this resource to be loaded directly when parent is null
.
444 | * Defaults to false
.
445 | *
446 | * ['singleton
']
447 | * Pass true
if this is a singleton resource through a hasOne
association.
448 | *
449 | * ['parent
']
450 | * True or false depending on if the resource is considered a parent resource.
451 | * This defaults to true
if a resource
452 | * name is given which does not match the controller.
453 | *
454 | * ['class
']
455 | * The class to use for the model (string).
456 | *
457 | * ['instanceName
']
458 | * The name of the instance variable to load the resource into.
459 | *
460 | * ['findBy
']
461 | * Find using a different attribute other than id. For example.
462 | *
463 | * $this->loadResource(['findBy' => 'permalink']);
464 | * // will use where('permalink', $this->params['id'])->firstOrFail()
465 | *
466 | * ['idParam
']
467 | * Find using a param key other than 'id'. For example:
468 | *
469 | * $this->loadResource(['idParam' => 'url']); // will use find($this->params['url'])
470 | *
471 | * ['collection
']
472 | * Specify which actions are resource collection actions in addition to index
. This
473 | * is usually not necessary because it will try to guess depending on if the id param is present.
474 | *
475 | * $this->loadResource(['collection' => ['sort', 'list']]);
476 | *
477 | * ['create
']
478 | * Specify which actions are new resource actions in addition to new
, create
479 | * and store
.
480 | * Pass an action name into here if you would like to build a new resource instead of
481 | * fetch one.
482 | *
483 | * $this->loadResource(['create' => 'build']);
484 | *
485 | * ['collectionScope
']
486 | * The name of the query scope to fetch the collection records of collection actions (E.g. index
action).
487 | *
488 | * $this->loadResource(['collectionScope' => 'scopePopular']); // will use Article::popular()->get(); to fetch records of collection actions
489 | *
490 | * You can pass parameters with an array. For example:
491 | *
492 | * $this->loadResource(['collectionScope' => ['scopeOfType', 'published']]); // will use Article::ofType('published')->get();
493 | *
494 | * By default, collection actions (index
action) returns all the collection record with:
495 | *
496 | * Article::get(); // which is equivalent to Article::get();
497 | *
498 | * ['prepend
']
499 | * Passing true
will use prependBeforeFilter() instead of a normal beforeFilter().
500 | *
501 | */
502 | public function loadResource($args = null)
503 | {
504 | $args = is_array($args) ? $args : func_get_args();
505 | ControllerResource::addBeforeFilter($this, __METHOD__, $args);
506 | }
507 |
508 | /**
509 | * Sets up a before filter which authorizes the resource using the instance variable.
510 | * For example, if you have an ArticlesController it will check the $this->article instance variable
511 | * and ensure the user can perform the current action on it. Under the hood it is doing
512 | * something like the following.
513 | *
514 | * $this->authorize($this->params['action'], $this->article ?: 'Article')
515 | *
516 | * Call this method directly on the controller class.
517 | *
518 | * class BooksController extends Controller
519 | * {
520 | * public function __construct()
521 | * {
522 | * $this->authorizeResource();
523 | * }
524 | * }
525 | *
526 | * If you pass in the name of a resource which does not match the controller it will assume
527 | * it is a parent resource.
528 | *
529 | * class BooksController extends Controller
530 | * {
531 | * public function __construct()
532 | * {
533 | * $this->authorizeResource('author');
534 | * $this->authorizeResource('book');
535 | * }
536 | * }
537 | *
538 | * Here it will authorize 'show
', $this->author
on every action before authorizing the book.
539 | *
540 | * That first argument is optional and will default to the singular name of the controller.
541 | * A hash of options (see below) can also be passed to this method to further customize it.
542 | *
543 | * See loadAndAuthorizeResource() to automatically load the resource too.
544 | *
545 | * Options:
546 | * ['only
']
547 | * Only applies before filter to given actions.
548 | *
549 | * ['except
']
550 | * Does not apply before filter to given actions.
551 | *
552 | * ['singleton
']
553 | * Pass true
if this is a singleton resource through a hasOne
association.
554 | *
555 | * ['parent
']
556 | * True or false depending on if the resource is considered a parent resource. This defaults to true
if a resource
557 | * name is given which does not match the controller.
558 | *
559 | * ['class
']
560 | * The class to use for the model (string). This passed in when the instance variable is not set.
561 | * Pass false
if there is no associated class for this resource and it will use a symbol of the resource name.
562 | *
563 | * ['instance_name
']
564 | * The name of the instance variable for this resource.
565 | *
566 | * ['through
']
567 | * Authorize conditions on this parent resource when instance isn't available.
568 | *
569 | * ['prepend
']
570 | * Passing true
will use prependBeforeFilter() instead of a normal beforeFilter().
571 | *
572 | */
573 | public function authorizeResource($args = null)
574 | {
575 | $args = is_array($args) ? $args : func_get_args();
576 | ControllerResource::addBeforeFilter($this, __METHOD__, $args);
577 | }
578 |
579 | // TODO: Add checkAuthorization() ?
580 | // More infos at: https://github.com/ryanb/cancan/blob/master/lib/cancan/controller_additions.rb#L256
581 |
582 | /**
583 | * Throws a Efficiently\AuthorityController\Exceptions\AccessDenied exception if the currentAuthority cannot
584 | * perform the given action. This is usually called in a controller action or
585 | * before filter to perform the authorization.
586 | *
587 | * public function show($id)
588 | * {
589 | * $this->article = Article::find($id); // Tips: instead of $id, you can use $this->params['id']
590 | * $this->authorize('read', $this->article);
591 | *
592 | * // But you still need to return the view
593 | * // return view('articles.show', compact_property($this, 'article'));
594 | * }
595 | *
596 | * A 'message' option can be passed to specify a different message.
597 | *
598 | * $this->authorize('read', $this->article, ['message' => "Not authorized to read ".$this->article->name]);
599 | *
600 | * You can also use I18n to customize the message. Action aliases defined in Authority work here.
601 | *
602 | * return [
603 | * 'unauthorized' => [
604 | * 'manage' => [
605 | * 'all' => "Not authorized to :action :subject.",
606 | * 'user' => "Not allowed to manage other user accounts.",
607 | * ],
608 | * 'update' => [
609 | * 'project' => "Not allowed to update this project."
610 | * ],
611 | * ],
612 | * ];
613 | *
614 | * You can catch the exception and modify its behavior in the report() method of the app/Exceptions/Handler.php file.
615 | * For example here we set the error message to a flash and redirect to the home page.
616 | *
617 | * public function report(Exception $e)
618 | * {
619 | * if ($e instanceof \Efficiently\AuthorityController\Exceptions\AccessDenied) {
620 | * $msg = $e->getMessage();
621 | * \Log::error('Access denied! '.$msg);
622 | *
623 | * return redirect()->route('home')->with('flash_alert', $msg);
624 | * }
625 | *
626 | * return parent::report($e);
627 | * }
628 | *
629 | * //code...
630 | *
631 | * See the Efficiently\AuthorityController\Exceptions\AccessDenied exception for more details on working with the exception.
632 | *
633 | * See the loadAndAuthorizeResource() method to automatically add the authorize() behavior
634 | * to the default RESTful actions.
635 | *
636 | */
637 | public function authorize($args = null)
638 | {
639 | $args = is_array($args) ? $args : func_get_args();
640 | $this->_authorized = true;
641 | return call_user_func_array([$this->getCurrentAuthority(), 'authorize'], $args);
642 | }
643 |
644 | public function setCurrentAuthority($authority)
645 | {
646 | $this->currentAuthority = $authority;
647 | }
648 |
649 | // alias of setCurrentAuthority() to match CanCan API
650 | public function setCurrentAbility($ability)
651 | {
652 | $this->setCurrentAuthority($ability);
653 | }
654 |
655 | /**
656 | * Creates and returns the current user's authority and caches it. If you
657 | * want to override how the Authority is defined then this is the place.
658 | * Just define the method in the controller to change behavior.
659 | *
660 | * public function getCurrentAuthority()
661 | * {
662 | * // instead of app('authority');
663 | * $this->currentAuthority = $this->currentAuthority ?: app('UserAuthority', [$this->getCurrentAccount()]);
664 | *
665 | * return $this->currentAuthority;
666 | * }
667 | *
668 | * Notice it is important to cache the authority object so it is not
669 | * recreated every time.
670 | *
671 | */
672 | public function getCurrentAuthority()
673 | {
674 | if (is_null($this->currentAuthority)) {
675 | $this->currentAuthority = app('authority');
676 | }
677 |
678 | return $this->currentAuthority;
679 | }
680 |
681 | // alias of getCurrentAuthority() to match CanCan API
682 | public function getCurrentAbility()
683 | {
684 | return $this->getCurrentAuthority();
685 | }
686 |
687 | public function getCurrentUser()
688 | {
689 | if (is_null($this->currentUser)) {
690 | $this->currentUser = $this->getCurrentAuthority()->getCurrentUser();
691 | }
692 |
693 | return $this->currentUser;
694 | }
695 |
696 | /**
697 | * Use in the controller or view to check the user's permission for a given action
698 | * and object.
699 | *
700 | * $this->can('destroy', $this->project);
701 | *
702 | * You can also pass the class instead of an instance (if you don't have one handy).
703 | *
704 | * @if (Authority::can('create', 'Project'))
705 | * {{ link_to_route('projects.create', "New Project") }}
706 | * @endif
707 | *
708 | * If it's a nested resource, you can pass the parent instance in an associative array. This way it will
709 | * check conditions which reach through that association.
710 | *
711 | * @if (Authority::can('create', ['Project' => $category]))
712 | * {{ link_to_route('categories.projects.create', "New Project") }}
713 | * @endif
714 | *
715 | * This simply calls "can()" on the $this->currentAuthority
. See Authority::can().
716 | *
717 | */
718 | public function can($args = null)
719 | {
720 | $args = is_array($args) ? $args : func_get_args();
721 | return call_user_func_array([$this->getCurrentAuthority(), 'can'], $args);
722 | }
723 |
724 | /**
725 | * Convenience method which works the same as "can()" but returns the opposite value.
726 | *
727 | * $this->cannot('destroy', $this->project);
728 | *
729 | */
730 | public function cannot($args = null)
731 | {
732 | $args = is_array($args) ? $args : func_get_args();
733 | return call_user_func_array([$this->getCurrentAuthority(), 'cannot'], $args);
734 | }
735 |
736 | // setParams() should be forbidden for security reasons ?
737 | // public function setParams($params = [])
738 | // {
739 | // $this->params = $params;
740 | // }
741 |
742 | public function getParams()
743 | {
744 | return (array) $this->params;
745 | }
746 | }
747 |
--------------------------------------------------------------------------------
/src/Efficiently/AuthorityController/ControllerResource.php:
--------------------------------------------------------------------------------
1 | $method();
49 | });
50 |
51 | call_user_func_array([$controller, $beforeFilterMethod], [$filterName, array_only($options, ['only', 'except'])]);
52 | }
53 | }
54 |
55 | public function __construct($controller, $name = null, $options = [])
56 | {
57 | $args = array_slice(func_get_args(), 1);
58 | $name = array_key_exists(0, $args) && is_string($args[0]) ? array_shift($args) : null;
59 |
60 | $lastArg = last($args);
61 | if (is_array($lastArg)) {
62 | $args = array_merge($args, array_extract_options($lastArg));
63 | }
64 | $options = $options ?: array_extract_options($args);
65 |
66 | $this->controller = $controller;
67 | $this->params = $controller->getParams();
68 | $this->name = $name;
69 | $this->options = $options;
70 | }
71 |
72 | public function loadAndAuthorizeResource()
73 | {
74 | $this->loadResource();
75 | $this->authorizeResource();
76 | }
77 |
78 | public function loadResource()
79 | {
80 | if ($this->loadedInstance()) {
81 | if (! $this->getResourceInstance()) {
82 | $this->setResourceInstance($this->loadResourceInstance());
83 | }
84 | } elseif ($this->loadedCollection()) {
85 | // Load resources of collection actions (E.g. 'index') here. Even if we don't support $instance->accessibleBy() (see: https://github.com/ryanb/cancan/blob/f2f40c7aac4a00a88651641129eaad71916c1c82/lib/cancan/model_additions.rb#L22)
86 | if (! $this->getCollectionInstance()) {
87 | $this->setCollectionInstance($this->loadCollection());
88 | }
89 | }
90 | }
91 |
92 | public function authorizeResource()
93 | {
94 | $resource = $this->getResourceInstance() ?: $this->getResourceClassWithParent();
95 | $this->controller->authorize($this->getAuthorizationAction(), $resource);
96 | }
97 |
98 | public function isParent()
99 | {
100 | return array_key_exists('parent', $this->options) ? $this->options['parent'] : ($this->name && $this->name !== $this->getNameFromController());
101 | }
102 |
103 | protected function loadResourceInstance()
104 | {
105 | if (! $this->isParent() && in_array($this->params['action'], $this->getCreateActions())) {
106 | return $this->buildResource();
107 | } elseif ($this->getIdParam() || array_key_exists('singleton', $this->options)) {
108 | return $this->findResource();
109 | }
110 | }
111 |
112 | protected function loadedInstance()
113 | {
114 | return $this->isParent() || $this->isMemberAction();
115 | }
116 |
117 | protected function loadedCollection()
118 | {
119 | return ! $this->getCurrentAuthority()->hasCondition($this->getAuthorizationAction(), $this->getResourceClass());
120 | }
121 |
122 | protected function loadCollection()
123 | {
124 | $resourceModel = app($this->getResourceBase());
125 | $collectionScope = $this->getCollectionScopeWithParams();
126 | $collection = $resourceModel;
127 | if ($collectionScope) {
128 | list($collectionScope, $collectionScopeParams) = $collectionScope;
129 | $collectionScope = camel_case(str_replace('scope', '', $collectionScope));
130 | $collection = call_user_func_array([$collection, $collectionScope], $collectionScopeParams);
131 | }
132 |
133 | return $collection->get();
134 | }
135 |
136 | protected function buildResource()
137 | {
138 | $resourceBase = $this->getResourceBase();
139 | $resourceParams = $this->getResourceParams();
140 |
141 | $resource = app($resourceBase, is_array($resourceParams) ? [$resourceParams] : []);
142 |
143 | return $this->setAttributes($resource);
144 | }
145 |
146 | protected function setAttributes($resource)
147 | {
148 | if (array_key_exists('singleton', $this->options) && $this->getParentResource()) {
149 | $resource->{camel_case($this->getParentName())}()->associate($this->getParentResource());
150 | }
151 | // TODO: ?Implements initial attributes feature?
152 | // See: https://github.com/ryanb/cancan/blob/1.6.10/lib/cancan/controller_resource.rb#L91
153 |
154 | return $resource;
155 | }
156 |
157 | protected function findResource()
158 | {
159 | $resource = null;
160 | if (array_key_exists('singleton', $this->options) && respond_to($this->getParentResource(), $this->getName())) {
161 | $resource = call_user_func([$this->getParentResource(), $this->getName()]);
162 | } else {
163 | $resourceModel = app($this->getResourceBase());
164 | if (array_key_exists('findBy', $this->options)) {
165 | if (respond_to($resourceModel, "findBy".studly_case($this->options['findBy']))) {
166 | $resource = call_user_func_array([$resourceModel, "findBy".studly_case($this->options['findBy']) ], [$this->getIdParam()]);
167 | } elseif (respond_to($resourceModel, camel_case($this->options['findBy']))) {
168 | $resource = call_user_func_array([$resourceModel, camel_case($this->options['findBy']) ], [$this->getIdParam()]);
169 | } else {
170 | $resource = $resourceModel->where($this->getResourcePrimaryKey(), $this->getIdParam())->firstOrFail();
171 | }
172 | } else {
173 | $resource = $resourceModel->where($this->getResourcePrimaryKey(), $this->getIdParam())->firstOrFail();
174 | }
175 | }
176 |
177 | if (! is_null($resource)) {
178 | return $resource;
179 | }
180 | throw new \Illuminate\Database\Eloquent\ModelNotFoundException;
181 | }
182 |
183 | protected function getAuthorizationAction()
184 | {
185 | return $this->isParent() ? "show" : $this->params['action'];
186 | }
187 |
188 | protected function getResourcePrimaryKey()
189 | {
190 | return (! array_key_exists('idParam', $this->options) && $this->isParent() && $this->getIdKey() === $this->getName()."_id") ? "id" : $this->getIdKey();
191 | }
192 |
193 | protected function getIdKey()
194 | {
195 | if (array_key_exists('idParam', $this->options)) {
196 | return $this->options['idParam'];
197 | } else {
198 | return $this->isParent() ? $this->getName()."_id" : "id";
199 | }
200 | }
201 |
202 | protected function getIdParam()
203 | {
204 | return array_key_exists($this->getIdKey(), $this->params) ? print_r($this->params[$this->getIdKey()], true) : "";
205 | }
206 |
207 | protected function isMemberAction()
208 | {
209 | return in_array($this->params['action'], $this->getCreateActions()) || array_key_exists('singleton', $this->options) || ($this->getIdParam() && ! in_array($this->params['action'], $this->getCollectionActions()));
210 | }
211 |
212 | // Returns the class name used for this resource. This can be overriden by the 'class' option.
213 | // If false is passed in it will use the resource name as a lowercase string in which case it should
214 | // only be used for authorization, not loading since there's no class to load through.
215 | protected function getResourceClass()
216 | {
217 | if (array_key_exists('class', $this->options)) {
218 | if ($this->options['class'] === false) {
219 | return $this->getName();
220 | } elseif (is_string($this->options['class'])) {
221 | return studly_case($this->options['class']);
222 | }
223 | }
224 |
225 | return studly_case($this->getNamespacedName());
226 | }
227 |
228 | protected function getResourceClassWithParent()
229 | {
230 | // NOTICE: Against CanCan, we reverse the key and value, because in PHP an array key can't be an Object.
231 | return $this->getParentResource() ? [$this->getResourceClass() => $this->getParentResource()] : $this->getResourceClass();
232 | }
233 |
234 | protected function setResourceInstance($instance)
235 | {
236 | $instanceName = $this->getInstanceName();
237 | if (property_exists($this->controller, $instanceName)) {
238 | set_property($this->controller, $instanceName, $instance);
239 | } else {
240 | $this->controller->$instanceName = $instance;
241 | }
242 | }
243 |
244 | protected function getResourceInstance()
245 | {
246 | if ($this->loadedInstance()) {
247 | $instanceName = $this->getInstanceName();
248 | if (property_exists($this->controller, $instanceName)) {
249 | return get_property($this->controller, $instanceName);
250 | }
251 | }
252 | }
253 |
254 | protected function setCollectionInstance($instance)
255 | {
256 | $instanceName = str_plural($this->getInstanceName());
257 | if (property_exists($this->controller, $instanceName)) {
258 | set_property($this->controller, $instanceName, $instance);
259 | } else {
260 | $this->controller->$instanceName = $instance;
261 | }
262 | }
263 |
264 | protected function getCollectionInstance()
265 | {
266 | if ($this->loadedInstance()) {
267 | $instanceName = str_plural($this->getInstanceName());
268 | if (property_exists($this->controller, $instanceName)) {
269 | return get_property($this->controller, $instanceName);
270 | }
271 | }
272 | }
273 |
274 | /**
275 | * The object that methods (such as "find", "new" or "build") are called on.
276 | * If the 'through' option is passed it will go through an association on that instance.
277 | * If the 'shallow' option is passed it will use the getResourceClass() method if there's no parent
278 | * If the 'singleton' option is passed it won't use the association because it needs to be handled later.
279 | */
280 | protected function getResourceBase()
281 | {
282 | if (array_key_exists('through', $this->options)) {
283 | if ($this->getParentResource()) {
284 | if (array_key_exists('singleton', $this->options)) {
285 | return $this->getResourceClass();
286 | } elseif (array_key_exists('throughAssociation', $this->options)) {
287 | $associationName = $this->options['throughAssociation'];
288 | return get_classname($this->getParentResource()->$associationName()->getModel());
289 | } else {
290 | $associationName = str_plural(camel_case($this->getName()));
291 | return get_classname($this->getParentResource()->$associationName()->getModel());
292 | }
293 | } elseif (array_key_exists('shallow', $this->options)) {
294 | return $this->getResourceClass();
295 | } else {
296 | // Maybe this should be a record not found error instead?
297 | throw new Exceptions\AccessDenied(null, $this->getAuthorizationAction(), $this->getResourceClass());
298 | }
299 | } else {
300 | return $this->getResourceClass();
301 | }
302 | }
303 |
304 | protected function getParentName()
305 | {
306 | if (array_key_exists('through', $this->options)) {
307 | return array_first(array_flatten((array) $this->options['through']), function ($value) {
308 | return $this->fetchParent($value);
309 | });
310 | }
311 | }
312 |
313 | // The object to load this resource through.
314 | protected function getParentResource()
315 | {
316 | if ($this->getParentName()) {
317 | return $this->fetchParent($this->getParentName());
318 | }
319 | }
320 |
321 | protected function fetchParent($name)
322 | {
323 | $name = camel_case($name);
324 | if (property_exists($this->controller, $name)) {
325 | return get_property($this->controller, $name);
326 | } elseif (respond_to($this->controller, "get".studly_case($name))) {
327 | $name ="get".studly_case($name);
328 | return $this->controller->$name();
329 | } elseif (respond_to($this->controller, $name)) {
330 | return $this->controller->$name();
331 | }
332 | }
333 |
334 | protected function getResourceParams()
335 | {
336 | if (array_key_exists('class', $this->options)) {
337 | $paramsKey = $this->extractKey($this->options['class']);
338 | if (array_key_exists($paramsKey, $this->params)) {
339 | return $this->params[$paramsKey];
340 | }
341 | }
342 |
343 | return $this->getResourceParamsByNamespacedName();
344 | }
345 |
346 | protected function getResourceParamsByNamespacedName()
347 | {
348 | $paramsKey = $this->extractKey($this->getNamespacedName());
349 |
350 | return array_key_exists($paramsKey, $this->params) ? $this->params[$paramsKey] : [];
351 | }
352 |
353 | protected function getNamespace($controllerName = null)
354 | {
355 | $controllerName = $controllerName ?: $this->params['controller'];
356 |
357 | return array_slice(preg_split("/\\\\|\//", $controllerName), 0, -1);
358 | }
359 |
360 | protected function getNamespacedName()
361 | {
362 | $namespaceName = null;
363 | $namespace = $this->getNamespace();
364 | if (! empty($namespace)) {
365 | $namespaceName = studly_case(str_singular(implode("\\", array_flatten([$namespace, studly_case($this->getName())]))));
366 | }
367 |
368 | if (class_exists($namespaceName)) {
369 | return $namespaceName;
370 | }
371 |
372 | $className = studly_case($this->getName());
373 | if (class_exists($className)) {
374 | // Support Laravel Alias
375 | $aliasLoader = \Illuminate\Foundation\AliasLoader::getInstance();
376 | $aliasName = array_get($aliasLoader->getAliases(), $className);
377 | return class_exists($aliasName) ? $aliasName : $className;
378 | }
379 |
380 | $controllerNamespaces = $this->getNamespace(get_classname($this->controller));
381 | // Detect the Root Namespace, based on the current controller namespace
382 | // And test if the resource class exists with it
383 | // Borrowed from: https://github.com/laravel/framework/blob/v5.0.13/src/Illuminate/Routing/UrlGenerator.php#L526
384 | if (! empty($controllerNamespaces) && ! (strpos($className, '\\') === 0)) {
385 | $rootNamespace = head($controllerNamespaces);
386 | $guessName = $rootNamespace.'\\'.$className;
387 | if (class_exists($guessName)) {
388 | return $guessName;
389 | }
390 | }
391 |
392 | return $this->getName();
393 | }
394 |
395 | protected function getCurrentAuthority()
396 | {
397 | return $this->controller->getCurrentAuthority();
398 | }
399 |
400 | // Alias of getCurrentAuthority() to match CanCan API
401 | protected function getCurrentAbility()
402 | {
403 | return $this->getCurrentAuthority();
404 | }
405 |
406 | protected function getName()
407 | {
408 | return $this->name ? $this->name : $this->getNameFromController();
409 | }
410 |
411 | protected function getNameFromController()
412 | {
413 | return static::getNameByController($this->params['controller']);
414 | }
415 |
416 | protected function getInstanceName()
417 | {
418 | if (array_key_exists('instanceName', $this->options)) {
419 | return $this->options['instanceName'];
420 | } else {
421 | return camel_case($this->getName());
422 | }
423 | }
424 |
425 | protected function getCollectionActions()
426 | {
427 | $optionsCollection = array_key_exists('collection', $this->options) ? $this->options['collection'] : [];
428 | return array_unique(array_flatten(array_merge(['index'], (array) $optionsCollection)));
429 | }
430 |
431 | // NOTICE: Against Rails, 'new' action is named 'create' in Laravel.
432 | // And the Rails 'create' action is named 'store' in Laravel.
433 | protected function getCreateActions()
434 | {
435 | // We keep the 'new' option to match CanCan API
436 | $optionNew = array_key_exists('new', $this->options) ? $this->options['new'] : [];
437 | $optionCreate = array_key_exists('create', $this->options) ? $this->options['create'] : [];
438 | $options = array_merge((array) $optionNew, (array) $optionCreate);
439 | return array_unique(array_flatten(array_merge(['new', 'create', 'store'], $options)));
440 | }
441 |
442 | // Alias of getCreateActions() to match CanCan API
443 | protected function getNewActions()
444 | {
445 | return $this->getCreateActions();
446 | }
447 |
448 | protected function extractKey($value)
449 | {
450 | return str_replace('/', '', snake_case(preg_replace('/\\\\/', '', $value)));
451 | }
452 |
453 | protected function getCollectionScope()
454 | {
455 | return array_key_exists('collectionScope', $this->options) ? $this->options['collectionScope'] : null;
456 | }
457 |
458 | public function getCollectionScopeWithParams()
459 | {
460 | $collectionScope = $this->getCollectionScope();
461 | if ($collectionScope) {
462 | $collectionScopeParams = [];
463 | if (is_array($collectionScope)) {
464 | $collectionScopeParams = array_splice($collectionScope, 1);
465 | $collectionScope = array_shift($collectionScope);
466 | }
467 | return [$collectionScope, $collectionScopeParams];
468 | } else {
469 | return $collectionScope;
470 | }
471 | }
472 | }
473 |
--------------------------------------------------------------------------------
/src/Efficiently/AuthorityController/Exceptions/AccessDenied.php:
--------------------------------------------------------------------------------
1 | getMessage(); //-> "Not authorized!"
13 | * $exception->action; //-> 'read'
14 | * $exception->subject; //-> 'Product'
15 | */
16 | class AccessDenied extends \Exception
17 | {
18 | public $action;
19 | public $subject;
20 | public $defaultMessage;
21 |
22 | public function __construct($message = null, $action = null, $subject = null, $code = 0, \Exception $previous = null)
23 | {
24 | $this->action = $action;
25 | $this->subject = $subject;
26 | $this->defaultMessage = ac_trans("messages.unauthorized.default");
27 | $this->message = $message ?: $this->defaultMessage;
28 |
29 | parent::__construct($this->message, $code, $previous);
30 | }
31 |
32 | public function __toString()
33 | {
34 | return $this->message ?: $this->defaultMessage;
35 | }
36 |
37 | public function setDefaultMessage($value = null)
38 | {
39 | $this->defaultMessage = $value;
40 | $this->message = $this->message ?: $this->defaultMessage;
41 | }
42 |
43 | public function getDefaultMessage()
44 | {
45 | return $this->defaultMessage;
46 | }
47 |
48 | public function getAction()
49 | {
50 | return $this->action;
51 | }
52 |
53 | public function getSubject()
54 | {
55 | return $this->subject;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Efficiently/AuthorityController/Facades/Authority.php:
--------------------------------------------------------------------------------
1 | current();
27 | $resourceParams = [];
28 | list($resourceParams['controller'], $resourceParams['action']) = explode('@', $router->currentRouteAction());
29 | $resourceParams['controller'] = $this->normalizeControllerName($resourceParams['controller']);
30 |
31 | $resourceId = str_singular($resourceParams['controller']);
32 | if (request()->has($resourceId)) {
33 | $params = request()->all();
34 | } else {
35 | $specialInputKeys = $this->specialInputKeys();
36 | $params = [$resourceId => request()->except($specialInputKeys)] + request()->only($specialInputKeys);
37 | }
38 | $routeParams = $currentRoute->parametersWithoutNulls();
39 |
40 | // In Laravel, unlike Rails, by default 'id' parameter of a 'Product' resource is 'products'
41 | // And 'shop_id' parameter of a 'Shop' parent resource is 'shops'
42 | // So we need to reaffect correct parameter name before any controller's actions or filters.
43 | $routeParamsParsed = [];
44 | $keysToRemove = [];
45 | $lastRouteParamKey = last(array_keys($routeParams));
46 | if ($lastRouteParamKey === 'id' || $resourceId === str_singular($lastRouteParamKey)) {
47 | $id = last($routeParams);
48 | if (is_a($id, 'Illuminate\Database\Eloquent\Model')) {
49 | $id = $id->getKey();
50 | }
51 | if (is_string($id) || is_numeric($id)) {
52 | array_pop($routeParams);
53 | $routeParamsParsed['id'] = $id;
54 | }
55 | }
56 |
57 | foreach ($routeParams as $parentIdKey => $parentIdValue) {
58 | if (is_a($parentIdValue, 'Illuminate\Database\Eloquent\Model')) {
59 | $parentIdValue = $parentIdValue->getKey();
60 | }
61 | if (is_string($parentIdValue) || is_numeric($parentIdValue)) {
62 | if (! ends_with($parentIdKey, '_id')) {
63 | $parentIdKey = str_singular($parentIdKey).'_id';
64 | }
65 | $routeParamsParsed[$parentIdKey] = $parentIdValue;
66 | $keysToRemove[] = $parentIdKey;
67 | }
68 | }
69 | $routeParams = array_except($routeParams, $keysToRemove);
70 |
71 | /**
72 | * You can escape or purify these parameters. For example:
73 | *
74 | * class ProductsController extends Controller
75 | * {
76 | * public function __construct()
77 | * {
78 | * $self = $this;
79 | * $this->beforeFilter(function () use($self) {
80 | * if (array_get($self->params, 'product')) {
81 | * $productParams = $this->yourPurifyOrEscapeMethod('product');
82 | * $self->params['product'] = $productParams;
83 | * }
84 | * });
85 | * }
86 | * }
87 | *
88 | */
89 | $this->params = array_filter(array_merge($params, $routeParams, $routeParamsParsed, $resourceParams));
90 |
91 | if (property_exists($controller, 'params')) {
92 | set_property($controller, 'params', $this->params);
93 | } else {
94 | $controller->params = $this->params;
95 | }
96 | });
97 |
98 | $controller->paramsBeforeFilter($paramsFilterName);
99 | }
100 | }
101 |
102 | /**
103 | * Get an item from the parameters.
104 | *
105 | * @param string $key
106 | * @param mixed $default
107 | * @return mixed
108 | */
109 | public function get($key, $default = null)
110 | {
111 | return array_get($this->params, $key, $default);
112 | }
113 |
114 | /**
115 | * Determine if the request contains a given parameter item.
116 | *
117 | * @param string|array $key
118 | * @return bool
119 | */
120 | public function has($key)
121 | {
122 | return !!array_get($this->params, $key);
123 | }
124 |
125 | /**
126 | * Get all of the parameters for the request.
127 | *
128 | * @return array
129 | */
130 | public function all()
131 | {
132 | return $this->params;
133 | }
134 |
135 | /**
136 | * Get a subset of the items from the parameters.
137 | *
138 | * @param array $keys
139 | * @return array
140 | */
141 | public function only($keys = null)
142 | {
143 | $keys = is_array($keys) ? $keys : func_get_args();
144 | return array_only($this->params, $keys);
145 | }
146 |
147 | /**
148 | * Get all of the input except for a specified array of items.
149 | *
150 | * @param array $keys
151 | * @return array
152 | */
153 | public function except($keys = null)
154 | {
155 | $keys = is_array($keys) ? $keys : func_get_args();
156 | return array_except($this->params, $keys);
157 | }
158 |
159 | /**
160 | * Adds an item to the parameters.
161 | *
162 | * @param string $key Key to add value to.
163 | * @param mixed $value New data.
164 | *
165 | * @return mixed
166 | */
167 | public function add($key, $value)
168 | {
169 | return array_set($this->params, $key, $value);
170 | }
171 |
172 | /**
173 | * Returns all inputs keys who starts with an underscore character (_
).
174 | * For exmaple '_method' and '_token' inputs
175 | *
176 | * @param array $inputKeys
177 | * @return array
178 | */
179 | protected function specialInputKeys($inputKeys = [])
180 | {
181 | $inputKeys = $inputKeys ?: array_keys(request()->all());
182 | return array_filter($inputKeys, function ($value) {
183 | return is_string($value) ? starts_with($value, '_') : false;
184 | });
185 | }
186 |
187 | /**
188 | * @param string $controller
189 | * @return string
190 | */
191 | protected function normalizeControllerName($controller)
192 | {
193 | $name = preg_replace("/^(.+)Controller$/", "$1", $controller);
194 | return str_plural(snake_case(class_basename($name)));
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/src/Efficiently/AuthorityController/Rule.php:
--------------------------------------------------------------------------------
1 | action === 'manage' || in_array($this->action, $action);
35 | }
36 |
37 | /**
38 | * @return boolean
39 | */
40 | public function onlyCondition()
41 | {
42 | return ! $this->isConditionsEmpty();
43 | }
44 |
45 | /**
46 | * @return boolean
47 | */
48 | public function isConditionsEmpty()
49 | {
50 | $conditions = $this->conditions;
51 | return empty($conditions);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Efficiently/AuthorityController/helpers.php:
--------------------------------------------------------------------------------
1 | $value) {
11 | if (is_string($key)) {
12 | $options[$key] = $value;
13 | unset($array[$key]);
14 | }
15 | }
16 | }
17 |
18 | return $options;
19 | }
20 | }
21 |
22 | if (! function_exists('get_classname')) {
23 | /**
24 | * Like get_class() function but compatible with Mockery and $object parameter is required
25 | *
26 | * @param object|\Mockery\MockInterface $object
27 | * @return string
28 | */
29 | function get_classname($object)
30 | {
31 | $classname = get_class($object);
32 | if ($object instanceof \Mockery\MockInterface) {
33 | $classname = preg_replace('/_/', '\\', preg_replace('/^Mockery_[0-9]+_+(.+)$/', '$1', $classname));
34 | }
35 |
36 | return $classname;
37 | }
38 | }
39 |
40 | if (! function_exists('respond_to')) {
41 | /**
42 | * Like method_exists function but compatible with Mockery
43 | *
44 | * @param mixed $object
45 | * @param string $methodName
46 | * @return boolean
47 | */
48 | function respond_to($object, $methodName)
49 | {
50 | if (method_exists($object, $methodName)) {
51 | return true;
52 | } elseif (is_a($object, '\Mockery\MockInterface') && ($expectationDirector = array_get($object->mockery_getExpectations(), $methodName))) {
53 | foreach ((array) $expectationDirector->getExpectations() as $expectation) {
54 | if ($expectation->isEligible()) {
55 | return true;
56 | }
57 | }
58 | } elseif (is_string($object) && class_exists($object) && is_a(($instance=app($object)), '\Mockery\MockInterface')) {
59 | // Check if a mocked static method exists or not. You need to do:
60 | //
61 | // $category = Mockery::mock('alias:Category', ['getProducts'=>'products']);
62 | // App::instance('Category', $category);
63 | // respond_to('Category', 'getProducts');//-> true
64 | return respond_to($instance, $methodName);
65 | }
66 |
67 | return false;
68 | }
69 | }
70 |
71 | if (! function_exists('compact_property')) {
72 | function compact_property($instance, $properties)
73 | {
74 | $properties = array_slice(func_get_args(), 1);
75 | $compactArray = [];
76 | foreach ($properties as $property) {
77 | if (property_exists($instance, $property)) {
78 | $$property = get_property($instance, $property);
79 |
80 | $compactArray = array_merge($compactArray, compact($property));
81 | }
82 | }
83 |
84 | return $compactArray;
85 | }
86 | }
87 |
88 | if (! function_exists('ac_trans')) {
89 | function ac_trans($id, $parameters = [], $domain = 'messages', $locale = null)
90 | {
91 | $namespace = null;
92 | // TODO: DRY conditions
93 | if (! Lang::has($id)) {
94 | $namespace = 'authority-controller::';
95 | $id = $namespace.$id;
96 |
97 | if (! Lang::has($id)) {
98 | $defaultId = 'messages.unauthorized.default';
99 | $id = $namespace.$defaultId;
100 |
101 | if (! Lang::has($id)) {
102 | $id = $defaultId;
103 | if (Lang::has($id, 'en')) {
104 | return trans($id, $parameters, $domain, 'en');
105 | }
106 | return trans($namespace.$id, $parameters, $domain, 'en');
107 | }
108 | }
109 | }
110 |
111 | return trans($id, $parameters, $domain, $locale);
112 | }
113 | }
114 |
115 | if (! function_exists('ac_trans_choice')) {
116 | function ac_trans_choice($id, $number, array $parameters = [], $domain = 'messages', $locale = null)
117 | {
118 | $namespace = null;
119 | // TODO: DRY conditions
120 | if (! Lang::has($id)) {
121 | $namespace = 'authority-controller::';
122 | $id = $namespace.$id;
123 |
124 | if (! Lang::has($id)) {
125 | $defaultId = 'messages.unauthorized.default';
126 | $id = $namespace.$defaultId;
127 |
128 | if (! Lang::has($id)) {
129 | $id = $defaultId;
130 | if (Lang::has($id, 'en')) {
131 | return trans_choice($id, $number, $parameters, $domain, 'en');
132 | }
133 | return trans_choice($namespace.$id, $number, $parameters, $domain, 'en');
134 | }
135 | }
136 | }
137 |
138 | return trans_choice($id, $number, $parameters, $domain, $locale);
139 | }
140 | }
141 |
142 | if (! function_exists('set_property')) {
143 | function set_property($object, $propertyName, $value)
144 | {
145 | if (property_exists($object, $propertyName)) {
146 | $reflection = new \ReflectionProperty($object, $propertyName);
147 | $reflection->setAccessible(true);
148 | $reflection->setValue($object, $value);
149 | } else {
150 | $object->$propertyName = $value;
151 | }
152 | }
153 | }
154 |
155 | if (! function_exists('get_property')) {
156 | function get_property($object, $propertyName)
157 | {
158 | if (property_exists($object, $propertyName)) {
159 | $reflection = new \ReflectionProperty($object, $propertyName);
160 | $reflection->setAccessible(true);
161 | return $reflection->getValue($object);
162 | }
163 | return null;
164 | }
165 | }
166 |
167 | if (! function_exists('invoke_method')) {
168 | function invoke_method($object, $methodName, $values = [])
169 | {
170 | $values = (array) $values;
171 | if (method_exists($object, $methodName)) {
172 | $reflection = new \ReflectionMethod($object, $methodName);
173 | $reflection->setAccessible(true);
174 | return $reflection->invokeArgs($object, $values);
175 | }
176 | return call_user_func_array([$object, $methodName], $values);
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/config/config.php:
--------------------------------------------------------------------------------
1 | $serializer->serialize(function ($authority) {
8 |
9 | // Action aliases. For example:
10 | //
11 | // $authority->addAlias('moderate', ['read', 'update', 'delete']);
12 | //
13 | // See the wiki of AuthorityController for details:
14 | // https://github.com/efficiently/authority-controller/wiki/Action-aliases
15 | //
16 | // Define abilities for the passed in user here. For example:
17 | // $user = auth()->guest() ? new App\User : $authority->getCurrentUser();
18 | // if ($user->hasRole('admin')) {
19 | // $authority->allow('manage', 'all');
20 | // } else {
21 | // $authority->allow('read', 'all');
22 | // }
23 |
24 | // The first argument to `allow` is the action you are giving the user
25 | // permission to do.
26 | // If you pass 'manage' it will apply to every action. Other common actions
27 | // here are 'read', 'create', 'update' and 'destroy'.
28 | //
29 | // The second argument is the resource the user can perform the action on.
30 | // If you pass 'all' it will apply to every resource. Otherwise pass a Eloquent
31 | // class name of the resource.
32 | //
33 | // The third argument is an optional anonymous function (Closure) to further filter the
34 | // objects.
35 | // For example, here the user can only update available products.
36 | // $authority->allow('update', App\Product::class, function ($self, $product) {
37 | // return $product->available === true;
38 | // });
39 |
40 | // See the wiki of AuthorityController for details:
41 | // https://github.com/efficiently/authority-controller/wiki/Defining-Authority-rules
42 | //
43 | // Loop through each of the users permissions, and create rules:
44 | // foreach ($user->permissions as $perm) {
45 | // if ($perm->type == 'allow') {
46 | // $authority->allow($perm->action, $perm->resource);
47 | // } else {
48 | // $authority->deny($perm->action, $perm->resource);
49 | // }
50 | // }
51 | })
52 |
53 | ];
54 |
--------------------------------------------------------------------------------
/src/lang/en/messages.php:
--------------------------------------------------------------------------------
1 | [
5 | 'default' => 'You are not authorized to access this page.',
6 | ],
7 | ];
8 |
--------------------------------------------------------------------------------
/src/lang/fr/messages.php:
--------------------------------------------------------------------------------
1 | [
5 | 'default' => "Vous n'êtes pas autorisé à accéder à cette page.",
6 | ],
7 | ];
8 |
--------------------------------------------------------------------------------
/src/migrations/2015_02_23_095033_create_roles_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
17 | $table->string('name');
18 | $table->timestamps();
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | *
25 | * @return void
26 | */
27 | public function down()
28 | {
29 | Schema::drop('roles');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/migrations/2015_02_23_095107_create_permissions_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
17 | $table->string('type');
18 | $table->string('action');
19 | $table->string('resource');
20 | $table->integer('user_id')->unsigned();
21 | $table->timestamps();
22 |
23 | $table->index('user_id');
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::drop('permissions');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/migrations/2015_02_23_095152_create_role_user_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
17 | $table->integer('role_id')->unsigned();
18 | $table->integer('user_id')->unsigned();
19 | $table->timestamps();
20 | });
21 | }
22 |
23 | /**
24 | * Reverse the migrations.
25 | *
26 | * @return void
27 | */
28 | public function down()
29 | {
30 | Schema::drop('role_user');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/translations/en/messages.php:
--------------------------------------------------------------------------------
1 | [
5 | 'manage' => [
6 | 'all' => 'You are not authorized to access this page.',
7 | // 'all' => "You do not have access to :action :subject!",
8 | ],
9 | ],
10 | ];
11 |
--------------------------------------------------------------------------------
/src/translations/fr/messages.php:
--------------------------------------------------------------------------------
1 | [
5 | 'manage' => [
6 | 'all' => "Vous n'êtes pas autorisé à accéder à cette page.",
7 | // 'all' => "Vous n'avez pas accès à :action pour un(e) :subject!",
8 | ],
9 | ],
10 | ];
11 |
--------------------------------------------------------------------------------
/tests/AcAuthorityTest.php:
--------------------------------------------------------------------------------
1 | user = new stdClass;
12 | $this->user->id = 1;
13 | $this->user->email = "testuser@localhost";
14 | $this->user->name = "TestUser";
15 |
16 | $this->authority = app('authority');
17 | $this->authority->setCurrentUser($this->user);
18 | }
19 |
20 | public function tearDown()
21 | {
22 | m::close();
23 | }
24 |
25 | public function testCanStoreCurrentUser()
26 | {
27 | $this->assertSame($this->user, $this->authority->getCurrentUser());
28 |
29 | $user = new stdClass;
30 | $this->authority->setCurrentUser($user);
31 | $this->assertSame($user, $this->authority->getCurrentUser());
32 | }
33 |
34 | public function testCanEvaluateRulesOnObject()
35 | {
36 | $rulesCount = $this->authority->getRules()->count();
37 | $this->authority->allow('destroy', 'Project', function ($self, $project) {
38 | return $self->user()->id === $project->user_id;
39 | });
40 | $this->assertGreaterThan($rulesCount, $this->authority->getRules()->count());
41 |
42 | $project = m::mock('Project');
43 | $project->user_id = 1;
44 | $this->assertCan('destroy', $project);
45 |
46 | $this->assertCannot('destroy', new stdClass);
47 | }
48 |
49 | // A user cannot do anything without rules
50 | public function testCannotDoAnythingWithoutRules()
51 | {
52 | $project = $this->mock('Project');
53 |
54 | $this->assertCount(0, $this->authority->getRules());
55 |
56 | $this->assertCannot('read', 'all');
57 | $this->assertCannot('read', $project);
58 | $this->assertCannot('read', 'Project');
59 |
60 | $this->assertCannot('create', 'all');
61 | $this->assertCannot('create', $project);
62 | $this->assertCannot('create', 'Project');
63 |
64 | $this->assertCannot('update', 'all');
65 | $this->assertCannot('update', $project);
66 | $this->assertCannot('update', 'Project');
67 |
68 | $this->assertCannot('delete', 'all');
69 | $this->assertCannot('delete', $project);
70 | $this->assertCannot('delete', 'Project');
71 |
72 | $this->assertCannot('manage', 'all');
73 | $this->assertCannot('manage', $project);
74 | $this->assertCannot('manage', 'Project');
75 | }
76 |
77 | // Adding deny rule overrides prior rules
78 | public function testDenyRuleOverridesPriorRules()
79 | {
80 | $project = $this->mock('Project');
81 |
82 | $this->authority->allow('manage', 'Project');
83 | $this->authority->deny('destroy', 'Project');
84 |
85 | $this->assertCannot('destroy', $project);
86 | $this->assertCan('read', $project);
87 | $this->assertCan('update', $project);
88 | }
89 |
90 | // Adding allow rules do not override prior rules, but instead are logically or'ed
91 | public function testAllowRulesDoNotOverridePriorRules()
92 | {
93 | $user = $this->mock("User");
94 | $userAttributes = [
95 | "id" => 1, "email" => "admin@localhost", "name" => "Administrator",
96 | "created_at" => "2013-12-17 10:17:21", "updated_at" => "2013-12-17 10:17:21"
97 | ];
98 | $this->fillMock($user, $userAttributes);
99 |
100 | $this->authority = app('authority');
101 | $this->authority->setCurrentUser($this->user);
102 |
103 | $this->authority->allow('read', 'User', function ($self, $user) {
104 | return $user->id != 1;// Should return false
105 | });
106 |
107 | $this->authority->allow('read', 'User', function ($self, $user) {
108 | return $user->email == "admin@localhost";// Should return true
109 | });
110 |
111 | $this->authority->allow('read', 'User', function ($self, $user) {
112 | return $user->name != "Administrator";// Should return false
113 | });
114 |
115 | // $user can view 'index' action even if there is only one 'allow' rules which is true
116 | $this->assertCan('index', $user);
117 | }
118 |
119 | public function testRulesPrecedence()
120 | {
121 | $user = $this->mock("User");
122 | $userAttributes = [
123 | "id" => 1, "email" => "admin@localhost", "name" => "Administrator",
124 | "created_at" => "2013-12-17 10:17:21", "updated_at" => "2013-12-17 10:17:21"
125 | ];
126 | $this->fillMock($user, $userAttributes);
127 |
128 | $this->authority = app('authority');
129 | $this->authority->setCurrentUser($this->user);
130 |
131 | $this->authority->allow('read', 'User', function ($self, $user) {
132 | return $user->id != 1;// Should return false
133 | });
134 |
135 | $this->authority->allow('read', 'User', function ($self, $user) {
136 | return $user->email != "admin@localhost";// Should return false
137 | });
138 |
139 | $this->authority->allow('read', 'User', function ($self, $user) {
140 | return $user->name != "Administrator";// Should return false
141 | });
142 |
143 | $this->authority->allow('update', 'User');
144 |
145 | $this->assertCan('update', 'User');
146 | $this->assertCan('update', $user);
147 |
148 | $this->assertCan('index', 'User');
149 |
150 | // $user cannot view 'index' action if there is only 'allow' rules with conditions
151 | $this->assertCannot('index', $user);
152 |
153 | // $user can view 'index' action if there is above one 'allow' rule without conditions
154 | $this->authority->allow('index', 'User');
155 | $this->assertCan('index', $user);
156 |
157 | // $user cannot view the 'index' action if there above one 'deny' rules with conditions
158 | $this->authority->deny('read', 'User', function ($self, $user) {
159 | return $user->name == "Administrator";// Should return true
160 | });
161 | $this->assertCannot('index', $user);
162 |
163 | // Deny rule is overrided by allow rule
164 | $this->authority->allow('index', 'User');
165 | $this->assertCan('index', $user);
166 |
167 | // $user cannot view the 'index' action if there above one 'deny' rules without conditions
168 | $this->authority->deny('index', 'User');
169 | $this->assertCannot('index', $user);
170 | }
171 |
172 | public function testDefaultAliasActions()
173 | {
174 | $this->assertEquals(['read'], $this->authority->getAliasesForAction('index'));
175 | $this->assertEquals(['read'], $this->authority->getAliasesForAction('show'));
176 |
177 | $this->assertEquals(['create'], $this->authority->getAliasesForAction('new'));
178 | $this->assertEquals(['create'], $this->authority->getAliasesForAction('store'));
179 |
180 | $this->assertEquals(['update'], $this->authority->getAliasesForAction('edit'));
181 |
182 | $this->assertEquals(['delete'], $this->authority->getAliasesForAction('destroy'));
183 |
184 | $this->assertEquals(['read', 'index', 'show'], $this->authority->getExpandActions('read'));
185 | $this->assertEquals(['create', 'new', 'store'], $this->authority->getExpandActions('create'));
186 | $this->assertEquals(['update', 'edit'], $this->authority->getExpandActions('update'));
187 | $this->assertEquals(['delete', 'destroy'], $this->authority->getExpandActions('delete'));
188 | }
189 |
190 | // Helpers
191 |
192 | protected function assertCan($action, $resource, $resourceValue = null, $authority = null)
193 | {
194 | $authority = $authority ?: $this->authority;
195 | $this->assertTrue($authority->can($action, $resource, $resourceValue));
196 | }
197 |
198 | protected function assertCannot($action, $resource, $resourceValue = null, $authority = null)
199 | {
200 | $authority = $authority ?: $this->authority;
201 | $this->assertTrue($this->authority->cannot($action, $resource, $resourceValue));
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/tests/AcControllerAdditionsTest.php:
--------------------------------------------------------------------------------
1 | controllerClass = $this->mock('AcControllerAdditionsClass');
23 |
24 | $this->controller = $this->controllerClass->makePartial();
25 | $this->controller->shouldReceive('getParams')->andReturn([]);
26 | $this->controller->shouldReceive('getCurrentUser')->andReturn("currentUser");
27 | }
28 |
29 | // Authorize should assign $_authorized property and pass args to current authority
30 | public function testAuthorizeShouldAssignAuthorizedInstanceVariableAndPassArgsToCurrentAuthority()
31 | {
32 | $this->controller->shouldReceive('getCurrentAuthority->authorize')->with('foo', 'bar')->once();
33 |
34 | $this->controller->authorize('foo', 'bar');
35 | $this->assertTrue($this->getProperty($this->controller, '_authorized'));
36 | }
37 |
38 | // Should have a getCurrentAuthority() method which generates an authority for the current user
39 | public function testShouldHaveACurrentAuthorityMethodWhichGeneratesAnAuthorityForTheCurrentUser()
40 | {
41 | $this->assertInstanceOf("Efficiently\AuthorityController\Authority", $this->controller->getCurrentAuthority());
42 | }
43 |
44 | // Should provide a can() and cannot() methods which go through the current authority
45 | public function testShouldProvideACanAndCannotMethodsWhichGoThroughTheCurrentAuthority()
46 | {
47 | $this->assertInstanceOf("Efficiently\AuthorityController\Authority", $this->controller->getCurrentAuthority());
48 | $this->assertFalse($this->controller->can('foo', 'bar'));
49 | $this->assertTrue($this->controller->cannot('foo', 'bar'));
50 | }
51 |
52 | // loadAndAuthorizeResource() should setup a before filter which passes call to ControllerResource
53 | public function testLoadAndAuthorizeResourceShouldSetupABeforeFilterWhichPassesCallToControllerResource()
54 | {
55 | $controller = $this->controller;
56 | $controllerResourceClass = 'Efficiently\AuthorityController\ControllerResource';
57 | App::offsetUnset($controllerResourceClass);
58 | App::bind($controllerResourceClass, function ($app, $parameters) use ($controllerResourceClass, $controller) {
59 | $this->assertEquals($parameters, [$controller, null, ['foo' => 'bar']]);
60 | $controllerResource = m::mock($controllerResourceClass, $parameters);
61 | $controllerResource->shouldReceive('loadAndAuthorizeResource')->once();
62 | return $controllerResource;
63 | });
64 |
65 | $controller->shouldReceive('beforeFilter')->with(m::type('string'), [])->once()
66 | ->andReturnUsing(function ($filterName, $options) use ($controller) {
67 | $this->assertTrue(Event::hasListeners($this->filterPrefix.$filterName));
68 | return Event::fire($this->filterPrefix.$filterName);
69 | });
70 |
71 | $controller->loadAndAuthorizeResource(['foo' => 'bar']);
72 | }
73 |
74 | // loadAndAuthorizeResource() should properly pass first argument as the resource name
75 | public function testloadAndAuthorizeResourceShouldProperlyPassFirstArgumentAsTheResourceName()
76 | {
77 | $controller = $this->controller;
78 | $controllerResourceClass = 'Efficiently\AuthorityController\ControllerResource';
79 | App::offsetUnset($controllerResourceClass);
80 | App::bind($controllerResourceClass, function ($app, $parameters) use ($controllerResourceClass, $controller) {
81 | $this->assertEquals($parameters, [$controller, 'project', ['foo' => 'bar']]);
82 | $controllerResource = m::mock($controllerResourceClass, $parameters);
83 | $controllerResource->shouldReceive('loadAndAuthorizeResource')->once();
84 | return $controllerResource;
85 | });
86 |
87 | $controller->shouldReceive('beforeFilter')->with(m::type('string'), [])->once()
88 | ->andReturnUsing(function ($filterName, $options) use ($controller) {
89 | $this->assertTrue(Event::hasListeners($this->filterPrefix.$filterName));
90 | return Event::fire($this->filterPrefix.$filterName);
91 | });
92 |
93 | $controller->loadAndAuthorizeResource('project', ['foo' => 'bar']);
94 | }
95 |
96 | // loadAndAuthorizeResource() with 'prepend' should prepend the before filter
97 | public function testLoadAndAuthorizeResourceWithPrependShouldPrependTheBeforeFilter()
98 | {
99 | $this->controller->shouldReceive('prependBeforeFilter')->once();
100 |
101 | $this->controller->loadAndAuthorizeResource(['foo' => 'bar', 'prepend' => true]);
102 | }
103 |
104 | // authorizeResource() should setup a before filter which passes call to ControllerResource
105 | public function testAuthorizeResourceShouldSetupABeforeFilterWhichPassesCallToControllerResource()
106 | {
107 | $controller = $this->controller;
108 | $controllerResourceClass = 'Efficiently\AuthorityController\ControllerResource';
109 | App::offsetUnset($controllerResourceClass);
110 | App::bind($controllerResourceClass, function ($app, $parameters) use ($controllerResourceClass, $controller) {
111 | $this->assertEquals($parameters, [$controller, null, ['foo' => 'bar']]);
112 | $controllerResource = m::mock($controllerResourceClass, $parameters);
113 | $controllerResource->shouldReceive('authorizeResource')->once();
114 | return $controllerResource;
115 | });
116 |
117 | $controller->shouldReceive('beforeFilter')->with(m::type('string'), ['except' => 'show'])->once()
118 | ->andReturnUsing(function ($filterName, $options) use ($controller) {
119 | $this->assertTrue(Event::hasListeners($this->filterPrefix.$filterName));
120 | return Event::fire($this->filterPrefix.$filterName);
121 | });
122 |
123 | $controller->authorizeResource(['foo' => 'bar', 'except' => 'show']);
124 | }
125 |
126 | // loadResource() should setup a before filter which passes call to ControllerResource
127 | public function testLoadResourceShouldSetupABeforeFilterWhichPassesCallToControllerResource()
128 | {
129 | $controller = $this->controller;
130 | $controllerResourceClass = 'Efficiently\AuthorityController\ControllerResource';
131 | App::offsetUnset($controllerResourceClass);
132 | App::bind($controllerResourceClass, function ($app, $parameters) use ($controllerResourceClass, $controller) {
133 | $this->assertEquals($parameters, [$controller, null, ['foo' => 'bar']]);
134 | $controllerResource = m::mock($controllerResourceClass, $parameters);
135 | $controllerResource->shouldReceive('loadResource')->once();
136 | return $controllerResource;
137 | });
138 |
139 | $controller->shouldReceive('beforeFilter')->with(m::type('string'), ['only' => ['show', 'index']])->once()
140 | ->andReturnUsing(function ($filterName, $options) use ($controller) {
141 | $this->assertTrue(Event::hasListeners($this->filterPrefix.$filterName));
142 | return Event::fire($this->filterPrefix.$filterName);
143 | });
144 |
145 | $controller->loadResource(['foo' => 'bar', 'only' => ['show', 'index']]);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/tests/AcExceptionsHandler.php:
--------------------------------------------------------------------------------
1 | 'bar', 'value'];
11 | $options = array_extract_options($args);
12 | $expect = ['foo' => 'bar'];
13 | $this->assertEquals($options, $expect);
14 | }
15 |
16 | public function testCompactProperty()
17 | {
18 | $expectName = "Best movie";
19 | $myProduct = new AcProduct;
20 | $myProduct->setName($expectName);
21 | $compactedArray = compact_property($myProduct, 'name');
22 |
23 | $this->assertArrayHasKey('name', $compactedArray);
24 | $this->assertEquals($expectName, $compactedArray['name']);
25 | }
26 |
27 | public function testCompactProperties()
28 | {
29 | $expectName = "Best movie";
30 | $expectPrice = "15";
31 | $myProduct = new AcProduct;
32 | $myProduct->setName($expectName);
33 | $myProduct->setPrice($expectPrice);
34 | $compactedArray = compact_property($myProduct, 'name', 'price');
35 |
36 | $this->assertArrayHasKey('name', $compactedArray);
37 | $this->assertArrayHasKey('price', $compactedArray);
38 | $this->assertEquals($expectName, $compactedArray['name']);
39 | $this->assertEquals($expectPrice, $compactedArray['price']);
40 | }
41 |
42 | public function testGetClassname()
43 | {
44 | $mock = m::mock('Project');
45 | $this->assertNotEquals('Project', get_class($mock));
46 | $this->assertEquals('Project', get_classname($mock));
47 |
48 | $mockNamespace = m::mock('Sub\Task');
49 | $this->assertNotEquals('Sub\Task', get_class($mockNamespace));
50 | $this->assertEquals('Sub\Task', get_classname($mockNamespace));
51 | }
52 |
53 | public function testAcTrans()
54 | {
55 | $defaultErrorMessage = ac_trans("messages.unauthorized.default");
56 | $this->assertEquals('You are not authorized to access this page.', $defaultErrorMessage);
57 |
58 | App::setLocale('fr');
59 | $defaultErrorMessageFr = ac_trans("messages.unauthorized.default");
60 |
61 | $this->assertNotEquals('You are not authorized to access this page.', $defaultErrorMessageFr);
62 | $this->assertEquals("Vous n'êtes pas autorisé à accéder à cette page.", $defaultErrorMessageFr);
63 | }
64 |
65 | public function testRespondTo()
66 | {
67 | $mock = m::mock('Project');
68 | $this->assertFalse(method_exists($mock, 'toto'));
69 | $this->assertFalse(respond_to($mock, 'toto'));
70 |
71 | $mock->shouldReceive('toto');
72 | $this->assertFalse(method_exists($mock, 'toto'));
73 | $this->assertTrue(respond_to($mock, 'toto'));
74 | }
75 |
76 | public function testGetProperty()
77 | {
78 | $category = new AcCategory;
79 | $privatePropertyName = 'privateProperty';
80 | $this->assertNotEquals('public property', get_property($category, $privatePropertyName));
81 | $this->assertEquals('private property', get_property($category, $privatePropertyName));
82 |
83 | }
84 |
85 | public function testSetProperty()
86 | {
87 | $category = new AcCategory;
88 | $privatePropertyName = 'privateProperty';
89 | set_property($category, $privatePropertyName, 'updated private property');
90 | $this->assertNotEquals('private property', get_property($category, $privatePropertyName));
91 | $this->assertEquals('updated private property', get_property($category, $privatePropertyName));
92 | }
93 |
94 | public function testInvokeMethod()
95 | {
96 | $category = new AcCategory;
97 |
98 | $this->assertTrue(is_callable([$category, 'publicMethod']));
99 |
100 | $privateMethodName = 'privateMethod';
101 | $this->assertFalse(is_callable([$category, $privateMethodName]));
102 | $this->assertEquals('private method', invoke_method($category, $privateMethodName));
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/tests/AcParametersTest.php:
--------------------------------------------------------------------------------
1 | app = $this->app = app();
32 | $this->app['router'] = $this->router = $this->mockRouter();
33 |
34 | $this->controllerName = "ProjectsController";
35 | $this->router->resource('projects', $this->controllerName);
36 |
37 | $this->parameters = new \Efficiently\AuthorityController\Parameters;
38 | $this->app->instance('Params', $this->parameters);
39 | }
40 |
41 | public function testAddParameter()
42 | {
43 | Params::add('key', 'value');
44 | $this->assertEquals(Params::get('key'), 'value');
45 | }
46 |
47 | public function testAddParameterWithDotKeys()
48 | {
49 | Params::add('key.subkey', 'value');
50 | $this->assertEquals(Params::get('key.subkey'), 'value');
51 | }
52 |
53 | public function testOnlyParameters()
54 | {
55 | Params::add('key1', 'value1');
56 | Params::add('key2', 'value2');
57 | $this->assertEquals(Params::only('key1'), ['key1' => 'value1']);
58 | }
59 |
60 | public function testExceptParameters()
61 | {
62 | Params::add('key1', 'value1');
63 | Params::add('key2', 'value2');
64 | $this->assertEquals(Params::except('key2'), ['key1' => 'value1']);
65 | }
66 |
67 | public function testExtractResourceFromInput()
68 | {
69 | $input = ['project' => ['name' => 'foo']];
70 | $parameters = $this->parameters;
71 | $controller = $this->mockController();
72 |
73 | $this->call('POST', '/projects', $input);// store action
74 |
75 | $this->assertArrayHasKey('project', $this->getProperty($parameters, 'params'));
76 | $this->assertEquals($this->getProperty($parameters, 'params')['project'], $input['project']);
77 |
78 | $this->assertArrayHasKey('project', $this->getProperty($controller, 'params'));
79 | $this->assertEquals($this->getProperty($controller, 'params')['project'], $input['project']);
80 | }
81 |
82 | public function testResolveResourceFromInput()
83 | {
84 | $input = ['name' => 'foo'];
85 | $parameters = $this->parameters;
86 | $controller = $this->mockController();
87 |
88 | $this->call('POST', '/projects', $input);// store action
89 |
90 | $this->assertArrayHasKey('project', $this->getProperty($parameters, 'params'));
91 | $this->assertEquals($this->getProperty($parameters, 'params')['project'], $input);
92 |
93 | $this->assertArrayHasKey('project', $this->getProperty($controller, 'params'));
94 | $this->assertEquals($this->getProperty($controller, 'params')['project'], $input);
95 | }
96 |
97 | public function testExtractResourceFromInputWithSingularControllerAndRoute()
98 | {
99 | $input = ['project' => ['name' => 'foo']];
100 | $parameters = $this->parameters;
101 |
102 | $controllerName = "ProjectController";
103 | Route::resource('project', $controllerName);
104 | $controller = $this->mockController($controllerName);
105 |
106 | $this->call('POST', '/project', $input);// store action
107 |
108 | $this->assertArrayHasKey('project', $this->getProperty($parameters, 'params'));
109 | $this->assertEquals($this->getProperty($parameters, 'params')['project'], $input['project']);
110 |
111 | $this->assertArrayHasKey('project', $this->getProperty($controller, 'params'));
112 | $this->assertEquals($this->getProperty($controller, 'params')['project'], $input['project']);
113 | }
114 |
115 | public function testResolveResourceFromInputWithSingularControllerAndRoute()
116 | {
117 | $input = ['name' => 'foo'];
118 | $parameters = $this->parameters;
119 |
120 | $controllerName = "ProjectController";
121 | Route::resource('project', $controllerName);
122 | $controller = $this->mockController($controllerName);
123 |
124 | $this->call('POST', '/project', $input);// store action
125 |
126 | $this->assertArrayHasKey('project', $this->getProperty($parameters, 'params'));
127 | $this->assertEquals($this->getProperty($parameters, 'params')['project'], $input);
128 |
129 | $this->assertArrayHasKey('project', $this->getProperty($controller, 'params'));
130 | $this->assertEquals($this->getProperty($controller, 'params')['project'], $input);
131 | }
132 |
133 | public function testResolveActionAndControllerNamesFromRequest()
134 | {
135 | $input = ['project' => ['name' => 'foo']];
136 | $parameters = $this->parameters;
137 | $controller = $this->mockController();
138 |
139 | $this->call('POST', '/projects', $input);// store action
140 |
141 | $this->assertArrayHasKey('action', $this->getProperty($parameters, 'params'));
142 | $this->assertEquals($this->getProperty($parameters, 'params')['action'], 'store');
143 |
144 | $this->assertArrayHasKey('controller', $this->getProperty($parameters, 'params'));
145 | $this->assertEquals($this->getProperty($parameters, 'params')['controller'], 'projects');
146 |
147 |
148 | $this->assertArrayHasKey('action', $this->getProperty($controller, 'params'));
149 | $this->assertEquals($this->getProperty($controller, 'params')['action'], 'store');
150 |
151 | $this->assertArrayHasKey('controller', $this->getProperty($controller, 'params'));
152 | $this->assertEquals($this->getProperty($controller, 'params')['controller'], 'projects');
153 | }
154 |
155 | public function testResolveResourceIdFromRequest()
156 | {
157 | $parameters = $this->parameters;
158 | $controller = $this->mockController();
159 |
160 | $this->call('GET', '/projects/5');// show action
161 |
162 | $this->assertArrayHasKey('id', $this->getProperty($parameters, 'params'));
163 | $this->assertEquals($this->getProperty($parameters, 'params')['id'], '5');
164 |
165 | $this->assertArrayHasKey('id', $this->getProperty($controller, 'params'));
166 | $this->assertEquals($this->getProperty($controller, 'params')['id'], '5');
167 | }
168 |
169 | public function testResolveResourceAndParentResourceIdsFromRequest()
170 | {
171 | $parameters = $this->parameters;
172 |
173 | $controllerName = "TasksController";
174 | Route::resource('projects.tasks', $controllerName);
175 | $controller = $this->mockController($controllerName);
176 |
177 | $this->call('GET', '/projects/5/tasks/2');// show action of task resource
178 |
179 | $this->assertArrayHasKey('project_id', $this->getProperty($parameters, 'params'));
180 | $this->assertEquals($this->getProperty($parameters, 'params')['project_id'], '5');
181 |
182 | $this->assertArrayHasKey('project_id', $this->getProperty($controller, 'params'));
183 | $this->assertEquals($this->getProperty($controller, 'params')['project_id'], '5');
184 |
185 |
186 | $this->assertArrayHasKey('id', $this->getProperty($parameters, 'params'));
187 | $this->assertEquals($this->getProperty($parameters, 'params')['id'], '2');
188 |
189 | $this->assertArrayHasKey('id', $this->getProperty($controller, 'params'));
190 | $this->assertEquals($this->getProperty($controller, 'params')['id'], '2');
191 | }
192 |
193 | public function testResolveResourceIdFromRequestWithSingularController()
194 | {
195 | $parameters = $this->parameters;
196 |
197 | $controllerName = "ProjectController";
198 | Route::resource('projects', $controllerName);
199 | $controller = $this->mockController($controllerName);
200 |
201 | $this->call('GET', '/projects/6');// show action
202 |
203 | $this->assertArrayHasKey('id', $this->getProperty($parameters, 'params'));
204 | $this->assertEquals($this->getProperty($parameters, 'params')['id'], '6');
205 |
206 | $this->assertArrayHasKey('id', $this->getProperty($controller, 'params'));
207 | $this->assertEquals($this->getProperty($controller, 'params')['id'], '6');
208 | }
209 |
210 | public function testResolveResourceIdFromRequestWithSingularRoute()
211 | {
212 | $parameters = $this->parameters;
213 |
214 | Route::resource('project', $this->controllerName);
215 | $controller = $this->mockController();
216 |
217 | $this->call('GET', '/projects/7');// show action
218 |
219 | $this->assertArrayHasKey('id', $this->getProperty($parameters, 'params'));
220 | $this->assertEquals($this->getProperty($parameters, 'params')['id'], '7');
221 |
222 | $this->assertArrayHasKey('id', $this->getProperty($controller, 'params'));
223 | $this->assertEquals($this->getProperty($controller, 'params')['id'], '7');
224 | }
225 |
226 | public function testResolveResourceIdFromRequestWithSingularControllerAndRoute()
227 | {
228 | $parameters = $this->parameters;
229 |
230 | $controllerName = "ProjectController";
231 | Route::resource('project', $controllerName);
232 | $controller = $this->mockController($controllerName);
233 |
234 | $this->call('GET', '/project/8');// show action
235 |
236 | $this->assertArrayHasKey('id', $this->getProperty($parameters, 'params'));
237 | $this->assertEquals($this->getProperty($parameters, 'params')['id'], '8');
238 |
239 | $this->assertArrayHasKey('id', $this->getProperty($controller, 'params'));
240 | $this->assertEquals($this->getProperty($controller, 'params')['id'], '8');
241 | }
242 |
243 | public function testResolveResourceAndParentResourceIdsFromRequestWithSingularControllerAndRoute()
244 | {
245 | $parameters = $this->parameters;
246 |
247 | $controllerName = "TaskController";
248 | Route::resource('project.task', $controllerName);
249 | $controller = $this->mockController($controllerName);
250 |
251 | $this->call('GET', '/project/5/task/2');// show action of task resource
252 |
253 | $this->assertArrayHasKey('project_id', $this->getProperty($parameters, 'params'));
254 | $this->assertEquals($this->getProperty($parameters, 'params')['project_id'], '5');
255 |
256 | $this->assertArrayHasKey('project_id', $this->getProperty($controller, 'params'));
257 | $this->assertEquals($this->getProperty($controller, 'params')['project_id'], '5');
258 |
259 |
260 | $this->assertArrayHasKey('id', $this->getProperty($parameters, 'params'));
261 | $this->assertEquals($this->getProperty($parameters, 'params')['id'], '2');
262 |
263 | $this->assertArrayHasKey('id', $this->getProperty($controller, 'params'));
264 | $this->assertEquals($this->getProperty($controller, 'params')['id'], '2');
265 | }
266 |
267 | public function testExtractResourceIdFromRequestWithImplicitAction()
268 | {
269 | $parameters = $this->parameters;
270 |
271 | $controllerName = "UsersController";
272 | Route::get('users/{id}', ['as'=>'users.show', 'uses' =>'UsersController@show']); // show action
273 | $controller = $this->mockController($controllerName);
274 |
275 | $this->call('GET', '/users/6');// show action
276 |
277 | $this->assertArrayHasKey('id', $this->getProperty($parameters, 'params'));
278 | $this->assertEquals($this->getProperty($parameters, 'params')['id'], '6');
279 |
280 | $this->assertArrayHasKey('id', $this->getProperty($controller, 'params'));
281 | $this->assertEquals($this->getProperty($controller, 'params')['id'], '6');
282 | }
283 |
284 | public function testExtractResourceIdFromRequestWithEditAction()
285 | {
286 | $parameters = $this->parameters;
287 |
288 | $controllerName = "UsersController";
289 | Route::get('users/{id}/edit', ['as'=>'users.edit', 'uses' =>'UsersController@edit']); // edit action
290 | $controller = $this->mockController($controllerName);
291 |
292 | $this->call('GET', '/users/6/edit');// edit action
293 |
294 | $this->assertArrayHasKey('id', $this->getProperty($parameters, 'params'));
295 | $this->assertEquals($this->getProperty($parameters, 'params')['id'], '6');
296 |
297 | $this->assertArrayHasKey('id', $this->getProperty($controller, 'params'));
298 | $this->assertEquals($this->getProperty($controller, 'params')['id'], '6');
299 | }
300 |
301 | public function testExtractResourceAndParentResourceIdsFromRequest()
302 | {
303 | $parameters = $this->parameters;
304 |
305 | Route::get('users/{id}', ['as'=>'users.show', 'uses' =>'UsersController@show']); // user show action
306 |
307 | $controllerName = "PostsController";
308 | Route::get('users/{user_id}/posts/{id}', ['as'=>'users.posts.show', 'uses' =>'PostsController@show']); // post show action
309 | $controller = $this->mockController($controllerName);
310 |
311 | $this->call('GET', '/users/7/posts/3');// show action of post resource
312 |
313 | $this->assertArrayHasKey('user_id', $this->getProperty($parameters, 'params'));
314 | $this->assertEquals($this->getProperty($parameters, 'params')['user_id'], '7');
315 |
316 | $this->assertArrayHasKey('user_id', $this->getProperty($controller, 'params'));
317 | $this->assertEquals($this->getProperty($controller, 'params')['user_id'], '7');
318 |
319 |
320 | $this->assertArrayHasKey('id', $this->getProperty($parameters, 'params'));
321 | $this->assertEquals($this->getProperty($parameters, 'params')['id'], '3');
322 |
323 | $this->assertArrayHasKey('id', $this->getProperty($controller, 'params'));
324 | $this->assertEquals($this->getProperty($controller, 'params')['id'], '3');
325 | }
326 |
327 | public function testExtractResourceAndNestedResourceIdsFromRequest()
328 | {
329 | $parameters = $this->parameters;
330 |
331 | Route::get('users/{id}', ['as'=>'users.show', 'uses' =>'UsersController@show']); // user show action
332 |
333 | Route::get('users/{user_id}/posts/{id}', ['as'=>'users.posts.show', 'uses' =>'PostsController@show']); // post show action
334 |
335 | $controllerName = "CommentsController";
336 | // comment show action
337 | Route::get('users/{user_id}/posts/{post_id}/comments/{id}', ['as'=>'users.posts.comments.show', 'uses' =>'CommentsController@show']);
338 | $controller = $this->mockController($controllerName);
339 |
340 | $this->call('GET', '/users/7/posts/3/comments/8');// show action of comment resource
341 |
342 | $this->assertArrayHasKey('user_id', $this->getProperty($parameters, 'params'));
343 | $this->assertEquals($this->getProperty($parameters, 'params')['user_id'], '7');
344 |
345 | $this->assertArrayHasKey('user_id', $this->getProperty($controller, 'params'));
346 | $this->assertEquals($this->getProperty($controller, 'params')['user_id'], '7');
347 |
348 | $this->assertArrayHasKey('post_id', $this->getProperty($parameters, 'params'));
349 | $this->assertEquals($this->getProperty($parameters, 'params')['post_id'], '3');
350 |
351 | $this->assertArrayHasKey('post_id', $this->getProperty($controller, 'params'));
352 | $this->assertEquals($this->getProperty($controller, 'params')['post_id'], '3');
353 |
354 | $this->assertArrayHasKey('id', $this->getProperty($parameters, 'params'));
355 | $this->assertEquals($this->getProperty($parameters, 'params')['id'], '8');
356 |
357 | $this->assertArrayHasKey('id', $this->getProperty($controller, 'params'));
358 | $this->assertEquals($this->getProperty($controller, 'params')['id'], '8');
359 | }
360 |
361 |
362 | protected function mockController($controllerName = null)
363 | {
364 | $controllerName = $controllerName ?: $this->controllerName;
365 |
366 | $this->router = $this->app['router'];
367 |
368 | $events = $this->getProperty($this->router, 'events');
369 | $this->setProperty($this->router, 'events', $events);
370 |
371 | $this->app['router'] = $this->router;
372 |
373 | $this->mock($controllerName);
374 | $controllerInstance = $this->app->make($controllerName);
375 | $controllerInstance->shouldReceive('paramsBeforeFilter')->with(m::type('string'))->once();
376 | $controllerInstance->shouldReceive('callAction')->with(m::type('string'), m::type('array'))->andReturnUsing(function ($method, $parameters) use ($controllerInstance) {
377 | $this->app->make('Params')->fillController($controllerInstance);
378 |
379 | $filterName = "router.filter: controller.parameters.".get_classname($controllerInstance);
380 | $this->assertTrue(Event::hasListeners($filterName));
381 | Event::fire($filterName);
382 |
383 | return new \Symfony\Component\HttpFoundation\Response;
384 | });
385 |
386 | $this->mock('\Efficiently\AuthorityController\ControllerResource');
387 | $this->controllerResource = $this->app->make('\Efficiently\AuthorityController\ControllerResource');
388 | $this->controllerResource->shouldReceive('getNameByController')->with('ProjectsController')->andReturn('project');
389 |
390 | return $controllerInstance;
391 | }
392 |
393 | protected function mockRouter($app = null)
394 | {
395 | $app = $app ?: $this->app;
396 | $routerFacade = new \Illuminate\Support\Facades\Route;
397 | $this->invokeMethod($routerFacade, 'createFreshMockInstance', ['router']);
398 | $router = $routerFacade::getFacadeRoot()->makePartial();
399 |
400 | $router->shouldReceive('substituteBindings')->with(m::type('\Illuminate\Routing\Route'))->andReturnUsing(function ($route) use ($router) {
401 | foreach ($route->parameters() as $key => $value) {
402 | if (isset($router->binders[$key])) {
403 | $route->setParameter($key, $router->performBinding($key, $value, $route));
404 | }
405 | }
406 | $routePartial = m::mock($route);
407 | $routePartial->shouldReceive('signatureParameters')->andReturn([]);
408 | $router->substituteImplicitBindings($routePartial);
409 |
410 | return $routePartial;
411 | });
412 |
413 | $this->setProperty($router, 'events', $app['events']);
414 | $this->setProperty($router, 'routes', new \Illuminate\Routing\RouteCollection);
415 | $this->setProperty($router, 'container', $app);
416 |
417 | return $router;
418 | }
419 | }
420 |
--------------------------------------------------------------------------------
/tests/AcProjectsControllerTest.php:
--------------------------------------------------------------------------------
1 | controllerName = "AcProjectsController";
18 | $this->modelName = "AcProject";
19 | $this->modelAttributes = ['id' => 5, 'name' => 'Test AuthorityController package', 'priority' => 1];
20 | $this->resourceName = "ac_projects"; // str_plural(snake_case($this->modelName));
21 |
22 | Route::resource($this->resourceName, $this->controllerName);
23 |
24 | $this->userAttributes = ['id' => 1, 'username' => 'tortue', 'firstname' => 'Tortue',
25 | 'lastname' => 'Torche', 'email' => 'tortue.torche@spam.me', 'password' => Hash::make('tortuetorche'),
26 | 'displayname' => 'Tortue Torche'];
27 |
28 | $this->user = $this->getUserWithRole('admin');
29 | $this->authority = $this->getAuthority($this->user);
30 |
31 | $this->authority->allow('manage', $this->modelName);
32 | }
33 |
34 | public function testIndexActionAllows()
35 | {
36 | $actionName = 'index';
37 |
38 | $model = $this->buildModel();
39 |
40 | $this->assertCan($actionName, $this->modelName);
41 |
42 | $this->action('GET', $this->controllerName."@".$actionName);
43 | $this->assertViewHas('acProjects', $model->get());
44 | }
45 |
46 | /**
47 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
48 | */
49 | public function testIndexActionDenies()
50 | {
51 | $actionName = 'index';
52 |
53 | $this->authority->deny($actionName, $this->modelName);
54 |
55 | $model = $this->buildModel();
56 |
57 | $this->assertCannot($actionName, $this->modelName);
58 |
59 | $this->action('GET', $this->controllerName."@".$actionName);
60 | }
61 |
62 | public function testCreateActionAllows()
63 | {
64 | $actionName = 'create';
65 |
66 | $modelAttributes = array_except($this->modelAttributes, 'id');
67 | $model = $this->buildModel($modelAttributes);
68 |
69 | $this->assertCan($actionName, $this->modelName);
70 |
71 | $this->action('GET', $this->controllerName."@".$actionName);
72 | $this->assertResponseOk();
73 | }
74 |
75 | /**
76 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
77 | */
78 | public function testCreateActionDenies()
79 | {
80 | $actionName = 'create';
81 |
82 | $this->authority->deny($actionName, $this->modelName);
83 |
84 | $modelAttributes = array_except($this->modelAttributes, 'id');
85 | $model = $this->buildModel($modelAttributes);
86 |
87 | $this->assertCannot($actionName, $this->modelName);
88 |
89 | $this->action('GET', $this->controllerName."@".$actionName);
90 | }
91 |
92 | public function testStoreActionAllows()
93 | {
94 | $actionName = 'store';
95 |
96 | $modelAttributes = array_except($this->modelAttributes, 'id');
97 | $model = $this->buildModel($modelAttributes);
98 |
99 | $this->assertCan($actionName, $this->modelName);
100 |
101 | $this->action('POST', $this->controllerName."@".$actionName, [], $modelAttributes);
102 | $this->assertRedirectedToRoute($this->resourceName.'.index');
103 | }
104 |
105 | /**
106 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
107 | */
108 | public function testStoreActionDenies()
109 | {
110 | $actionName = 'store';
111 |
112 | $this->authority->deny($actionName, $this->modelName);
113 |
114 | $modelAttributes = array_except($this->modelAttributes, 'id');
115 | $model = $this->buildModel($modelAttributes);
116 |
117 | $this->assertCannot($actionName, $this->modelName);
118 |
119 | $this->action('POST', $this->controllerName."@".$actionName, [], $modelAttributes);
120 | }
121 |
122 | public function testShowActionAllows()
123 | {
124 | $actionName = 'show';
125 |
126 | $model = $this->buildModel();
127 |
128 | $this->assertCan($actionName, $this->modelName);
129 |
130 | $response = $this->action('GET', $this->controllerName."@".$actionName, [$model->id]);
131 | $this->assertViewHas('acProject');
132 | $view = $response->original;
133 | $this->assertEquals($view->acProject->id, 5);
134 | }
135 |
136 | /**
137 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
138 | */
139 | public function testShowActionDenies()
140 | {
141 | $actionName = 'show';
142 |
143 | $this->authority->deny($actionName, $this->modelName);
144 |
145 | $model = $this->buildModel();
146 |
147 | $this->assertCannot($actionName, $this->modelName);
148 |
149 | $this->action('GET', $this->controllerName."@".$actionName, [$model->id]);
150 | }
151 |
152 | public function testEditActionAllows()
153 | {
154 | $actionName = 'edit';
155 |
156 | $model = $this->buildModel();
157 |
158 | $this->assertCan($actionName, $this->modelName);
159 |
160 | $response = $this->action('GET', $this->controllerName."@".$actionName, [$model->id]);
161 | $this->assertViewHas('acProject');
162 | $view = $response->original;
163 | $this->assertEquals($view->acProject->id, 5);
164 | }
165 |
166 | /**
167 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
168 | */
169 | public function testEditActionDenies()
170 | {
171 | $actionName = 'edit';
172 |
173 | $this->authority->deny($actionName, $this->modelName);
174 |
175 | $model = $this->buildModel();
176 |
177 | $this->assertCannot($actionName, $this->modelName);
178 |
179 | $this->action('GET', $this->controllerName."@".$actionName, [$model->id]);
180 | }
181 |
182 | public function testUpdateActionAllows()
183 | {
184 | $actionName = 'update';
185 |
186 | $modelAttributes = $this->modelAttributes;
187 | $model = $this->buildModel($modelAttributes);
188 |
189 | $this->assertCan($actionName, $this->modelName);
190 |
191 | $this->action('PATCH', $this->controllerName."@".$actionName, [$model->id], $modelAttributes);
192 | $this->assertRedirectedToRoute($this->resourceName.'.show', 5);
193 | }
194 |
195 | /**
196 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
197 | */
198 | public function testUpdateActionDenies()
199 | {
200 | $actionName = 'update';
201 |
202 | $this->authority->deny($actionName, $this->modelName);
203 |
204 | $modelAttributes = $this->modelAttributes;
205 | $model = $this->buildModel($modelAttributes);
206 |
207 | $this->assertCannot($actionName, $this->modelName);
208 |
209 | $this->action('PATCH', $this->controllerName."@".$actionName, [$model->id], $modelAttributes);
210 | }
211 |
212 | public function testDestroyActionAllows()
213 | {
214 | $actionName = 'destroy';
215 |
216 | $model = $this->buildModel();
217 |
218 | $this->assertCan($actionName, $this->modelName);
219 |
220 | $this->action('DELETE', $this->controllerName."@".$actionName, [$model->id]);
221 | $this->assertRedirectedToRoute($this->resourceName.'.index');
222 | }
223 |
224 | /**
225 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
226 | */
227 | public function testDestroyActionDenies()
228 | {
229 | $actionName = 'destroy';
230 |
231 | $this->authority->deny($actionName, $this->modelName);
232 |
233 | $model = $this->buildModel();
234 |
235 | $this->assertCannot($actionName, $this->modelName);
236 |
237 | $this->action('DELETE', $this->controllerName."@".$actionName, [$model->id]);
238 | }
239 |
240 | protected function buildModel($modelAttributes = null)
241 | {
242 | $modelAttributes = $modelAttributes ?: $this->modelAttributes;
243 | $mock = $this->mock($this->modelName);
244 | $model = $this->fillMock($mock, $modelAttributes);
245 |
246 | $mock->shouldReceive('where->firstOrFail')->/*once()->*/andReturn($model);
247 | $mock->shouldReceive('save')->/*once()->*/andReturn(true);
248 | $models = collect();
249 | $models->push($model);
250 | $mock->shouldReceive('get')->andReturn($models);
251 | $mock->shouldReceive('all')->andReturn($models->all());
252 |
253 | return $model;
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/tests/AcTasksControllerTest.php:
--------------------------------------------------------------------------------
1 | parentControllerName = "AcProjectsController";
23 | $this->parentModelName = "AcProject";
24 | $this->parentModelAttributes = ['id' => 3, 'name' => 'Test AuthorityController package', 'priority' => 1];
25 | $this->parentResourceName = "ac_projects";
26 |
27 | $this->controllerName = "AcTasksController";
28 | $this->modelName = "AcTask";
29 | $this->modelAttributes = ['id' => 5, 'name' => 'Write more tests!', 'ac_project_id' => 3];
30 | $this->resourceName = "ac_projects.ac_tasks";
31 |
32 | Route::resource($this->parentResourceName, $this->parentControllerName);
33 | Route::resource($this->resourceName, $this->controllerName);
34 |
35 | $this->userAttributes = ['id' => 1, 'username' => 'tortue', 'firstname' => 'Tortue',
36 | 'lastname' => 'Torche', 'email' => 'tortue.torche@spam.me', 'password' => Hash::make('tortuetorche'),
37 | 'displayname' => 'Tortue Torche'];
38 |
39 | $this->user = $this->getUserWithRole('admin');
40 | $this->authority = $this->getAuthority($this->user);
41 |
42 | $this->authority->allow('manage', $this->parentModelName);
43 | $this->authority->allow('manage', $this->modelName);
44 |
45 | }
46 |
47 | public function testIndexActionAllows()
48 | {
49 | $actionName = 'index';
50 |
51 | $parentModel = $this->buildParentModel();
52 |
53 | $model = $this->buildModel();
54 |
55 | $this->assertCan($actionName, $this->modelName);
56 | $this->action('GET', $this->controllerName."@".$actionName, [$parentModel->id]);
57 |
58 | $this->assertViewHas('acTasks', $model->get());
59 | }
60 |
61 | /**
62 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
63 | */
64 | public function testIndexActionDenies()
65 | {
66 | $actionName = 'index';
67 |
68 | $this->authority->deny($actionName, $this->modelName);
69 |
70 | $parentModel = $this->buildParentModel();
71 |
72 | $model = $this->buildModel();
73 |
74 | $this->assertCannot($actionName, $this->modelName);
75 | $this->action('GET', $this->controllerName."@".$actionName, [$parentModel->id]);
76 | }
77 |
78 | public function testCreateActionAllows()
79 | {
80 | $actionName = 'create';
81 |
82 | $parentModel = $this->buildParentModel();
83 |
84 | $modelAttributes = array_except($this->modelAttributes, 'id');
85 | $model = $this->buildModel($modelAttributes);
86 |
87 | $this->assertCan($actionName, $this->modelName);
88 |
89 | $this->action('GET', $this->controllerName."@".$actionName, [$parentModel->id]);
90 |
91 | $this->assertResponseOk();
92 | }
93 |
94 | /**
95 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
96 | */
97 | public function testCreateActionDenies()
98 | {
99 | $actionName = 'create';
100 |
101 | $this->authority->deny($actionName, $this->modelName);
102 |
103 | $parentModel = $this->buildParentModel();
104 |
105 | $modelAttributes = array_except($this->modelAttributes, 'id');
106 | $model = $this->buildModel($modelAttributes);
107 |
108 | $this->assertCannot($actionName, $this->modelName);
109 |
110 | $this->action('GET', $this->controllerName."@".$actionName, [$parentModel->id]);
111 | }
112 |
113 | public function testStoreActionAllows()
114 | {
115 | $actionName = 'store';
116 |
117 | $parentModel = $this->buildParentModel();
118 |
119 | $modelAttributes = array_except($this->modelAttributes, 'id');
120 | $model = $this->buildModel($modelAttributes);
121 |
122 | $this->assertCan($actionName, $this->modelName);
123 |
124 | $this->action('POST', $this->controllerName."@".$actionName, [$parentModel->id], $modelAttributes);
125 | $this->assertRedirectedToRoute($this->resourceName.'.index', $parentModel->id);
126 | }
127 |
128 | /**
129 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
130 | */
131 | public function testStoreActionDenies()
132 | {
133 | $actionName = 'store';
134 |
135 | $this->authority->deny($actionName, $this->modelName);
136 |
137 | $parentModel = $this->buildParentModel();
138 |
139 | $modelAttributes = array_except($this->modelAttributes, 'id');
140 | $model = $this->buildModel($modelAttributes);
141 |
142 | $this->assertCannot($actionName, $this->modelName);
143 |
144 | $this->action('POST', $this->controllerName."@".$actionName, [$parentModel->id], $modelAttributes);
145 | }
146 |
147 | public function testShowActionAllows()
148 | {
149 | $actionName = 'show';
150 |
151 | $parentModel = $this->buildParentModel();
152 |
153 | $model = $this->buildModel();
154 |
155 | $this->assertCan($actionName, $this->modelName);
156 |
157 | $response = $this->action('GET', $this->controllerName."@".$actionName, [$parentModel->id, $model->id]);
158 | $this->assertViewHas('acTask');
159 | $view = $response->original;
160 | $this->assertEquals($view->acTask->ac_project_id, 3);
161 | $this->assertEquals($view->acTask->id, 5);
162 | }
163 |
164 | /**
165 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
166 | */
167 | public function testShowActionDenies()
168 | {
169 | $actionName = 'show';
170 |
171 | $this->authority->deny($actionName, $this->modelName);
172 |
173 | $parentModel = $this->buildParentModel();
174 |
175 | $model = $this->buildModel();
176 |
177 | $this->assertCannot($actionName, $this->modelName);
178 |
179 | $this->action('GET', $this->controllerName."@".$actionName, [$parentModel->id, $model->id]);
180 | }
181 |
182 | public function testEditActionAllows()
183 | {
184 | $actionName = 'edit';
185 |
186 | $parentModel = $this->buildParentModel();
187 |
188 | $model = $this->buildModel();
189 |
190 | $this->assertCan($actionName, $this->modelName);
191 |
192 | $response = $this->action('GET', $this->controllerName."@".$actionName, [$parentModel->id, $model->id]);
193 | $this->assertViewHas('acTask');
194 | $view = $response->original;
195 | $this->assertEquals($view->acTask->ac_project_id, 3);
196 | $this->assertEquals($view->acTask->id, 5);
197 | }
198 |
199 | /**
200 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
201 | */
202 | public function testEditActionDenies()
203 | {
204 | $actionName = 'edit';
205 |
206 | $this->authority->deny($actionName, $this->modelName);
207 |
208 | $parentModel = $this->buildParentModel();
209 |
210 | $model = $this->buildModel();
211 |
212 | $this->assertCannot($actionName, $this->modelName);
213 |
214 | $this->action('GET', $this->controllerName."@".$actionName, [$parentModel->id, $model->id]);
215 | }
216 |
217 | public function testUpdateActionAllows()
218 | {
219 | $actionName = 'update';
220 |
221 | $parentModel = $this->buildParentModel();
222 |
223 | $modelAttributes = $this->modelAttributes;
224 | $model = $this->buildModel($modelAttributes);
225 |
226 | $this->assertCan($actionName, $this->modelName);
227 |
228 | $this->action('PATCH', $this->controllerName."@".$actionName, [$parentModel->id, $model->id], $modelAttributes);
229 | $this->assertRedirectedToRoute($this->resourceName.'.show', [3, 5]);
230 | }
231 |
232 | /**
233 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
234 | */
235 | public function testUpdateActionDenies()
236 | {
237 | $actionName = 'update';
238 |
239 | $this->authority->deny($actionName, $this->modelName);
240 |
241 | $parentModel = $this->buildParentModel();
242 |
243 | $modelAttributes = $this->modelAttributes;
244 | $model = $this->buildModel($modelAttributes);
245 |
246 | $this->assertCannot($actionName, $this->modelName);
247 |
248 | $this->action('PATCH', $this->controllerName."@".$actionName, [$parentModel->id, $model->id], $modelAttributes);
249 | }
250 |
251 | public function testDestroyActionAllows()
252 | {
253 | $actionName = 'destroy';
254 |
255 | $parentModel = $this->buildParentModel();
256 |
257 | $model = $this->buildModel();
258 |
259 | $this->assertCan($actionName, $this->modelName);
260 |
261 | $this->action('DELETE', $this->controllerName."@".$actionName, [$parentModel->id, $model->id]);
262 | $this->assertRedirectedToRoute($this->resourceName.'.index', $parentModel->id);
263 | }
264 |
265 | /**
266 | * @expectedException Efficiently\AuthorityController\Exceptions\AccessDenied
267 | */
268 | public function testDestroyActionDenies()
269 | {
270 | $actionName = 'destroy';
271 |
272 | $this->authority->deny($actionName, $this->modelName);
273 |
274 | $parentModel = $this->buildParentModel();
275 |
276 | $model = $this->buildModel();
277 |
278 | $this->assertCannot($actionName, $this->modelName);
279 |
280 | $this->action('DELETE', $this->controllerName."@".$actionName, [$parentModel->id, $model->id]);
281 | }
282 |
283 | protected function buildParentModel($parentModelAttributes = null)
284 | {
285 | $parentModelAttributes = $parentModelAttributes ?: $this->parentModelAttributes;
286 | $parentMock = $this->mock($this->parentModelName);
287 | $parentModel = $this->fillMock($parentMock, $parentModelAttributes);
288 |
289 | $parentMock->shouldReceive('where->firstOrFail')->once()->andReturn($parentModel);
290 | $associationName = str_plural(camel_case($this->modelName));
291 | $parentMock->shouldReceive($associationName.'->getModel')->/*once()->*/andReturn(app($this->modelName));
292 |
293 | return $parentModel;
294 | }
295 |
296 | protected function buildModel($modelAttributes = null)
297 | {
298 | $modelAttributes = $modelAttributes ?: $this->modelAttributes;
299 | $mock = $this->mock($this->modelName);
300 | $model = $this->fillMock($mock, $modelAttributes);
301 |
302 | $mock->shouldReceive('where->firstOrFail')->/*once()->*/andReturn($model);
303 | $mock->shouldReceive('save')->/*once()->*/andReturn(true);
304 | $models = collect();
305 | $models->push($model);
306 | $mock->shouldReceive('get')->andReturn($models);
307 | $mock->shouldReceive('all')->andReturn($models->all());
308 |
309 | return $model;
310 | }
311 | }
312 |
--------------------------------------------------------------------------------
/tests/AcTestCase.php:
--------------------------------------------------------------------------------
1 | fillMock($mock, $attributes);
22 | }
23 |
24 | return $mock;
25 | });
26 |
27 | return $mock;
28 | }
29 |
30 | protected function fillMock($mock, $attributes = [])
31 | {
32 | $instance = $mock->makePartial();
33 | foreach ($attributes as $key => $value) {
34 | $instance->$key = $value;
35 | }
36 |
37 | return $instance;
38 | }
39 |
40 | protected function getPackageProviders($app)
41 | {
42 | return [
43 | Collective\Html\HtmlServiceProvider::class,
44 | Efficiently\AuthorityController\AuthorityControllerServiceProvider::class,
45 | ];
46 | }
47 |
48 | protected function getPackageAliases($app)
49 | {
50 | return [
51 | 'Input' => Illuminate\Support\Facades\Input::class,
52 | 'Form' => Collective\Html\FormFacade::class,
53 | 'HTML' => Collective\Html\HtmlFacade::class,
54 | 'Authority' => Efficiently\AuthorityController\Facades\Authority::class,
55 | 'Params' => Efficiently\AuthorityController\Facades\Params::class,
56 | ];
57 | }
58 |
59 | /**
60 | * Resolve application HTTP exception handler.
61 | *
62 | * @param \Illuminate\Foundation\Application $app
63 | *
64 | * @return void
65 | */
66 | protected function resolveApplicationExceptionHandler($app)
67 | {
68 | $app->singleton('Illuminate\Contracts\Debug\ExceptionHandler', 'AcExceptionsHandler');
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/tests/fixtures/AcProduct.php:
--------------------------------------------------------------------------------
1 | name = $value;
12 | }
13 |
14 | public function getName()
15 | {
16 | return $this->name;
17 | }
18 |
19 | public function isNamed()
20 | {
21 | return !! $this->name;
22 | }
23 |
24 | public function setPrice($value)
25 | {
26 | $this->price = $value;
27 | }
28 |
29 | public function getPrice()
30 | {
31 | return $this->price;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/fixtures/controllers/AcBaseController.php:
--------------------------------------------------------------------------------
1 | layout)) {
20 | $this->layout = view($this->layout);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/fixtures/controllers/AcCategoriesController.php:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/efficiently/authority-controller/ea9d1fc8817222202f9b0657ea294cb59eb9018d/tests/fixtures/controllers/AcCategoriesController.php
--------------------------------------------------------------------------------
/tests/fixtures/controllers/AcProjectsController.php:
--------------------------------------------------------------------------------
1 | acProjectModel = $acProjectModel ?: new AcProject;
30 | $this->loadAndAuthorizeResource();
31 | }
32 |
33 | /**
34 | * Display a listing of the resource.
35 | *
36 | * @return Response
37 | */
38 | public function index()
39 | {
40 | // $this->acProjects = $this->acProjectModel->get();
41 |
42 | return view('ac_projects.index', compact_property($this, 'acProjects'));
43 | }
44 |
45 | /**
46 | * Show the form for creating a new resource.
47 | *
48 | * @return Response
49 | */
50 | public function create()
51 | {
52 | return view('ac_projects.create');
53 | }
54 |
55 | /**
56 | * Store a newly created resource in storage.
57 | *
58 | * @return Response
59 | */
60 | public function store()
61 | {
62 | // $this->acProject = app('AcProject');
63 |
64 | $this->acProject->fill(request()->except('_method', '_token'));
65 | try {
66 | $this->acProject->save();
67 | return redirect()->route('ac_projects.index');
68 | } catch (Exception $e) {
69 | return redirect()->route('ac_projects.create')
70 | ->withErrors($this->acProject->errors())
71 | ->with('message', 'There were validation errors.');
72 | }
73 | }
74 |
75 | /**
76 | * Display the specified resource.
77 | *
78 | * @param int $id
79 | * @return Response
80 | */
81 | public function show($id)
82 | {
83 | // $this->acProject = $this->acProjectModel->findOrFail($id);
84 |
85 | return view('ac_projects.show', compact_property($this, 'acProject'));
86 | }
87 |
88 | /**
89 | * Show the form for editing the specified resource.
90 | *
91 | * @param int $id
92 | * @return Response
93 | */
94 | public function edit($id)
95 | {
96 | // $this->acProject = $this->acProjectModel->find($id);
97 |
98 | if (is_null($this->acProject)) {
99 | return redirect()->route('ac_projects.index');
100 | }
101 |
102 | return view('ac_projects.edit', compact_property($this, 'acProject'));
103 | }
104 |
105 | /**
106 | * Update the specified resource in storage.
107 | *
108 | * @param int $id
109 | * @return Response
110 | */
111 | public function update($id)
112 | {
113 | // $this->acProject = $this->acProjectModel->find($id);
114 |
115 | $this->acProject->fill(request()->except('_method', '_token'));
116 | try {
117 | $this->acProject->save();
118 | return redirect()->route('ac_projects.show', $id);
119 | } catch (Exception $e) {
120 | return redirect()->route('ac_projects.edit', $id)
121 | ->withErrors($this->acProject->errors())
122 | ->with('message', 'There were validation errors.');
123 | }
124 | }
125 |
126 | /**
127 | * Remove the specified resource from storage.
128 | *
129 | * @param int $id
130 | * @return Response
131 | */
132 | public function destroy($id)
133 | {
134 | // $this->acProjectModel->find($id)->delete();
135 |
136 | $this->acProject->delete();
137 |
138 | return redirect()->route('ac_projects.index');
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/tests/fixtures/controllers/AcTasksController.php:
--------------------------------------------------------------------------------
1 | acProjectModel = $acProjectModel;
44 | $this->acTaskModel = $acTaskModel;
45 | $this->loadAndAuthorizeResource('ac_project');
46 | $this->loadAndAuthorizeResource('ac_task', ['through' => 'ac_project']);
47 | }
48 |
49 | /**
50 | * Display a listing of the resource.
51 | *
52 | * @param int $acProjectId
53 | * @return Response
54 | */
55 | public function index($acProjectId)
56 | {
57 | // $this->acTasks = $this->acTaskModel->get();
58 |
59 | return view('ac_tasks.index', compact_property($this, 'acProject', 'acTasks'));
60 | }
61 |
62 | /**
63 | * Show the form for creating a new resource.
64 | *
65 | * @param int $acProjectId
66 | * @return Response
67 | */
68 | public function create($acProjectId)
69 | {
70 | return view('ac_tasks.create', compact_property($this, 'acProject'));
71 | }
72 |
73 | /**
74 | * Store a newly created resource in storage.
75 | *
76 | * @param int $acProjectId
77 | * @return Response
78 | */
79 | public function store($acProjectId)
80 | {
81 | // $this->acTask = app('AcTask');
82 |
83 | $this->acTask->fill(request()->except('_method', '_token'));
84 | $this->acTask->ac_project_id = $acProjectId;
85 | try {
86 | $this->acTask->save();
87 | return redirect()->route('ac_projects.ac_tasks.index', $acProjectId);
88 | } catch (Exception $e) {
89 | return redirect()->route('ac_projects.ac_tasks.create', $acProjectId)
90 | ->withErrors($this->acTask->errors())
91 | ->with('message', 'There were validation errors.');
92 | }
93 | }
94 |
95 | /**
96 | * Display the specified resource.
97 | *
98 | * @param int $acProjectId
99 | * @param int $id
100 | * @return Response
101 | */
102 | public function show($acProjectId, $id)
103 | {
104 | // $this->acTask = $this->acTaskModel->findOrFail($id);
105 |
106 | return view('ac_tasks.show', compact_property($this, 'acTask'));
107 | }
108 |
109 | /**
110 | * Show the form for editing the specified resource.
111 | *
112 | * @param int $acProjectId
113 | * @param int $id
114 | * @return Response
115 | */
116 | public function edit($acProjectId, $id)
117 | {
118 | // $this->acTask = $this->acTaskModel->find($id);
119 |
120 | if (is_null($this->acTask)) {
121 | return redirect()->route('ac_projects.ac_tasks.index', $acProjectId);
122 | }
123 |
124 | return view('ac_tasks.edit', compact_property($this, 'acTask'));
125 | }
126 |
127 | /**
128 | * Update the specified resource in storage.
129 | *
130 | * @param int $acProjectId
131 | * @param int $id
132 | * @return Response
133 | */
134 | public function update($acProjectId, $id)
135 | {
136 | // $this->acTask = $this->acTaskModel->find($id);
137 |
138 | $this->acTask->fill(request()->except('_method', '_token'));
139 | try {
140 | $this->acTask->save();
141 | return redirect()->route('ac_projects.ac_tasks.show', [$acProjectId, $id]);
142 | } catch (Exception $e) {
143 | return redirect()->route('ac_projects.ac_tasks.edit', $id)
144 | ->withErrors($this->acTask->errors())
145 | ->with('message', 'There were validation errors.');
146 | }
147 | }
148 |
149 | /**
150 | * Remove the specified resource from storage.
151 | *
152 | * @param int $acProjectId
153 | * @param int $id
154 | * @return Response
155 | */
156 | public function destroy($acProjectId, $id)
157 | {
158 | // $this->acTaskModel->find($id)->delete();
159 |
160 | $this->acTask->delete();
161 |
162 | return redirect()->route('ac_projects.ac_tasks.index', $acProjectId);
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/tests/fixtures/models/AcCategory.php:
--------------------------------------------------------------------------------
1 | 'required',
9 | 'priority' => 'required',//|unique:projects',
10 | ];
11 |
12 | protected $fillable = ['name', 'priority'];
13 |
14 | public function acUser()
15 | {
16 | return $this->belongsTo(AcUser::class);
17 | }
18 |
19 | public function acTasks()
20 | {
21 | return $this->hasMany(AcTask::class);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/fixtures/models/AcRole.php:
--------------------------------------------------------------------------------
1 | belongsTo(AcProject::class);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/fixtures/models/AcUser.php:
--------------------------------------------------------------------------------
1 | 'required|between:3,16|unique:users',
22 | 'email' => 'required|email|unique:users',
23 | 'password' => 'required|min:4|confirmed',
24 | 'password_confirmation' => 'required|min:4',
25 | ];
26 |
27 | /**
28 | * The attributes excluded from the model's JSON form.
29 | *
30 | * @var array
31 | */
32 | protected $hidden = ['password'];
33 |
34 | protected $fillable = ['username', 'firstname', 'lastname', 'email', 'displayname'];
35 |
36 | public function roles()
37 | {
38 | return $this->belongsToMany(AcRole::class)->withTimestamps();
39 | }
40 |
41 | public function permissions()
42 | {
43 | return $this->hasMany(AcPermission::class);
44 | }
45 |
46 | public function hasRole($key)
47 | {
48 | foreach ($this->roles as $role) {
49 | if ($role->name === $key) {
50 | return true;
51 | }
52 | }
53 | return false;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/fixtures/views/ac_projects/create.blade.php:
--------------------------------------------------------------------------------
1 |
{!! link_to_route('ac_projects.create', 'Add new project') !!}
4 | 5 | @if ($acProjects->count()) 6 |Name | 10 |Priotity | 11 |||
---|---|---|---|
{{ $acProject->name }} | 18 |{{ $acProject->priority }} | 19 |{!! link_to_route('ac_projects.edit', 'Edit', $acProject->id) !!} | 20 |21 | {!! Form::open(array('method' => 'DELETE', 'route' => ['ac_projects.destroy', $acProject->id])) !!} 22 | {!! Form::submit('Delete') !!} 23 | {!! Form::close() !!} 24 | | 25 |
{!! link_to_route('ac_projects.index', 'Return to all demandes') !!}
4 | 5 |Name | 9 |Priority | 10 |||
---|---|---|---|
{{ $acProject->name }} | 16 |{{ $acProject->priority }} | 17 |{!! link_to_route('ac_projects.edit', 'Edit', $acProject->id) !!} | 18 |19 | {!! Form::open(array('method' => 'DELETE', 'route' => ['ac_projects.destroy', $acProject->id])) !!} 20 | {!! Form::submit('Delete') !!} 21 | {!! Form::close() !!} 22 | | 23 |
{!! link_to_route('ac_projects.ac_tasks.create', 'Add new task', $acProject->id) !!}
4 | 5 | @if ($acTasks->count()) 6 |Name | 10 |||
---|---|---|
{{ $acTask->name }} | 17 |{!! link_to_route('ac_projects.ac_tasks.edit', 'Edit', [$acTask->ac_project_id, $acTask->id]) !!} | 18 |19 | {!! Form::open(['method' => 'DELETE', 'route' => ['ac_projects.ac_tasks.destroy', $acTask->ac_project_id, $acTask->id]]) !!} 20 | {!! Form::submit('Delete') !!} 21 | {!! Form::close() !!} 22 | | 23 |
{!! link_to_route('ac_projects.ac_tasks.index', 'Return to all issues', $acTask->ac_project_id) !!}
4 | 5 |Name | 9 |||
---|---|---|
{{ $acTask->name }} | 15 |{!! link_to_route('ac_projects.ac_tasks.edit', 'Edit', [$acTask->ac_project_id, $acTask->id]) !!} | 16 |17 | {!! Form::open(['method' => 'DELETE', 'route' => ['ac_projects.ac_tasks.destroy', $acTask->ac_project_id, $acTask->id]]) !!} 18 | {!! Form::submit('Delete') !!} 19 | {!! Form::close() !!} 20 | | 21 |