├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── public └── .gitkeep ├── src ├── Mmanos │ └── Api │ │ ├── Api.php │ │ ├── ApiServiceProvider.php │ │ ├── Authentication.php │ │ ├── Authentication │ │ └── Client.php │ │ ├── ControllerTrait.php │ │ ├── Cors.php │ │ ├── Exceptions │ │ ├── Handler.php │ │ └── HttpException.php │ │ ├── InternalRequest.php │ │ └── Transformations.php ├── config │ └── api.php ├── lang │ └── .gitkeep ├── migrations │ └── 2015_05_30_000000_oauth_server.php └── views │ └── .gitkeep └── tests └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Mark Manos 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RESTful API package for Laravel 5 2 | 3 | This is an API package for the Laravel framework. It allows you to build a flexible RESTful API that can be consumed externally and by your own application. 4 | 5 | ## Installation 6 | 7 | #### Composer 8 | 9 | Add this to you composer.json file, in the require object: 10 | 11 | ```javascript 12 | "mmanos/laravel-api": "dev-master" 13 | ``` 14 | 15 | After that, run composer install to install the package. 16 | 17 | #### Service Provider 18 | 19 | Register the `Mmanos\Api\ApiServiceProvider` in your `app` configuration file. 20 | 21 | #### Class Alias 22 | 23 | Add a class alias to `app/config/app.php`, within the `aliases` array. 24 | 25 | ```php 26 | 'aliases' => array( 27 | // ... 28 | 'Api' => 'Mmanos\Api\Api', 29 | ) 30 | ``` 31 | 32 | ## Laravel 4 33 | 34 | Use the `1.0` branch or the `v1.*` tags for Laravel 4 support. 35 | 36 | ## Configuration 37 | 38 | #### Publish config files and migrations 39 | 40 | Publish the `lucadegasperi/oauth2-server-laravel` config file and migrations to your application. 41 | 42 | ```console 43 | $ php artisan vendor:publish --provider="LucaDegasperi\OAuth2Server\OAuth2ServerServiceProvider" 44 | ``` 45 | 46 | Edit the published config file to fit your authentication needs. See this [configuration options](https://github.com/lucadegasperi/oauth2-server-laravel/wiki/Configuration-Options) page for information. 47 | 48 | Publish the `mmanos/laravel-api` config file and migrations to your application. 49 | 50 | ```console 51 | $ php artisan vendor:publish --provider="Mmanos\Api\ApiServiceProvider" 52 | ``` 53 | 54 | And then run the migrations. 55 | 56 | ```console 57 | $ php artisan migrate 58 | ``` 59 | 60 | Add the following line to your `app/Http/Kernel.php` file in the `$middleware` array to catch any OAuth error and respond appropriately: 61 | 62 | ```php 63 | 'LucaDegasperi\OAuth2Server\Middleware\OAuthExceptionHandlerMiddleware' 64 | ``` 65 | 66 | In order to make some the authorization and resource server work correctly with Laravel 5, remove the `App\Http\Middleware\VerifyCsrfToken` line from the `$middleware` array and place it in the $`routeMiddleware` array like this: `'csrf' => 'App\Http\Middleware\VerifyCsrfToken',`. 67 | 68 | > **Note:** remember to add the csrf middleware manually on any route where it's appropriate. 69 | 70 | #### Handling Exceptions 71 | 72 | We need to modify the exception handler to properly format exceptions thrown by this package. Update the `App/Exceptions/Handler.php` file to use the exception handler from this package. 73 | 74 | ```php 75 | use Exception; 76 | use Mmanos\Api\Exceptions\Handler as ExceptionHandler; 77 | 78 | class Handler extends ExceptionHandler { 79 | ... 80 | } 81 | ``` 82 | 83 | Then add the `Mmanos\Api\Exceptions\HttpException` exception class to the `$dontReport` array so regular HTTP Exceptions are not reported. 84 | 85 | ## Controllers 86 | 87 | #### Configuration 88 | 89 | Add the `ControllerTrait` to each of your API controllers. You could optionally add this to a BaseController extended by all of your other controllers. 90 | 91 | ```php 92 | use Illuminate\Routing\Controller; 93 | use Mmanos\Api\ControllerTrait; 94 | 95 | class BaseController extends Controller 96 | { 97 | use ControllerTrait; 98 | } 99 | ``` 100 | 101 | #### Pagination 102 | 103 | If you return a pagination object from your controller action this package will add the following headers to the response: 104 | 105 | * Pagination-Page 106 | * Pagination-Num 107 | * Pagination-Total 108 | * Pagination-Last-Page 109 | 110 | #### Setting custom response headers 111 | 112 | You may access the response object and set any additional headers directly from your controller action: 113 | 114 | ```php 115 | $this->response()->header('Example-Header', 'Example value'); 116 | ``` 117 | 118 | #### Errors 119 | 120 | Dealing with errors when building your API is easy. Simply use the `Api::abort` method to throw an exception that will be formatted in a useful manner. 121 | 122 | Throw a 404 Not Found error: 123 | 124 | ```php 125 | Api::abort(404); 126 | ``` 127 | 128 | Or a 403 Access Denied error: 129 | 130 | ```php 131 | Api::abort(403); 132 | ``` 133 | 134 | Customize the error message: 135 | 136 | ```php 137 | Api::abort(403, 'Access denied to scope: users:write'); 138 | ``` 139 | 140 | Pass the errors from a validation object to get a clean response with all validation errors: 141 | 142 | ```php 143 | Api::abort(422, $validator->errors()); 144 | ``` 145 | 146 | #### Protecting your API endpoints 147 | 148 | You may use the `protect` route filter to ensure the request is authenticated: 149 | 150 | ```php 151 | $this->beforeFilter('protect'); 152 | ``` 153 | 154 | Or you may call the `Api::protect()` method directly. 155 | 156 | If this check fails, a call to `Api::abort(401)` is made resulting in an Unauthorized error response. 157 | 158 | #### Checking scope access 159 | 160 | Use the `checkscope` route filter to ensure the requested resource is accessible: 161 | 162 | ```php 163 | $this->beforeFilter('checkscope:users.write'); 164 | ``` 165 | 166 | Or you may call the `Api::checkScope('users:write')` method directly. 167 | 168 | If this check fails, a call to `Api::abort(403)` is made resulting in an Access Denied error response with the scope name. 169 | 170 | #### Transforming output 171 | 172 | Any model, collection, or pagination object returned by your controller action will be automatically sent through any bound transformer classes. 173 | 174 | ## Transformers 175 | 176 | Transformers allow you to easily and consistently transform objects into an array. By using a transformer you can type-cast integers, type-cast booleans, and nest relationships. 177 | 178 | #### Bind a class to a transformer 179 | 180 | ```php 181 | Api::bindTransformer('User', 'Transformers\User'); 182 | ``` 183 | 184 | #### Set a class property 185 | 186 | Alternatively, you could add a `transformer` property to your class to be auto-recognized by this package: 187 | 188 | ```php 189 | class User extends Eloquent 190 | { 191 | public $transformer = 'Transformers\User'; 192 | } 193 | ``` 194 | 195 | #### Creating a transformer class 196 | 197 | Ensure your transformer class has a `transform` static method: 198 | 199 | ```php 200 | namespace Transformers; 201 | 202 | class User 203 | { 204 | public function transform($object, $request) 205 | { 206 | $output = $object->toArray(); 207 | $output['id'] = (int) $output['id']; 208 | $output['created_at'] = $object->created_at->setTimezone('UTC')->toISO8601String(); 209 | $output['updated_at'] = $object->updated_at->setTimezone('UTC')->toISO8601String(); 210 | 211 | if ($request->input('hide_email')) { 212 | unset($output['email']); 213 | } 214 | 215 | return $output; 216 | } 217 | } 218 | ``` 219 | 220 | ## Internal Requests 221 | 222 | A big part of this package is being able to perform requests on your API internally. This allows you to build your application on top of a consumable API. 223 | 224 | #### Performing requests 225 | 226 | Use the `Api::internal()` method to initiate an internal request: 227 | 228 | ```php 229 | $users_array = Api::internal('api/users')->get(); 230 | ``` 231 | 232 | #### Passing extra parameters 233 | 234 | ```php 235 | $users_array = Api::internal('api/users', array('sort' => 'newest'))->get(); 236 | ``` 237 | 238 | #### Specify HTTP method 239 | 240 | ```php 241 | $new_user_array = Api::internal('api/users', array('email' => 'test@example.com'))->post(); 242 | ``` 243 | 244 | ## CORS Support 245 | 246 | CORS support is enabled by default, but only if the `Origin` header is detected. Adjust the settings in the config file to control the behavior and header values. 247 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mmanos/laravel-api", 3 | "description": "A RESTful API package for Laravel 5.", 4 | "keywords": ["laravel", "api", "oauth", "rest", "restful", "server"], 5 | "authors": [ 6 | { 7 | "name": "Mark Manos", 8 | "email": "mark@airpac.com" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=5.3.0", 13 | "illuminate/support": "~5.0", 14 | "lucadegasperi/oauth2-server-laravel": "4.0.x" 15 | }, 16 | "autoload": { 17 | "classmap": [ 18 | "src/migrations" 19 | ], 20 | "psr-0": { 21 | "Mmanos\\Api\\": "src/" 22 | } 23 | }, 24 | "minimum-stability": "dev", 25 | "license": "MIT" 26 | } 27 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmanos/laravel-api/8fac248d91c797f4a8f960e7cd7466df5a814975/public/.gitkeep -------------------------------------------------------------------------------- /src/Mmanos/Api/Api.php: -------------------------------------------------------------------------------- 1 | check()) { 30 | static::abort(401); 31 | } 32 | } 33 | 34 | /** 35 | * Ensure the current client has access to the requested scope. 36 | * 37 | * @param string $scope 38 | * 39 | * @return void 40 | */ 41 | public static function checkScope($scope) 42 | { 43 | if (!Authentication::instance()->checkScope($scope)) { 44 | static::abort(403, "Access denied to scope: $scope"); 45 | } 46 | } 47 | 48 | /** 49 | * Initialize an internal api request. 50 | * 51 | * @param string $uri 52 | * @param array $params 53 | * 54 | * @return InternalRequest 55 | */ 56 | public static function internal($uri, array $params = array()) 57 | { 58 | return new InternalRequest($uri, $params); 59 | } 60 | 61 | /** 62 | * Apply any available transformations to the given model and return the result. 63 | * 64 | * @param Model $model 65 | * @param Request $request 66 | * 67 | * @return mixed 68 | */ 69 | public static function transform($object, $request = null) 70 | { 71 | return Transformations::transform($object, $request ?: Request::instance()); 72 | } 73 | 74 | /** 75 | * Bind a class to a transformer. 76 | * 77 | * @param string $class 78 | * @param string $transformer 79 | * 80 | * @return void 81 | */ 82 | public static function bindTransformer($class, $transformer) 83 | { 84 | Transformations::bind($class, $transformer); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Mmanos/Api/ApiServiceProvider.php: -------------------------------------------------------------------------------- 1 | bootAuthResourceOwner(); 27 | $this->bootFilters(); 28 | } 29 | 30 | /** 31 | * Register the service provider. 32 | * 33 | * @return void 34 | */ 35 | public function register() 36 | { 37 | $this->app->register('LucaDegasperi\OAuth2Server\Storage\FluentStorageServiceProvider'); 38 | $this->app->register('LucaDegasperi\OAuth2Server\OAuth2ServerServiceProvider'); 39 | 40 | $config_path = __DIR__.'/../../config/api.php'; 41 | $this->mergeConfigFrom($config_path, 'api'); 42 | $this->publishes([ 43 | $config_path => config_path('api.php') 44 | ], 'config'); 45 | 46 | $m_from = __DIR__ . '/../../migrations/'; 47 | $m_to = $this->app['path.database'] . '/migrations/'; 48 | $this->publishes([ 49 | $m_from.'2015_05_30_000000_oauth_server.php' => $m_to.'2015_05_30_000000_oauth_server.php', 50 | ], 'migrations'); 51 | } 52 | 53 | /** 54 | * Get the services provided by the provider. 55 | * 56 | * @return array 57 | */ 58 | public function provides() 59 | { 60 | return array(); 61 | } 62 | 63 | /** 64 | * Make the current resource owner (access_token or Authorization header) 65 | * the current authenticated user in Laravel. 66 | * 67 | * @return void 68 | */ 69 | protected function bootAuthResourceOwner() 70 | { 71 | if (config('api.auth_resource_owner', true) 72 | && !Auth::check() 73 | && Request::input('access_token', Request::header('Authorization')) 74 | ) { 75 | if ($user_id = Authentication::instance()->userId()) { 76 | Auth::onceUsingId($user_id); 77 | } 78 | } 79 | } 80 | 81 | /** 82 | * Add route filters. 83 | * 84 | * @return void 85 | */ 86 | protected function bootFilters() 87 | { 88 | if (config('api.cors_enabled', true)) { 89 | $this->app['router']->before(function ($request) { 90 | if (Request::header('Origin') && $_SERVER['REQUEST_METHOD'] === 'OPTIONS') { 91 | $response = Response::make(null, 204); 92 | Cors::attachHeaders($response); 93 | Cors::attachOriginHeader($response, Request::header('Origin')); 94 | return $response; 95 | } 96 | }); 97 | 98 | $this->app['router']->after(function ($request, $response) { 99 | if (Request::header('Origin')) { 100 | Cors::attachHeaders($response); 101 | Cors::attachOriginHeader($response, Request::header('Origin')); 102 | } 103 | }); 104 | } 105 | 106 | $this->app['router']->filter('protect', function ($route, $request) { 107 | Api::protect(); 108 | }); 109 | 110 | $this->app['router']->filter('checkscope', function ($route, $request, $scope = '') { 111 | // B/c Laravel uses : as a special character already. 112 | $scope = str_replace('.', ':', $scope); 113 | 114 | Api::checkScope($scope); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Mmanos/Api/Authentication.php: -------------------------------------------------------------------------------- 1 | client_id)) { 40 | return $this->client_id ? $this->client_id : null; 41 | } 42 | 43 | if (!$this->check()) { 44 | return null; 45 | } 46 | 47 | if (Request::input('client_id')) { 48 | $this->client_id = Request::input('client_id'); 49 | } 50 | else if ($id = Authorizer::getClientId()) { 51 | $this->client_id = $id; 52 | } 53 | else { 54 | $this->client_id = false; 55 | } 56 | 57 | return $this->client_id ? $this->client_id : null; 58 | } 59 | 60 | /** 61 | * Return an array of details for the current client. 62 | * 63 | * @return array 64 | */ 65 | public function client() 66 | { 67 | if (isset($this->client)) { 68 | return $this->client ? $this->client : null; 69 | } 70 | 71 | $client_id = $this->clientId(); 72 | if (!$client_id) { 73 | $this->client = false; 74 | return null; 75 | } 76 | 77 | $client = $this->fetchClient($client_id); 78 | if ($client) { 79 | $this->client = $client; 80 | } 81 | else { 82 | $this->client = false; 83 | } 84 | 85 | return $this->client ? $this->client : null; 86 | } 87 | 88 | /** 89 | * Get the userID for the current request (access_token or Authorization header). 90 | * 91 | * @return int 92 | */ 93 | public function userId() 94 | { 95 | if (isset($this->user_id)) { 96 | return $this->user_id ? $this->user_id : null; 97 | } 98 | 99 | $this->check(); 100 | $this->user_id = false; 101 | 102 | if ('user' == $this->type) { 103 | if ($user_id = Authorizer::getResourceOwnerId()) { 104 | return $this->user_id = $user_id; 105 | } 106 | } 107 | 108 | return null; 109 | } 110 | 111 | /** 112 | * Return the authentication type: user, client. 113 | * 114 | * @return string 115 | */ 116 | public function type() 117 | { 118 | $this->check(); 119 | return $this->type; 120 | } 121 | 122 | /** 123 | * Validate authorization for the current request. 124 | * 125 | * @return bool 126 | */ 127 | public function check() 128 | { 129 | if (isset($this->check)) { 130 | return $this->check; 131 | } 132 | 133 | try { 134 | Authorizer::validateAccessToken(); 135 | $this->type = 'user'; 136 | return $this->check = true; 137 | } catch (Exception $e) {} 138 | 139 | $client_id = Request::input('client_id'); 140 | $client_secret = Request::input('client_secret'); 141 | if (!$client_id || !$client_secret) { 142 | return $this->check = false; 143 | } 144 | 145 | $client = $this->fetchClient($client_id, $client_secret); 146 | if (!$client) { 147 | return $this->check = false; 148 | } 149 | 150 | if (!in_array('client_id_secret', $client->grants())) { 151 | return $this->check = false; 152 | } 153 | 154 | $this->client = $client; 155 | $this->type = 'client'; 156 | return $this->check = true; 157 | } 158 | 159 | /** 160 | * Ensure the current authentication has access to the requested scope. 161 | * 162 | * @param string $scope 163 | * 164 | * @return bool 165 | */ 166 | public function checkScope($scope) 167 | { 168 | if (isset($this->scopes[$scope])) { 169 | return $this->scopes[$scope]; 170 | } 171 | 172 | if (!empty($this->scopes['*'])) { 173 | return true; 174 | } 175 | 176 | if (!$this->check()) { 177 | return false; 178 | } 179 | 180 | if ('user' == $this->type) { 181 | $this->scopes[$scope] = Authorizer::hasScope($scope); 182 | } 183 | else { 184 | $client = $this->client(); 185 | $this->scopes[$scope] = in_array($scope, $client->scopes()); 186 | } 187 | 188 | if (!$this->scopes[$scope] && '*' != $scope) { 189 | $this->scopes[$scope] = $this->checkScope('*'); 190 | } 191 | 192 | return $this->scopes[$scope]; 193 | } 194 | 195 | /** 196 | * Fetch a Authentication\Client instance for the requested client id. 197 | * 198 | * @param string $client_id 199 | * @param string $client_secret 200 | * 201 | * @return Authentication\Client 202 | */ 203 | public static function fetchClient($client_id, $client_secret = null) 204 | { 205 | $query = DB::table('oauth_clients')->where('id', $client_id); 206 | if (null !== $client_secret) { 207 | $query->where('secret', $client_secret); 208 | } 209 | $client = $query->first(); 210 | 211 | if (!$client) { 212 | return null; 213 | } 214 | 215 | return new Authentication\Client($client_id, (array) $client); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/Mmanos/Api/Authentication/Client.php: -------------------------------------------------------------------------------- 1 | id = $id; 24 | $this->data = $data; 25 | } 26 | 27 | /** 28 | * Return the allowed scopes for this client. 29 | * 30 | * @return array 31 | */ 32 | public function scopes() 33 | { 34 | if (isset($this->scopes)) { 35 | return $this->scopes; 36 | } 37 | 38 | return $this->scopes = DB::table('oauth_client_scopes') 39 | ->select('scope_id') 40 | ->where('client_id', $this->id) 41 | ->lists('scope_id'); 42 | } 43 | 44 | /** 45 | * Return the allowed endpoints for this client. 46 | * 47 | * @return array 48 | */ 49 | public function endpoints() 50 | { 51 | if (isset($this->endpoints)) { 52 | return $this->endpoints; 53 | } 54 | 55 | return $this->endpoints = DB::table('oauth_client_endpoints') 56 | ->select('redirect_uri') 57 | ->where('client_id', $this->id) 58 | ->lists('redirect_uri'); 59 | } 60 | 61 | /** 62 | * Return the allowed grants for this client. 63 | * 64 | * @return array 65 | */ 66 | public function grants() 67 | { 68 | if (isset($this->grants)) { 69 | return $this->grants; 70 | } 71 | 72 | return $this->grants = DB::table('oauth_client_grants') 73 | ->select('grant_id') 74 | ->where('client_id', $this->id) 75 | ->lists('grant_id'); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Mmanos/Api/ControllerTrait.php: -------------------------------------------------------------------------------- 1 | response)) { 21 | $this->response = Response::make(); 22 | } 23 | 24 | return $this->response; 25 | } 26 | 27 | /** 28 | * Execute an action on the controller. 29 | * Overridden to perform output transformations. 30 | * 31 | * @param string $method 32 | * @param array $parameters 33 | * @return \Symfony\Component\HttpFoundation\Response 34 | */ 35 | public function callAction($method, $parameters) 36 | { 37 | $response = $this->response(); 38 | $action_response = parent::callAction($method, $parameters); 39 | 40 | switch (true) { 41 | case $action_response instanceof Model: 42 | $response->setContent(Api::transform($action_response)); 43 | break; 44 | 45 | case $action_response instanceof Collection: 46 | $output = array(); 47 | foreach ($action_response as $model) { 48 | $output[] = Api::transform($model); 49 | } 50 | 51 | $response->setContent($output); 52 | break; 53 | 54 | case $action_response instanceof Paginator: 55 | $output = array(); 56 | foreach ($action_response->getCollection() as $model) { 57 | $output[] = Api::transform($model); 58 | } 59 | 60 | $response->setContent($output) 61 | ->header('Pagination-Page', $action_response->currentPage()) 62 | ->header('Pagination-Num', $action_response->perPage()) 63 | ->header('Pagination-Total', $action_response->total()) 64 | ->header('Pagination-Last-Page', $action_response->lastPage()); 65 | break; 66 | 67 | case $action_response instanceof Response: 68 | $response = $action_response; 69 | break; 70 | 71 | default: 72 | $response->setContent($action_response); 73 | } 74 | 75 | return $response; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Mmanos/Api/Cors.php: -------------------------------------------------------------------------------- 1 | headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); 21 | $response->headers->set('Access-Control-Allow-Headers', config('api.cors_allowed_headers', 'Origin, Content-Type, Accept, Authorization, X-Requested-With')); 22 | $response->headers->set('Access-Control-Allow-Credentials', 'true'); 23 | 24 | if ($exposed = config('api.cors_exposed_headers', 'Pagination-Page, Pagination-Num, Pagination-Total, Pagination-Last-Page')) { 25 | $response->headers->set('Access-Control-Expose-Headers', $exposed); 26 | } 27 | } 28 | 29 | /** 30 | * Attach a CORS origin header to the given response, if allowed. 31 | * Returns true if an origin header was set; false, otherwise. 32 | * 33 | * @param Response $response 34 | * @param string $origin 35 | * 36 | * @return bool 37 | */ 38 | public static function attachOriginHeader($response, $origin) 39 | { 40 | if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { 41 | $response->headers->set('Access-Control-Allow-Origin', $origin); 42 | return true; 43 | } 44 | 45 | if ('*' == config('api.cors_allowed_origin', 'client')) { 46 | $response->headers->set('Access-Control-Allow-Origin', '*'); 47 | return true; 48 | } 49 | 50 | if ('client' == config('api.cors_allowed_origin', 'client')) { 51 | $client = Authentication::instance()->client(); 52 | if (empty($client) || empty($client->endpoints())) { 53 | return false; 54 | } 55 | 56 | foreach ($client->endpoints() as $endpoint) { 57 | $parts = parse_url($endpoint); 58 | if (empty($parts['scheme']) || empty($parts['host'])) { 59 | continue; 60 | } 61 | 62 | $port = ''; 63 | if (array_get($parts, 'port')) { 64 | $port = ':' . array_get($parts, 'port'); 65 | } 66 | 67 | $url = $parts['scheme'] . '://' . $parts['host'] . $port; 68 | 69 | if ($origin == $url) { 70 | $response->headers->set('Access-Control-Allow-Origin', $url); 71 | return true; 72 | } 73 | } 74 | } 75 | 76 | return false; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Mmanos/Api/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | response(); 19 | 20 | if ($request->header('Origin')) { 21 | Cors::attachHeaders($response); 22 | $response->headers->set('Access-Control-Allow-Origin', $request->header('Origin')); 23 | } 24 | 25 | return $response; 26 | } 27 | 28 | return parent::render($request, $e); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Mmanos/Api/Exceptions/HttpException.php: -------------------------------------------------------------------------------- 1 | extra = $extra; 28 | } 29 | 30 | /** 31 | * Return the extra parameter. 32 | * 33 | * @return array 34 | */ 35 | public function getExtra() 36 | { 37 | return $this->extra; 38 | } 39 | 40 | /** 41 | * Return the response body array. 42 | * 43 | * @return array 44 | */ 45 | public function body() 46 | { 47 | $msg = $this->getMessage(); 48 | 49 | if (422 == $this->getCode() && !empty($msg)) { 50 | $this->extra['errors'] = json_decode($msg, true); 51 | $msg = null; 52 | } 53 | 54 | if (empty($msg)) { 55 | switch ($this->getCode()) { 56 | case 404: 57 | $msg = 'Not Found'; 58 | break; 59 | case 405: 60 | $msg = 'Method Not Allowed'; 61 | break; 62 | case 400: 63 | $msg = 'Bad Request'; 64 | break; 65 | case 401: 66 | $msg = 'Unauthorized'; 67 | break; 68 | case 402: 69 | $msg = 'Payment Required'; 70 | break; 71 | case 403: 72 | $msg = 'Forbidden'; 73 | break; 74 | case 422: 75 | $msg = 'Unprocessable Entity'; 76 | break; 77 | case 410: 78 | $msg = 'Resource Deleted'; 79 | break; 80 | case 500: 81 | $msg = 'Server Error'; 82 | break; 83 | default: 84 | $msg = 'Error'; 85 | } 86 | } 87 | 88 | $output = array( 89 | 'status' => $this->getCode(), 90 | 'message' => $msg, 91 | ); 92 | 93 | if (!empty($this->getExtra())) { 94 | $output = array_merge($output, $this->getExtra()); 95 | } 96 | 97 | return $output; 98 | } 99 | 100 | /** 101 | * Return the response object. 102 | * 103 | * @return Response 104 | */ 105 | public function response() 106 | { 107 | return Response::json($this->body(), $this->getCode()); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Mmanos/Api/InternalRequest.php: -------------------------------------------------------------------------------- 1 | uri = $uri; 22 | $this->params = $params; 23 | } 24 | 25 | /** 26 | * Dispatch a GET request. 27 | * 28 | * @return mixed 29 | */ 30 | public function get() 31 | { 32 | return $this->dispatch('get'); 33 | } 34 | 35 | /** 36 | * Dispatch a POST request. 37 | * 38 | * @return mixed 39 | */ 40 | public function post() 41 | { 42 | return $this->dispatch('post'); 43 | } 44 | 45 | /** 46 | * Dispatch a PUT request. 47 | * 48 | * @return mixed 49 | */ 50 | public function put() 51 | { 52 | return $this->dispatch('put'); 53 | } 54 | 55 | /** 56 | * Dispatch a DELETE request. 57 | * 58 | * @return mixed 59 | */ 60 | public function delete() 61 | { 62 | return $this->dispatch('delete'); 63 | } 64 | 65 | /** 66 | * Dispatch this request. 67 | * 68 | * @param string $method 69 | * 70 | * @return mixed 71 | */ 72 | public function dispatch($method) 73 | { 74 | // Save original input. 75 | $original_input = Request::input(); 76 | 77 | // Create request. 78 | $request = Request::create($this->uri, $method, $this->params); 79 | 80 | // Replace input (maintain api auth parameters). 81 | Request::replace(array_merge($request->input(), array( 82 | 'client_id' => Request::input('client_id'), 83 | 'client_secret' => Request::input('client_secret'), 84 | 'access_token' => Request::input('access_token'), 85 | ))); 86 | 87 | // Dispatch request. 88 | $response = Route::dispatch($request); 89 | 90 | // Restore original input. 91 | Request::replace($original_input); 92 | 93 | $content = $response->getContent(); 94 | return empty($content) ? null : json_decode($response->getContent(), true); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Mmanos/Api/Transformations.php: -------------------------------------------------------------------------------- 1 | transformer)) { 36 | return new $object->transformer; 37 | } 38 | 39 | return null; 40 | } 41 | 42 | /** 43 | * Transform the given object. 44 | * 45 | * @param object $object 46 | * @param Request $request 47 | * 48 | * @return mixed 49 | */ 50 | public static function transform($object, $request) 51 | { 52 | $transformer = static::getTransformer($object); 53 | if (!$transformer) { 54 | return $object; 55 | } 56 | 57 | return $transformer->transform($object, $request); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/config/api.php: -------------------------------------------------------------------------------- 1 | true, 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Enable CORS support. 20 | |-------------------------------------------------------------------------- 21 | | 22 | | Attaches CORS headers to the outgoing response object, if true. 23 | | 24 | */ 25 | 26 | 'cors_enabled' => true, 27 | 28 | /* 29 | |-------------------------------------------------------------------------- 30 | | Allowed CORS origin. 31 | |-------------------------------------------------------------------------- 32 | | 33 | | Determines what to use for the allowed origin CORS header: 34 | | - client : Will fetch the allowed list endpoints from the 35 | | oauth_client_endpoints table 36 | | - * : Will allow all endpoints 37 | | 38 | */ 39 | 40 | 'cors_allowed_origin' => 'client', 41 | 42 | /* 43 | |-------------------------------------------------------------------------- 44 | | Allowed incoming CORS headers. 45 | |-------------------------------------------------------------------------- 46 | | 47 | | CSV of allowed incoming headers for CORS requests. 48 | | 49 | */ 50 | 51 | 'cors_allowed_headers' => 'Origin, Content-Type, Accept, Authorization, X-Requested-With', 52 | 53 | /* 54 | |-------------------------------------------------------------------------- 55 | | Exposed CORS outgoing headers 56 | |-------------------------------------------------------------------------- 57 | | 58 | | CSV of headers to allow through in the response of a CORS request. 59 | | 60 | */ 61 | 62 | 'cors_exposed_headers' => 'Pagination-Page, Pagination-Num, Pagination-Total, Pagination-Last-Page', 63 | 64 | ); 65 | -------------------------------------------------------------------------------- /src/lang/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmanos/laravel-api/8fac248d91c797f4a8f960e7cd7466df5a814975/src/lang/.gitkeep -------------------------------------------------------------------------------- /src/migrations/2015_05_30_000000_oauth_server.php: -------------------------------------------------------------------------------- 1 | insert(array( 16 | 'id' => 'password', 17 | 'created_at' => date('Y-m-d H:i:s'), 18 | 'updated_at' => date('Y-m-d H:i:s'), 19 | )); 20 | DB::table('oauth_grants')->insert(array( 21 | 'id' => 'refresh_token', 22 | 'created_at' => date('Y-m-d H:i:s'), 23 | 'updated_at' => date('Y-m-d H:i:s'), 24 | )); 25 | DB::table('oauth_grants')->insert(array( 26 | 'id' => 'authorization_code', 27 | 'created_at' => date('Y-m-d H:i:s'), 28 | 'updated_at' => date('Y-m-d H:i:s'), 29 | )); 30 | DB::table('oauth_grants')->insert(array( 31 | 'id' => 'client_credentials', 32 | 'created_at' => date('Y-m-d H:i:s'), 33 | 'updated_at' => date('Y-m-d H:i:s'), 34 | )); 35 | DB::table('oauth_grants')->insert(array( 36 | 'id' => 'client_id_secret', 37 | 'created_at' => date('Y-m-d H:i:s'), 38 | 'updated_at' => date('Y-m-d H:i:s'), 39 | )); 40 | 41 | // Scopes. 42 | DB::table('oauth_scopes')->insert(array( 43 | 'id' => '*', 44 | 'description' => 'Allow access to all scopes.', 45 | 'created_at' => date('Y-m-d H:i:s'), 46 | 'updated_at' => date('Y-m-d H:i:s'), 47 | )); 48 | } 49 | 50 | /** 51 | * Reverse the migrations. 52 | * 53 | * @return void 54 | */ 55 | public function down() 56 | { 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmanos/laravel-api/8fac248d91c797f4a8f960e7cd7466df5a814975/src/views/.gitkeep -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmanos/laravel-api/8fac248d91c797f4a8f960e7cd7466df5a814975/tests/.gitkeep --------------------------------------------------------------------------------