In order to learn API and afterward AngularJS, I thought I could create a blog's backend using laravel (which I never used before) and AngularJS for the frontend. The backend I want is a restful API allowing to read the blog if you're not authenticated and to CRUD if you are.
1/ I've been able to set up my database with my tables :
USER PAGES POSTS CATEGORIES
---- ---- ---- ----
id id id id
name name name name
password content content
mail category
role author
createdAt
updatedAt
2/ Controllers, Models an Routes seems fine :
UserController.php :
<?php
class UserController extends \BaseController {
public function index($id = null) {}
public function store($id = null) {}
public function update($id) {}
public function destroy($id) {}
}
User.php :
use Illuminate\Auth\UserTrait;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
class User extends Eloquent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
public $timestamps = false;
protected $table = 'users';
protected $hidden = array('password', 'remember_token');
public function posts() {
return $this->has_many('Post');
}
}
routes.php :
[...]
Route::get('/authtest', array('before' => 'auth.basic', function() {
return View::make('hello');
}));
Route::post('User', [
'as' => 'User/store',
'uses' => 'UserController#index']);
Route::get('User', [
'as' => 'User/index',
'uses' => 'UserController#store']);
Route::get('User/{id}', [
'as' => 'User/index',
'uses' => 'UserController#store']);
Route::put('User', [
'as' => 'User/update',
'uses' => 'UserController#update']);
Route::delete('User/{id}', [
'as' => 'User/destroy',
'uses' => 'UserController#destroy']);
[...]
3/ The thing is :
Supposing I want to login as admin:admin to the API to add a post. How can I do that ?
How can a user send credentials to my API?
Using curl I can type : curl --user user:password testserver/authtest which works with the auth.basic route parameter. But how do I do that in JS or some other language ? And how can I store this information so the user does not have to send its password everytime ?
here is a link to an implementation of sending basic auth using Angular:
http://wemadeyoulook.at/en/blog/implementing-basic-http-authentication-http-requests-angular/
As for the username and password, you can try storing them in an encrypted cookie - although I really don't recommend doing it this way. I'd rather use API keys.
Here is a library I made in Laravel to use API keys and is currently being implemented in multiple Angular projects:
https://github.com/chrisbjr/api-guard
Related
I'm having some trouble trying to achieve multiple connection to database in some clean way.
Keep in mind that this is my first symfony project ever, and i'm only a young developer.
In my project, the goal is to be able to select a client, with a specific database, and to connect to the database to be able to export some datas.
I tried to do the solution describe in this post Symfony 3 connection to multiple databases and i tried to generate dynamically an entityManager.
So i created a factory EntityManagerFactory :
Factory\EntityManagerFactory
<?php
namespace App\Factory;
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Yaml\Yaml;
class EntityManagerFactory {
private $config_db_group;
public function __construct(string $config_db_group) {
$this->config_db_group = $config_db_group;
}
public function createManager($idDb) {
$isDevMode = false;
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__ . "/src"), $isDevMode);
$connectionConfig = $this->getConfigDb($idDb);
$dbParams = [
'driver' => 'pdo_mysql',
'host' => $connectionConfig['host'],
'username' => $connectionConfig['user'],
'password' => $connectionConfig['password'],
'dbname' => $connectionConfig['db_name']
];
return EntityManager::create($dbParams, $config);
}
private function getConfigDb($idDb) {
$connectionConfig = Yaml::parseFile("$this->config_db_group");
return $connectionConfig[$idDb];
}
}
I have a yaml that describes the connection config :
config\dbgroup.yaml
1:
db_name: "db_name1"
host: "host1"
user: "user1"
password: "password1"
port: "3306"
2:
db_name: "db_name2"
host: "host2"
user: "user2"
password: "password2"
port: "3306"
In my config\services.yaml, i did something that was described in the post.
# Create a service for the factory
App\Factory\EntityManagerFactory:
arguments:
$config_db_group: '%kernel.project_dir%\config\db_group.yaml'
# Use the factory service as the first argument of the 'factory' option
# and the factory method as the second argument
App\Factory\EntityManager:
factory: ['#App\Factory\EntityManagerFactory', 'getManager']
I don't really understand what this is, i think this defines my factory as a service ? ...
And then i try to create an entityManager in my controller, this was just to test if it works, i think database request should be in a Repository, or a Services ?
<?php
namespace App\Controller;
use Twig\Environment;
use App\Factory\EntityManagerFactory;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Repository\Istrator\DatabaseGroupRepository;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class DashboardController extends AbstractController {
private $twig;
private $databaseGroupRepository;
private $factory;
public function __construct(Environment $twig, DatabaseGroupRepository $databaseGroupRepository, EntityManagerFactory $factory) {
$this->twig = $twig;
$this->databaseGroupRepository = $databaseGroupRepository;
$this->factory = $factory;
}
#[Route('{slug}/dashboard', name: 'app_dashboard')]
public function index(string $slug): Response {
// I get the specific database
$databaseGroup = $this->databaseGroupRepository->findBySlug($slug);
// Then i try to create an entityManager with the correct config
$entityManager = $this->factory->createManager($databaseGroup->getIdDb());
// Then just to try my connection, i create a basic query
$rsm = new ResultSetMapping();
$test = $entityManager->createNativeQuery("USE mydatabase; SELECT * FROM mytable", $rsm)->execute();
return new Response($this->twig->render('dashboard/dashboard.html.twig', [
'controller_name' => 'DashboardController',
]));
}
}
For now it doesn't work.. But i have several questions :
The databases i try to connect are not database with the same database schemes than my actual database. They are external database. Should i create Entities, and repository to manage them ? Or should i just do some connection, some request without entity and repository ?
In the stackoverflow post that i based my code on, there is a second way of doing it, by defining all future connection in a doctrine.yaml. I have a defined number of connection but like 50 or something, should i do this instead of creating entityManager dynamically ?
As you can see, i'm a bit confused right now but if someone could tell me their point of vue, it would be great.
If you need any other information, just tell me !
Thanks in advance
EDIT :
I found the solution, and it was really stupid :
In my EntityManagerFactory, i did this :
$dbParams = [
'driver' => 'pdo_mysql',
'host' => $connectionConfig['host'],
// IT'S NOT USERNAME, IT'S USER ....
'username' => $connectionConfig['user'],
'password' => $connectionConfig['password'],
'dbname' => $connectionConfig['db_name']
];
In the StackOverflow post that is used to create this factory, it was written username, but the correct field was user.
That was my first mistake, my second mistake is that, when i tried to execute my nativeQuery, I created a resultSetMapping empty :
$entityManager = $this->factory->createManager($databaseGroup->getIdDb());
// I did this
$rsm = new ResultSetMapping();
$test = $entityManager->createNativeQuery("USE mydatabase; SELECT * FROM mytable", $rsm)->execute();
// I SHOULD HAVE DONE THIS
$rsm = new ResultSetMappingBuilder($entityManager);
$rsm->addScalarResult('id', 'id');
[... for every field]
$result = $entityManager->createNativeQuery("SELECT id, prenom, nom FROM mytable",$rsm)->execute();
I use addScalarResult because what i get from those databases are not Entities I will keep in my program.
I hope if someone get stuck like me, this could help him/her/etc..
By default, the authorization plugin is apply to a global scope. For some controllers that I did not want to apply any authorization. I have to use the skipAuthorization config manually for each action. For authentication plugin, I can just only load the authentication component for each controller that requires authentication. However, the authorization middleware seems will always work even if I did not load the authorization component in the controller. So, why is that? And is there a way I can disable the authorization process for the entire controller?
You probably mean Authentication and not Authorization. In any case, from the Docs:
// in src/Controller/AppController.php
public function initialize()
{
parent::initialize();
$this->loadComponent('Authentication.Authentication');
}
By default the component will require an authenticated user for all
actions. You can disable this behavior in specific controllers using
allowUnauthenticated():
// in a controller beforeFilter or initialize // Make view and index not require a logged in user.
$this->Authentication->allowUnauthenticated(['view', 'index']);
More information: The Authentication plugin in the Cake Book.
I think you are not doing it in the right way. For authorization, you have to write a request policy. Whenever you bake controller just add --prefix Admin or whatever you want to.
cake bake controller Users --prefix Admin
Put all admin controllers in one place.
Add routes in your routes file
$builder->prefix('Admin',['_namePrefix' => 'admin:'], function (RouteBuilder $builder) {
$builder->connect('/', ['controller' => 'Users', 'action' => 'Index']);
$builder->fallbacks(DashedRoute::class);
});
`
Request Policy. Create a role table and add column role_id in the Users table and the rest you will understand with code below.
<?php
namespace App\Policy;
use Authorization\IdentityInterface;
use Authorization\Policy\RequestPolicyInterface;
use Cake\Http\ServerRequest;
class RequestPolicy implements RequestPolicyInterface
{
/**
* Method to check if the request can be accessed
*
* #param IdentityInterface|null Identity
* #param ServerRequest $request Server Request
* #return bool
*/
public function canAccess($identity, ServerRequest $request)
{
$role = 0;
if(!empty($identity)){
$data = $identity->getOriginalData();
$role = $data['role_id'];
}
if(!empty($request->getParam('prefix'))){
switch($request->getParam('prefix')){
case 'User' : return (bool)($role === 3);
case 'Admin': return (bool)($role === 1) || (bool)($role === 2);
}
}else{
return true;
}
return false;
}
}
`
and then implements AuthorizationServiceProviderInterface to the Application
use App\Policy\RequestPolicy;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\AuthorizationService;
use Authorization\Policy\MapResolver;
use Cake\Http\ServerRequest;
use Psr\Http\Message\ServerRequestInterface;
class Application extends BaseApplication implements AuthorizationServiceProviderInterface{
public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
{
$mapResolver = new MapResolver();
$mapResolver->map(ServerRequest::class, RequestPolicy::class);
return new AuthorizationService($mapResolver);
}
}
I can't store name and IP address to DB. I created a table 'info' with appropriate fields by running php artisan migrate.
A schema
Schema::create('info', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('ip');
$table->timestamp('created_at')->nullable();
});
A model for Info
class Info extends Model
{
protected $fillable = ['ip', 'name'];
}
Maybe the problem is in my HomeController where I get those variables?
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Info;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Request;
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function store(Request $request) {
Info::create(['info' => $request->input('info')]);
}
public function index()
{
if (Auth::check())
{
$name = Auth::user()->name;
$ip = Request::ip();
\App\Events\eventIp::dispatch($ip,$name);
return view('home');
}
}
}
My routes in web.php
Route::post('/home','HomeController#store');
Route::get('/home', 'HomeController#index')->name('home');
});
But it doesn't work. Gives no errors and no records in DB.
Something make me think that it have to do with my index function. I got info in function index and maybe function store doesn't have a clue what I mean.
A controller action is basically a method that usually gets executed when you open an url (as you connect them to routes).
In your example you have connected two routes to their respective actions:
Route::post('/home','HomeController#store');
Route::get('/home', 'HomeController#index')->name('home');
Now, when you log in succesfully, imagine that you end up in the page with url http://localhost:8000/home in your web browser.
The key difference is the method which you use to call your route (you can get an overview of the differences here), in your case you are using GET method.
The resulting action executed it the one associated to /home route with the GET method, that is the HomeController#index action (or method).
The store method, although is in the same HomeController class, doesn't get triggered unless you execute the /home route, but with the POST method.
You can confirm that if you put a debug message in each of the methods like this:
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function store(Request $request)
{
echo 'I will not be executed';
}
public function index()
{
echo 'I have been executed';
}
}
If you want to simply save a info record when you visit the /home route with the GET method, you can put the save in the index method itself and get rid of the store method:
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
// You can omit Auth::check() because you are using the auth middleware
// that exactly does this job.
Info::create([
'name' => Auth::user()->name,
'ip' => Request::ip(),
]);
return view('home');
}
}
Keep in mind that doing in this way you will get a new database record for each page view you make to that route (if you keep refreshing the page, you should see new records being added to database).
Update
When you use Eloquent Models, laravel will look for a table named after the pluralized model name (Info model will try to use infos table).
However you created a table named info. To solve that you can either rename the table and rerun the migration with php artisan migrate:refresh (it will delete all the existing data in the database you are using for your laravel app)
Or specify the table name to use for that laravel model:
class Info extends Model
{
protected $table = 'info';
protected $fillable = ['ip', 'name'];
}
How are you calling the functions? There is a couple of things wrong with your code, but you're saying there are no errors at all.
Firstly, your Info::create call does not need the ['info' => $request->input('info')] info. This is because your Info model has no database property called info, but normally you would get an obvious error with the approach, which is why I expect you are also calling the store method incorrectly.
Call the create method like so:
$infoModel = Info::create(['name' => $request->input('name'), 'ip' => $request->input['ip']]);
or, if you can guarantee your $request only contains the needed fields (properly validated), you can just do
$infoModel = Info::create($request->all());
Add a little more info to the question on how you are calling store and we can probably solve the rest of your problem.
Within your store function inside HomeController , use
Info::create([
'name' => Auth::user()->name,
'ip' => Request::ip(),
]);
and make sure Info model is imported.
Also make sure your route has the call to store function while POSTing data .
I am trying to use the Laravel inbuilt password reset in my app where Laravel 5.1 acts as the backend api and Angular 1.3 for all front-end views. I have set-up the Password reset as per the docs where I have done the following:
1) Create the table
php artisan migrate
2) Added this to the route:
Route::post('password/email', 'Auth/PasswordController#postEmail');
Route::post('password/reset', 'Auth/PasswordController#postReset');
Since I will be using Angular to display frontend forms, I did not add the views for GET. I havent done any changes to the Auth/PasswordController.php and right now its just like the way it came. But when I test the above URL from Postman POST request, I am getting the error:
View [emails.password] not found.
How can I let Angular Handle the views and not have Laravel worry about the view? Do I have to have Laravel View for the inbuilt password reset to work? How do I approach this?
Override the postEmail and postReset methods so that they return a JSON response (don't let it redirect). Subsequently post to /password/email and /password/reset from Angular via xhr.
Open app/Http/Controllers/Auth/PasswordController.php
<?php namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
class PasswordController extends Controller
{
use ResetsPasswords;
//add and modify this methods as you wish:
/**
* Send a reset link to the given user.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function postEmail(Request $request)
{
$this->validate($request, ['email' => 'required|email']);
$response = Password::sendResetLink($request->only('email'), function (Message $message) {
$message->subject($this->getEmailSubject());
});
switch ($response) {
case Password::RESET_LINK_SENT:
return redirect()->back()->with('status', trans($response));
case Password::INVALID_USER:
return redirect()->back()->withErrors(['email' => trans($response)]);
}
}
/**
* Reset the given user's password.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function postReset(Request $request)
{
$this->validate($request, [
'token' => 'required',
'email' => 'required|email',
'password' => 'required|confirmed',
]);
$credentials = $request->only(
'email', 'password', 'password_confirmation', 'token'
);
$response = Password::reset($credentials, function ($user, $password) {
$this->resetPassword($user, $password);
});
switch ($response) {
case Password::PASSWORD_RESET:
return redirect($this->redirectPath());
default:
return redirect()->back()
->withInput($request->only('email'))
->withErrors(['email' => trans($response)]);
}
}
}
Ceckout your path to views folder in app\bootstrap\cache\config.php at section "view"
'view' =>
array (
'paths' =>
array (
0 => '/home/vagrant/Code/app/resources/views',
),
'compiled' => '/home/vagrant/Code/app/storage/framework/views',
),
this path MUST be at SERVER! not at you local mashine like
"D:\WebServers\home\Laravel\app\bootstrap\cache", if you use the homestead.
And You must use command like: "php artisan config:clear | cache" at SERVER!
I had the same problem than you. You could manage to change the view in config/auth.php if you have another one not in resources/views/emails/password.blade.php.
Because this view isn't created by default, that's why you got the error.
I'm trying to speed up my site by taking advantage of the new HTTP cache features in CakePHP 2.1:
class ArticlesController extends AppController {
public function view($id) {
$article = $this->Article->find(
'first',
array('conditions' => array('Article.id' => $id))
);
$this->response->modified($article['Article']['modified']);
$this->set(compact('article'));
}
}
Caching works fine, but does not distinguish between different users (i.e. if a user logs in and visits a page that was already cached, the previously cached page is displayed, and user-specific content is not shown). I'd like one of the following to happen:
Cache discriminates between different users and stores a separate cache for each user
Caching is disabled if a user is logged in (the user login is only used for admin purposes)
I've tried adding
if (AuthComponent::user('id')) {
$this->disableCache();
}
But this doesn't seem to solve the problem
Does anyone know how to get this to work, or am I doing something fundamentally wrong?
You could try the etag caching method and generate a hash based on the article id and user id.
See http://book.cakephp.org/2.0/en/controllers/request-response.html#the-etag-header
The Etag header (called entity tag) is string that uniquely identifies the requested resource. It is very much like the checksum of a file, caching will compare checksums to tell whether they match or not.
To actually get advantage of using this header you have to either call manually CakeResponse::checkNotModified() method or have the RequestHandlerComponent included in your controller:
<?php
public function index() {
$articles = $this->Article->find('all');
$this->response->etag($this->Article->generateHash($articles));
if ($this->response->checkNotModified($this->request)) {
return $this->response;
}
...
}
I thought I'd post the solution(s) I eventually used, in case it helps anyone.
To disable caching completely for logged in users:
class ArticlesController extends AppController {
public function view($id) {
$article = $this->Article->find(
'first',
array('conditions' => array('Article.id' => $id))
);
if (!AuthComponent::user('id')) {
$this->response->etag($this->Article->generateHash($article));
}
$this->set(compact('article'));
}
}
To have a separate cache for each user (and for the case when no-one is logged in):
class Article extends AppModel {
public function generateHash($article) {
if (AuthComponent::user('id')) {
return md5(AuthComponent::user('id') . '-' . $article['Article']['modified']);
} else {
return md5($article['Article']['modified']);
}
}
}
class ArticlesController extends AppController {
public function view($id) {
$article = $this->Article->find(
'first',
array('conditions' => array('Article.id' => $id))
);
$this->response->etag($this->Article->generateHash($article));
$this->set(compact('article'));
}
}