I am following the cakephp documentation to implement the database logger function. I got everything working after creating the logger class in app/Lib/Log/Engine/DatabaseLogger.php
and add these lines to bootstrap
CakeLog::config('otherFile', array(
'engine' => 'DatabaseLogger',
'model' => 'LogEntry',
// ...
));
After this I can log to the database by calling
CakeLog::info(message);
Everything works fine, but then I ran into problem trying to automatically log the user's username and ip address. I have searched for many solutions onlien but I do not seems to be able to find an answer. Is it possible to access the controller in the DatabaseLogger class?
You don't need access to the controller
The information you need is effectively global, you don't need to access the controller object to get it.
Client IP
You can use the Router::getRequest method to get the current request object and retrieve the client IP from that:
$request = Router::getRequest();
$ip = $request->clientIp();
Or simply use the env method:
$ip = env('REMOTE_ADDR');
Current User's name
For this, just use the static session interface:
$name = CakeSession::read('Auth.User.name');
This will hopefully get you going in the right direction. I'm currently in the process of migrating some of my CakePHP apps up to 2.2. I'm not 100% certain on this, but it seems to be about the way to go about this. I am thinking about using database logging and I like your idea so let me know how this works out. If it doesn't, provide feedback and I'll dig deeper. ;)
Create your LogEntry model (database table) to include fields for the username and the ip address.
Create an AppController. In the beforeFilter method place the user's ip address in their session. The CakeRequest object will contain their IP address.
$this->request->clientIp();
In your beforeSave() method for that model, you will collect the username and IP address.
To access the username field (assuming you are using Auth component and clientIp as the key to their ip address):
App::import('Component', 'Session');
$Session = new SessionComponent();
$user = $Session->read('Auth.User');
$ip = $Session->read('clientIp');
Assign these to the right fields so that they will be saved.
Related
How do I overwrite the APP_NAME config variable dynamically based on the domain/hostname so that I can reference it in all Controllers and Blade templates?
OR how can I create a global variable (single record from a Model) that changes dynamically based on the domain, so that it is accessible in all Controllers and Blade templates?
I have 3 domains - each for a different athletic conference/League - that share 100% the same code. Currently, I have a function in /app/Helper/Helper.php that I call from every Controller. This gets the correct League based on the domain, and allows me to send the correct set of data to each view.
// Get league info based on domain
public static function getLeague(Request $request)
{
$host = $request->getHost();
if (App::environment('production')) {
// Some fancy Substring logic to get the domain name
} else {
// Manually set $host to one of my domains for offline debugging
}
$league = League::where(['url' => $host])->first();
return $league;
}
The above works great for Views and Controllers I have built.
However, I also leverage several out-of-the-box views and email templates for User Registration, Login, Forgot Password, etc., that use APP_NAME.
Some of them (like the Login or Register screens) have a typical Controller so I can call my Helper function and just pass the League to the view. But for others, like email templates, I've tried going down the rabbit trail of Controllers and Functions that eventually produce the email content, and I cannot find where to call my Helper function and pass the League Name to the view.
An example of an email template using APP_NAME, that I cannot figure out how to pass $league to it instead of it using the config variable, is:
/vendor/laravel/framework/src/Illuminate/Mail/resources/views/html/message.blade.php
So I'm stuck trying to figure out how to set the APP_NAME (or automatically calling my Helper function and creating a global $league variable that I can use in Controllers and Blade templates).
I have tried updating 'name' in /config/app.php, but it errors when trying to call the Helper function.
<?php
use App\Helper\Helper;
//dd($_SERVER['SERVER_NAME']);
$league = $league = Helper::getLeagueWithHost($_SERVER['SERVER_NAME']);
return [
'name' => $league->name,
....
]
Error:
Fatal error: Uncaught RuntimeException: A facade root has not been set. in C:\...\vendor\laravel\framework\src\Illuminate\Support\Facades\Facade.php on line 258
This all wouldn't be a problem to set manually in each .env file, but I have an AWS CodePipeline that deploys to each domain upon GitHub push, so I'd have to update 3 .env files every time.
Thanks for any help!
I found a way to accomplish the ultimate goal, but not in the manner being requested in my initial question.
As part of the AWS CodePipeline deployment, there are "Appspec" events that fire at various times in the process (application-stop, before-install, after-install, application-start, and validate-service).
I wrote a Bash script in the "after-install" event that checks the IP of the hostname (returns the Private IP of the current Lightsail instance being deployed). And depending on the hostname IP, it changes line 2 of my .env file to the desired APP_NAME.
There is some hard-coding involved, mapping each Lightsail Private IP to an APP_NAME (meh), but as long as I do not delete and recreate a Lightsail instance, the Private IPs will remain static. The only way to avoid that would be to solve the initial question of updating the APP_NAME at runtime based on the URL/domain.
So, if someone else is trying to solve this problem at runtime when a page loads, this solution will not work for you.
I created a way to authenticate a user with API keys, thanks to a class A implementing the SimplePreAuthenticatorInterface interface. Everything works well (the user is successfully authenticated).
I'm trying to store the API keys, for a later use during the user's journey. To do so, inside the authenticate method of my class A, I return a PreAuthenticatedToken in which the credentials are my API keys.
The problem is : Inside a custom service, when I try to get the token credentials, I get null... I successfully get the API keys when I comment the line 76 of the PreAuthenticatedToken Symfony core class :
public function eraseCredentials()
{
parent::eraseCredentials();
//$this->credentials = null;
}
My questions are:
1) Why is the method eraseCredentials called whereas the user is authenticated? I thought this method was called during user's logging out...
2) What am I doing wrong? Is the PreAuthenticatedToken token the right place to store my API keys? How can I get them back from a custom service ?
Thanks for helping me. :)
PS : I'm a newbee on posting in Stackoverflow (and in English ^^). Sorry in advance for any mistakes.
I found another similar question but it has no helping response and I added some more precisions.
EDIT: The code of my custom service where I try to get the credentials is:
$token = $this->container->get("security.token_storage")->getToken();
if ($token !== null) {
$credentials = $token->getCredentials();
// $credentials is null here
}
EDIT 2: The return part in my code of my SimplePreAuthenticatorInterface::authenticateToken method :
return new PreAuthenticatedToken(
$user,
$apiKeys,
$providerKey,
$user->getRoles()
);
Ad 1. It depends on your AuthenticationProviderManager: this class accepts $eraseCredentials as second parameter - by default set to true (here).
That's why eraseCredentials method is being called on PreAuthenticatedToken $token during authenication (here).
Ad 2. Please check How to Authenticate Users with API Keys tutorial. You should create your custom ApiKeyAuthenticator class and add logic there.
According to your comment:
Note that authenticateMethod from tutorial is being called inside authenticate method (here). At that time token credentials are not erased yet and you can access them. For security reason they are erased after authentication (but this can also be changed / configured via security.yml file). If you need them later you can introduce custom token class and store API key there.
I wanted to use the session table to get a list of all logged in users. To do this I attempted to use the following code to query the cake_sessions table:
$this->loadModel('CakeSession');
$sessions = $this->CakeSession->find('all', array('fields' => 'data'));
// next I process session data to find logged in users
But running this code I get the following error:
Call to undefined method CakeSession::find()
If I use the following code to directly access the table it works just fine:
$db = ConnectionManager::getDataSource("default");
$sessions = $db->fetchAll("SELECT data from cake_sessions");
// next I process session data to find logged in users
Is accessing the session table restricted? My current solution works just fine but I am confused why I cant use the the CakeSession->find().
Because CakeSession is not a model class. See
http://api.cakephp.org/2.8/class-CakeSession.html
The class you refer to is a datasource as the "namespace" tells you as well: Cake\Model\Datasource. In Cake2 session access was implemented as datasource. "Model" is a layer not just a single type of files.
Create a proper session model class in your apps Model folder. See http://book.cakephp.org/2.0/en/models.html
You might have to name it different to avoid conflicts because Cake2 is not using real php namespaces unlike Cake3.
I am using GeoIP package to get the user's IP and translate it into a zipcode. I don't want to do that for every request that the user is making but rather do a one time IP to zipcode, store it into session and then when I need to use it just check if the zipcode exists inside the session.
I tried to place the code inside AppServiceProvider#boot but it does not work. It is not remembered into the session. I tried inside routes but not working as well.
edit
The code inside boot method of appserviceprovider. This is just a test.
If (! Session()->has ('zipcode'))
Session(['zipcode' => geocodeZipcode()]);
The problem is that this runs everytime since the zipcode is not persisted in the session. The if is never false from my tests so far.
Where do I need to put the code to store the zipcode into the session and have it remembered even if the user is not logged in?
I basically need something like this:
1- User accesses a page on the server for the first time (any page)
2- I get the user IP and translate it to a zipcode
3- I store the zipcode into the session
4- For every other request the user makes I check if the zipcode exists into the session. If not I execute step 2.
5- Use the zipcode for its purpose
Where should I place the step 2 and 3?
In Laravel the session is initialized via middleware, and all the middlewares execute after the service providers boot phase
This is the reason why in your service provider you can't access the session: it has not been initialized yet
You should place your steps 2 and 3 in a middleware:
class ZipCodeMiddleware
{
public function handle( Request $request, Closure $next )
{
//ZIP CODE NOT FOUND IN SESSION: CREATE IT AND STORE
if ( ! Session::has( 'zipcode' ) )
{
//get ip and translate to zip
//store zip in the session
}
//use zip code here or access it later from Session
return $next($request);
}
}
Once you've stored the zip code in the session, you can access it from a controllers directly from the session, or, you could instance a class in the middleware and re-access it later with:
//use zip code here or access it later from Session
$zipClass = new ZipClass( $zipCode );
App::instance( ZipClass::class, $zipClass );
This way you can auto-inject the ZipClass depencency in your controllers and Laravel will give you back the $zipClass instance you built previously in the middleware
I've got a CI instance which connects to a Db and checks permissions before serving pages. If the page isn't accessible to the current user, it redirects to a login page.
The login page, obviously, has permissions set so that it's accessible to all users.
After a recent glitch, the database server came back up on a different IP address (thank you Amazon, EC2). This resulted in CI being unable to check permissions for any page - including Login. Since the code assumes anything other than a Yes is a No, it redirected to Login. The result was an infinite redirect loop.
While this exact problem shouldn't happen again (Static elastic IP), I'd like to detect when the Db connection is down and handle it appropriately.
I've seen This SO Question which is what I'm trying to achieve but I'm not explicitly loading the database in any controllers, it's in the autoload config file.
So,
How can I query the state of the Db connection from inside CI? Do I have to run a useless query and check if I get results back or is there a more elegant solution?
Edit: The check is currently being performed in a hook:
$hook['post_controller_constructor'] = array(
'class' => 'AuthHook',
'function' => 'ValidateCredentials',
'filename' => 'auth.php',
'filepath' => 'hooks'
);
You can extend the controller and load the database in its constructor:
class My_Controller extends CI_Controller {
public function __construct(){
parent::__construct();
if ( $this->load->database() === FALSE )
{
//do something
}
}
}
All your controllers will inherit the new controller.
"Since the code assumes anything other than a Yes is a No, it redirected to Login."
So therefore you only need to alter the login logic function to specifically check for a DB connection (and thus still auto-load the database).
Use something like
$result = $this->db->conn_id;