Multi-tenant in Laravel4 - laravel

I'm building a multi-tenant app, using the subdomain to separate the users.
e.g. .myapp.com
I want to give each tenant their own database too.
How can I detect the subdomain and set the database dynamically?
Also, the code below is from the official documentation and shows us how we can get the subdomain when setting up a route. But how do we pass the subdomain value to a controller function?
Route::group(array('domain' => '{account}.myapp.com'), function()
{
Route::get('user/{id}', function($account, $id)
{
//
});
});

The best way to achieve this would be in a before filter that you apply to the route group.
Route::group(['domain' => '{account}.myapp.com', 'before' => 'database.setup'], function()
{
// Your routes...
}
This before filters gets a $route parameter and a $request parameter given to it, so we can use $request to get the host.
Route::filter('database.setup', function($route, $request)
{
$account = $request->getHost();
}
You could then use the account to adjust the default database connection using Config::set in the filter. Perhaps you need to use the default connection first up to fetch the users database details.
$details = DB::details()->where('account', '=', $account)->first();
// Make sure you got some database details.
Config::set('database.connections.account', ['driver' => 'mysql', 'host' => $details->host, 'database' => $details->database, 'username' => $details->username, 'password' => $details->password]);
Config::set('database.connections.default', 'account');
During runtime you create a new database connection and then set the default connection to that newly created connection. Of course, you could leave the default as is and simply set the connection on all your models to account.
This should give you some ideas. Please note that none of this code was tested.
Also, each method on your controllers will receive the domain as the first parameter. So be sure to adjust for that if you're expecting other parameters.

Related

Is there an tutorial for how to set up an ldap server and after to connect it with a simple laravel app?

I set up the ldap using DigitalOcean tutorial and installed AdLdap2 package and if anybody can clarify to me why on login to the ldap server still try to login with default username and password, i can't explain properly the problem if it doesn't make sense to you, can you just put here some links which helped you to set up the server and connect a laravel project with ldap
I know I'm a little late but these are my notes from when I set it up for one of my apps.
Adldap2-Laravel (NoDatabaseProvider)
Installation
Install the Adldap2-laravel package:
$ composer require adldap2/adldap2-laravel
Create the auth scaffolding:
$ php artisan make:auth
Configuration
Define the following environment variables in your .env file:
# .env
LDAP_HOSTS=[192.168.1.10]
LDAP_PORT=[389]
LDAP_BASE_DN=[DC=contoso,DC=com]
LDAP_USERNAME=[ldap#contoso.com]
LDAP_PASSWORD=[password123]
Disable the default routes if only using LDAP authentication. In your /routes/web.php file, make the following changes for Auth::routes() :
# /routes/web.php
Auth::routes([
'reset' => false,
'verify' => false,
'register' => false,
])
In your /resources/views/auth/login.blade.php file, change the label for email address to Username, remove the #error('email) section from the corresponding text input and change the name on that text input to whatever LDAP attribute you are looking users up by (samaccountname or userprincipalname) Finished text input should look something similar to this:
# /resources/views/auth/login.blade.php
<input type="text" class="form-control" name="samaccountname" value="{{ old('samaccountname') }}" required autofocus>
Create the ldap.php and ldap_auth.php file by running the following two commands:
$ php artisan vendor:publish --provider "Adldap\Laravel\AdldapServiceProvider"
$ php artisan vendor:publish --provider "Adldap\Laravel\AdldapAuthServiceProvider"
Change the user driver to use ldap instead of eloquent under providers in /config/auth.php and comment out the model below it, like so:
# /config/auth.php
'providers' => [
'users' => [
'driver' => 'ldap', // <- was eloquent, changed to ldap
# 'model' => App\User::class, // <- commented out, not using anymore
In /config/ldap_auth.php:
Change the provider from DatabaseUserProvider to NoDatabaseUserProvider:
# /config/ldap_auth.php
'provider' => Adldap\Laravel\Auth\NoDatabaseProvider::class
Change the locate_users_by under ldap to whatever field you plan to authenticate users by (samaccountname or userprincipalname)
# /config/ldap_auth.php
'ldap' => [
'locate_users_by' => 'samaccountname',
'bind_users_by' => 'distinguishedname',
],
In your /app/Http/Controllers/Auth/LoginController.php file, you must add a username() function that returns the LDAP attribute you are authenticating users by:
# /app/Http/Controllers/Auth/LoginController.php
public function username()
{
return 'samaccountname';
}
In your /config/app.php file, it's helpful to add the alias for the Adldap Facade to keep from having to type it out every time you need to access it:
# /config/app.php
'aliases' => [
'App' => Illuminate\Support\Facades\App::class,
'Arr' => Illuminate\Support\Arr::class,
. // scroll
. // to
. // the
. // bottom
'View' => Illuminate\Support\Facades\View::class,
'Adldap' => Adldap\Laravel\Facades\Adldap::class // <- add this
],
Usage
After successfully authenticating, if you add use Auth; in your controller with the other use contracts in the beginning of the file, you can access the authenticated user's attributes like so:
# /app/Http/Controllers/HomeController
use Auth;
public function index()
{
$user = Auth::user();
$cn = $user->getCommonName(); // assigns currently authenticated user's Common Name
return view('home')->with('cn', $cn);
// using {{ $cn }} on the home's view would output the Common Name
}
If you want to query the LDAP server for all other users, you can add use Adldap; with the other use contracts and use the query builder:
# /app/Http/Controllers/HomeController
use Adldap;
public function index()
{
$users = Adldap::search()->where('objectclass', '=', 'person')->sortBy('cn', 'asc')->get();
return view('home', compact('users'))
// use a foreach loop on the view to iterate through users and attributes
}

How to use multiple method in single route in laravel

I want to use more than one method in a single route using laravel. I'm try this way but when i dd() it's show the plan string.
Route::get('/user',[
'uses' => 'AppController#user',
'as' => 'useraccess',
'roles'=> 'HomeController#useroles',
]);
When i dd() 'roles' option it's show the plan string like this.
"roles" => "HomeController#useroles"
my middleware check the role this way.
$actions=$request->route()->getAction();
$roles=isset($actions['roles'])? $actions['roles'] : null;
The simplest way to accept multiple HTTP methods in a single route is to use the match method, like so:
Route::match(['get', 'post'], '/user', [
'uses' => 'AppController#user',
'as' => 'useraccess',
'roles'=> 'HomeController#useroles',
]);
As for your middleware, to check the HTTP request type, a tidier way would be:
$method = request()->method();
And if you need to check for a specific method:
if (request()->isMethod('post')) {
// do stuff for post methods
}
Here's how you can do multiple methods on a single route:
Route::get('/route', 'RouteController#index');
Route::post('/route', 'RouteController#create');
Route::put('/route', 'RouteController#update');
/* Would be easier to use
* Route::put('/route/{route}', 'RouteController#update');
* Since Laravel gives you the Model of the primary key you've passed
* in to the route.
*/
Route::delete('/route', 'RouteController#destroy');
If you've written your own middleware, you can wrap the routes in a Route::group and apply your middleware to those routes, or individual routes respectively.
Route::middleware(['myMiddleware'])->group(function () {
Route::get('/route', 'RouteController#index');
Route::post('/route', 'RouteController#create');
Route::put('/route', 'RouteController#update');
});
Or
Route::group(['middleware' => 'myMiddleware'], function() {
Route::get('/route', 'RouteController#index');
Route::post('/route', 'RouteController#create');
Route::put('/route', 'RouteController#update');
});
Whichever is easier for you to read.
https://laravel.com/docs/5.6/routing#route-groups

Laravel sub-domain routing set global para for route() helper

I have setup sub-domain routing on my app (using Laravel 5.4) with the following web.php route:
Route::domain('{company}.myapp.localhost')->group(function () {
// Locations
Route::resource('/locations' , 'LocationController');
// Services
Route::resource('/services' , 'ServiceController');
});
However as my show and edit endpoints require an ID to be passed, using a normal route('services.show') helper results in an ErrorException stating Missing required parameters for [Route: services.create] [URI: services/create].
I appreciate this is necessary, but as the company is associated to the user on login (and is in the sub-domain) I don't want to be passing this for every view. I want to set this at a global level.
To avoid repeated queries, I thought about storing this in the session as so (in the :
protected function authenticated(Request $request, $user)
{
$current_company = $user->companies->first();
$company = [
'id' => $current_company->id,
'name' => $current_company->name,
'display_name' => $current_company->display_name
];
$request->session()->put('company', $company);
}
Which is fine, but I wonder if I can pass this to the route as a middleware or something. What's be best solution here?
Recommendation: remove the slash before the resource name.
The resource method will produce the following URIs:
/services/{service}
So, you should define your routes like this:
Route::domain('{company}.myapp.localhost')->group(function () {
// Locations
Route::resource('locations' , 'LocationController');
// Services
Route::resource('services' , 'ServiceController', ['only' => ['index', 'store']]);
Route::get('services');
});
I ran into this exact issue today, I poked around in the source and found a defaults method on the url generator that allows you to set global default route parameters like so:
app('url')->defaults(['yourGlobalRouteParameter' => $value]);
This will merge in whatever value(s) you specify into the global default parameters for the route url generator to use.

Laravel Conditional route filter

Hey guys could you please help me? This one is driving me crazy...
Let's say that I have a method for checking if the user is an admin or not:
public function isAdmin()
{
return Auth::user()->role === 'admin';
}
Then I attach it to a route filter:
Route::filter('admin', function($route, $request)
{
if ( ! Auth::user()->isAdmin()) {
Notification::error('No permission to view this page!');
return Redirect::back();
}
});
Now, I just pass it to the route group
Route::group(array('before' => 'admin'), function()
{
Route::post('users/{id}/update_password', 'UserController#update_password');
Route::post('users/{id}/delete', 'UserController#force_delete');
Route::delete('users/{id}', array('as' => 'users.destroy', 'uses' => 'UserController#destroy'));
Route::post('users/{id}/restore', 'UserController#restore');
Route::get('users/create', array('as' => 'users.create', 'uses' => 'UserController#create'));
Route::post('users', array('as' => 'users.store', 'uses' => 'UserController#store'));
Route::get('users/{id}/edit', array('as' => 'users.edit', 'uses' => 'UserController#edit'));
Route::put('users/{id}', array('as' => 'users.update', 'uses' => 'UserController#update'));
});
The question here is how do I allow a user to bypass this filter if for example he's trying to update it's own profile page an obviously he's not and admin?
I just want to block all access to the users routes for nonadmins but allow the user to edit/update etc on his own profile but allow the admin to do that too.
Could you please point me to the right direction?
You can get the related request segment to check it in your filter:
Route::filter('admin', function($route, $request)
{
if ( ! Auth::user()->isAdmin() && Auth::user()->username !== Request::segment(2)) {
Notification::error('No permission to view this page!');
return Redirect::back();
}
});
There are a few ways to do this, but having a filter that checks the request segments against the currently authenticated user isn't the best way.
Choice Number 1
You simply check that a user is auth'd (use the auth filter), and then in the controller itself you check whether or not the user is an admin, and/or it's their profile.
Choice Number 2
Define a secondary sets of routes specifically for a user modifying their own profile, that doesn't follow the /user/{id}/* pattern.
Route::group(['before' => 'admin'], function() {
// admin routes here
}
Route::group(['prefix' => '/me'], function() {
Route::post('/update_password', 'UserController#update_password');
Route::post('/delete', 'UserController#force_delete');
// etc
}
This would mean that to edit their own profile, they could simply go to /me/edit rather than /user/{id}/edit. To avoid issues like repeating the same code, or errors because an argument is missing, you could do something like this in your controller.
private function getUserOrMe($id)
{
return $id !== false ? User::find($id) : Auth::user();
}
public function edit($id = false)
{
$user = $this->getUserOrMe($id);
}
I recently used this particular method for an API. Sure it requires defining the routes again, but providing that you've set them up with groups that make use of the prefix option, it's a copy and paste job, plus, there are routes an admin would have that a user wouldn't.
Either way, filters weren't intended to do complex logic, but rather, to provide a level of base logic and protection for routes. Logic that identifies whether the current uri is that of the currently logged in user, is something better handled in a controller.

Laravel, Routing Wildcards to filter and then controller

I am trying to capture a wildcard from URL and then first pass it to a filter then route to controller. I am not sure how to plot the question exactly but here is what I've tried so far.
Route::get('test/(:any?)', array('as' => 'testroute', 'uses' => 'test#result', 'before' => "test_filter:$1"));
Route::filter('test_filter', function($id = NULL)
{
if($id)
echo "This id is " . $id; // Prints "This id is $1"
});
and
Route::get('test/(:any?)', array('as' => 'testroute', function($id = NULL)
{
if($id)
echo "this id is " . $id; // Does not do anything
}, 'uses' => 'test#result'));
Basically, I want to check if there is an id appended to the URL and set a cookie if there is one. But regardless of the case, I want this route to be handled by a controller no matter if there is any id appended or not.
I have to do the same thing with so many routes so I'd prefer something like a filter rather than modifying the controller's codes.
I know that I can directly pass the wildcard element to a closure, or I can feed this as a parameter to any controller but in that case I'll have to modify the controller codes, which I can't at the moment.
Can I do it through filters ? or any other way in which i wont have to modify the controller codes ?
Try passing an anonymous right after the before
Route::get('test/(:any?)',
array(
'as' => 'testroute',
'uses' => 'test#result',
'before' => "test_filter",
function($my_cookie_value)
{
// Set cookie here
}
)
);
Taken from here
I'd use a middleware http://laravel.com/docs/5.1/middleware (or filter for older Laravel versions) and add the route/s into a group which has the middleware as you can see in here http://laravel.com/docs/5.1/routing#route-group-middleware.
The middleware will be executed before the route code, where you can add a logic to manage your cookie.

Resources