I am looking for some help concerning WordPress plugin development. My plugin queries an API which requires an Authentication Token, that token is fetched via a Token delivery APi. The token expire every 3600 seconds.
I would like to store the Token, in a persistent way (for all sessions , like server side caching) and update it only when needed. Multiple Api call could be done with the same token. The problem is, if I store the token in a global variable, it gets reset each time a user reload a page which uses my plugin.
https://wordpress.stackexchange.com/questions/89263/how-to-set-and-use-global-variables-or-why-not-to-use-them-at-all
After looking for answer I found:
-WP_CACHE , but it is not persistent.
-I know I can store the token in the Database, but a Token in Database is not a use case I found elegant
-Tool such as Redis Object Cache for PHP but I found it to be really complicated , installing etc...
Is there any good practice or easy way of doing this? I only need to keep a string for an hour and access it within the plugin in PHP.
Thank you.
https://wordpress.stackexchange.com/questions/89263/how-to-set-and-use-global-variables-or-why-not-to-use-them-at-all
In this case, I would work with WordPress transients:
https://developer.wordpress.org/reference/functions/set_transient/
https://developer.wordpress.org/reference/functions/get_transient/
To set a transient, you can use this code:
$transient = 'your_token_name';
$value = 'your_token'
$expiration = 3600
set_transient( $transient, $value, $expiration );
To receive the value of your transient again, you can use:
$token = get_transient( $transient );
Using these methods is better than update_option or get_option since WordPress manages the expiration (deletion) of transients completely, so you don't have to implement your own logic for this.
Before you pass the value to the transient method, you can encrypt and decrypt it by storing a salt/key in your wp-config.php. You can find more infos about this topic here:
http://php.net/manual/es/function.openssl-encrypt.php
http://php.net/manual/es/function.openssl-decrypt.php
To define a constant in WordPress you need to go to your wp-config.php file and add it between the "you can edit" words:
define( 'YOUR_SALT', '12345678' );
You can read it again as a normal constant in WordPress:
$salt = YOUR_SALT;
Related
I know this question is frequently asked on this forum, but none of the proposed solutions has worked for me, basically, I am a WordPress plugin developer and developing a plugin whose session starts in the init file but close when the client closes his/her browser, and it is necessary to keep the session open. In this case, how can I get rid of this warning? Maybe I should use something other than the session or what you suggest.
Till now there are different proposed solutions like Use
if ( !session_id() ) {
session_start( [
'read_and_close' => true,
] );
}
but with this technique, I can never be able to use sessions in my plugin, and the whole functionality goes on the ground.
Any help in this regard would be much appreciated.
thanks
WordPress plugin or theme developers cannot use php's session subsystem as you have discovered.
If you need to save some kind of global context for your plugin to work, use WordPress's add_option() function to save it at the end of each operation. You can retrieve your context using get_option().
If you need per-user context, use add_user_meta() to save it and get_user_meta() to retrieve it. Like this:
$id = get_current_user_id();
$myContext = get_user_meta( $id, 'my_plugins_meta_tag_name' );
...
add_user_meta( $id, 'my_plugins_meta_tag_name', $myContext);
WordPress uses a database table to store these sorts of options. Some sites put an object cache in front of the table. But these APIs are how you do what you want to do.
In your plugin's deactivation hook, please consider calling delete_option() to clean up that storage. And, if you use per-user context, please consider using delete_user_meta() to remove your user_meta item from all of them.
$users = get_users( array( 'fields' => array( 'ID' ) ) );
foreach($users as $user){
delete_user_meta ( $user->ID, 'my_plugins_meta_tag_name' );
}
As for sensitive information, WordPress offers no other place to put it. And, if you were using sessions, php would need to put it somewhere as well. By default, php puts session data in the filesystem of the server. That's why WordPress hashes user passwords before storing them.
i want to set a cookie if a specified form (Contact Form 7) was sent.
If is write the code into the init-call it works:
function process_post() {
if( isset( $_POST['kampagne'] ) ) {
setcookie('cid', get_option( 'cf7-counter', 0) + 1 , time()+3600, '/');
}
}
add_action( 'init', 'process_post' );
But that's not good, because the value of cookie is an id - stored in database, which i must get in the moment where the form will send.
So I want to set the cookie in the "wpcf7_before_send_mail" hook. But within that the cookie will not generate.
function wpcf7_do_something($WPCF7_ContactForm)
{
$submission = WPCF7_Submission :: get_instance();
if ( $submission ){
$posted_data = $submission->get_posted_data();
if ( empty( $posted_data ) ){ return; }
$changed_name = get_option( 'cf7-counter', 0) + 1;
update_option('cf7-counter', $changed_name);
$mail = $WPCF7_ContactForm->prop( 'mail' );
$new_mail = str_replace( '[cf7-counter]', '00'. $changed_name .'', $mail );
$WPCF7_ContactForm->set_properties( array( 'mail' => $new_mail ) );
return $WPCF7_ContactForm;
}
}
add_action("wpcf7_before_send_mail", "wpcf7_do_something");
I hope you can help me.
Thank you very much!
Best regards.
But that's not good, because the value of cookie is an id - stored in database, which i must get in the moment where the form will send.
So what you need is the ID that will be stored into the database when creating the cookie.
Create it
if( isset( $_POST['kampagne'] ) ) {
$cookie = 'cid';
$id = uniqid("cid:", true);
setcookie($cookie, $id, $_SERVER['REQUEST_TIME'] + HOUR_IN_SECONDS, '/', ini_get('session.cookie_domain'), (bool)ini_get('session.cookie_secure'), (bool)ini_get('session.cookie_httponly'));
$_COOKIE[$cookie] = $id;
}
; and store it
$cookie = 'cid';
$WPCF7_ContactForm->set_properties(
[
'mail' => $new_mail,
'cid' => $_COOKIE[$cookie],
]
);
.
The contact form transaction now has the id that is in the cookie.
It is independent to get_option( 'cf7-counter', 0) by binding the submission to its own identifier - like the cookie is not the cf7-counter option.
Take note on explicitly setting $_COOKIE[$cookie] = $id;, always. This is to have the ID in the request itself.
Take double note on this action, as the code does set the cookie with a new ID even if it was already set. If that is not your intention, check if the cookie is unset beforehand (later references on setting a cookie in Wordpress have it, too).
It is now more eventually consistent, up to the level of uniqueness of the uniquid() function (study it if you want to make use of it), which is an improvement over the situation where the chance of being inconsistent was much higher (that is when using the cf7-counter option).
An insecure random-unique ID normally is enough for short term transaction tagging, e.g. having it for an hour in the cookie.
Which is something you may want to put more of your attention to. And the example is less Wordpress specific, there is more to get from in Wordpress like nonces, keys, salts, cookie-paths, user-level cookies etc. pp. to adhere to that system specifically.
It requires you to study Wordpress first, which I can't take of your shoulders, and there is even more to learn first, like how cookies work. Perhaps ask the more general questions first that you have to close such gaps.
Another answer I was thinking about first is to take a more fitting action so that you have the id the moment you can do both: setting the cookie and storing to the database.
As it has been commented, such a solution would need to find the right place in time. But that is merely how cookies work.
(And not specifically Wordpress.)
As you have already seen yourself, the action you wanted to use did not work.
And even if you would find such another action, this is still Wordpress, and it would be unpredictable that it would suffice: The cookie might be set in your tests, but not when a user visits the site (or any site that is out there with Contact Form 7 which this answer should answer if it is worth it).
To set a cookie in Wordpress, there is no free choice of the action to do it in, but it first of all needs to set the cookie for which the action is invariant:
How to set a cookie in Wordpress
Setting a redirect cookie in wordpress
As the action is set in stone to set a cookie, this goes back to the answer above.
However this is not Wordpress SE, but Stackoverflow so I've put the focus more on the PHP code itself where it relates to the PHP configuration and only HOUR_IN_SECONDS is a constant from Wordpress in it. See as well my comment on it about more specific Wordpress features you may want to add to it.
It hopefully answers your question to the level you need it.
I have adopted a CakePhp 2 project. We get to the project from another project, linking to the CakePhp project with a "token" and a conference ID as a parameter in the URL. Using that token, we authorize the user, and using the conference ID get the information from the database. The session value "auth" is set to true.
We have it running on 2 "platforms" locally on my system using a vagrant machine, and on a production server. Locally the session value dies really quick and at random times. On the production server not as often, but the issues we have where Ajax calls don't seem to do what are expected, we believe are being caused by a similar issue. We have many different projects, all Laravel, with zero issues where the session values clear. This issue is strictly with the CakePhp project.
All the authentication magic happens in the beforeFilter method. The code:
public function beforeFilter() {
$session = new CakeSession();
/**
*
* We will check if the current user is authorized here!
*
*/
// If the visitor is coming for the first time, there should be a parameter in
// the URL that is the auth code to check against the database.
if ( ( isset($_GET['conf']) && is_numeric($_GET['conf']) ) && isset($_GET['token']) ) {
$getConference = ClassRegistry::init('Conference')->find('first', ["conditions" => ["conference_id"=>$_GET['conf'] ]]);
$checkToken = ClassRegistry::init('User')->find('first', ["conditions" => ["remember_token"=>$_GET['token'] ]]);
if ($getConference && $checkToken) {
$checkToken['User']['remember_token'] = $this->generateToken();
if ( ClassRegistry::init('User')->save( $checkToken ) ) {
$session->write('auth', true);
$session->write('conferenceId', $_GET['conf']);
$this->redirect('/');
}
}
else {
$session->write('auth', false);
$session->write('conferenceId', null);
}
}
if (! $session->read('auth') || $session->read('conferenceId') == null ) {
echo "No permission!";
exit;
}
}
At the top of the controller:
App::uses('CakeSession', 'Model/Datasource');
When the URL parameters are present, it traps them, does the work, and redirects to the home route without the parameters.
$this->generateToken();
Creates a new token, and overwrites the old one in the database.
There are 2 main controllers. The controller with this code is the main projects controller. The only time it is really hit is the first time you go to the project, and we hit the index method. From there everything else is AJAX calls to the other controller. There is one link, a "home" type link that will hit that index method.
Sometimes these Ajax calls stop working, and clicking that home link will output "No Permission" instead of the expected html in the container the Ajax call outputs too.
Steps to troubleshoot led me to putting this beforeFilter method on the top of the second controller. Now, randomly I'll get no permission. Sometimes, when I'm on the main project that links to this CakePhp project, I click that link, I get no permission right off the bat.
I found this page: cakephp takes me to login page on multiple request and have tried to set the session details like this:
Configure::write('Session', array(
'defaults' => 'php',
'timeout' => '300' // <- added this element
));
And I have tried:
Configure::write('Session.timeout', '300');
Additionally, I have tried cookieTimeout in both of those cases.
I've also tried
Configure::write('Security.level', 'low');
and included
Configure::write('Session.autoRegenerate', true);
In any order, any of these cause the session to bomb out immediately. I get "No permission on page load, and never get anywhere.
The code for this project is honestly crap. The developer who wrote it had mistakes and errors all over the place. On top of that, we are a Laravel shop. We are just trying to keep the project limping along until sometime in the future when we can nuke it from orbit. So we just need to get this working. Any thoughts on what could be causing this? Any other details I am forgetting to include that would help troubleshoot this issue?
Thanks
Reading & writing session data
You can read values from the session using Set::classicExtract() compatible syntax:
CakeSession::read('Config.language');
$key should be the dot separated path you wish to write $value to:
CakeSession::write('Config.language', 'eng');
When you need to delete data from the session, you can use delete:
CakeSession::delete('Config.language');
You should also see the documentation on Sessions and SessionHelper for how to access Session data in the controller and view.
I'm currently trying to get a custom component working to log a user into the backend given their username and password. I realize the inherent security problems this may present, but I would really like an answer to this problem I've been struggling with. Here is the code in my controller.php:
function execute() {
$credentials = array( 'username' => $this->username,
'password' => $this->password );
$options = array();
$options['group'] = 'Public Backend';
$options['autoregister'] = false;
$options['action'] = 'core.login.admin';
$app =& JFactory::getApplication('administrator');
$result = $app->login($credentials, $options);
echo $result;
}
All the documentation that I've read says that this should work (even though it is not exactly kosher to perform admin tasks--like logging into the backend--from a non-admin context). Any idea on what I'm not getting?
EDIT: I forgot to mention which Joomla version I'm using: 2.5.14
I think you need to pay attention to the Joomla authentication/login/authorization flow and user flow which are managed via user and authentication plugins. From what you say it sounds like you will still be using the Joomla user table, is that right? So in that case the Joomla user plugin should work.
Assuming that is true you want to make an authentication plugin. This plugin will be checked along with the joomla authentication plugin and any other authentication plugins you may have running (e.g. remember me/cookie login).
I don't totally understand what you are trying to do (and you haven't said what version of Joomla you are on, which makes a difference in this case), for example do you really want to login to the full back end or do you just want to show some screens the way the insert image plugin does? If on Joomla 3 you might want to look at the Google Summer of Code project for bringing some admin to the front end (called com_services for now). That student also has an http login plugin you could look at. https://github.com/Buddhima/Joomla-HttpLogin-plugin
I have seen in some MVC applications the use of Token keys to prevent CSRF. A typical example of where it may be used is on the delete method for a post.
And I've seen implementations using both GET and POST methods.
An example GET request link with a token:
https://domain.com/posts/G7j/delete/EOwFwC4TIIydMVUHMXZZdkbUR0cluRSkFzecQy3m5pMTYVXRkcFIBWUZYLNUNSNgQKdnpTWu
And an example of a POST request with a token:
<form action="/posts/G7j/delete" method="post">
<input type="hidden" name="token" value="EOwFwC4TIIydMVUHMXZZdkbUR0cluRSkFzecQy3m5pMTYVXRkcFIBWUZYLNUNSNgQKdnpTWu" />
<button type="submit">Delete</button>
</form>
I've been looking into implementing this into my CakePHP applications based on the documents: http://book.cakephp.org/2.0/en/core-libraries/components/security-component.html
And according to the documents, adding the Security component auto adds the form key to all forms that use the Form Helper.
e.g.
public $components = array(
'Security' => array(
'csrfExpires' => '+1 hour'
)
);
However I have some questions:
1.) Why use POST over GET for some actions such as delete? As the request in the controller will check if the user is authenticated, has permission, and that it has the correct form key.
2.) How can I use the security component with a GET request in CakePHP? Presume I would also need to handle the routing as well.
Firstly CakePHP uses post links to delete as an added level of security because lets say for example your authentication system is not 100% secure and users can access a delete method by manually typing in a URL - if I go in and type /users/delete/10 , I can actually delete users on your server which is risky as you can imagine.
Secondarily GET requests can be cached or bookmarked so users who bookmark these links could end up navigating to broken links which is never a good thing ,also sensitive data will be visible in the URL so for example if someone bookmarks a login page with the GET variables intact - this could compromise there security.
Finally you can easily generate your own tokens using the following code :
$string = Security::hash('string', 'sha1 or md5', true);
print $this->Html->link("Link to somewhere",array("controller"=>"users","action"=>"delete",$string));
The above will use the salt key setup in your core config file but you can also replace true with a custom salt.
For the first question:
The reason probably lies in the definition of HTTP methods. GET is defined as one of the safe methods, which means that it can not be used for changing the state of the server but only for retrieving information. You can read more on the HTTP methods on this link. Since HTML forms are not capable of sending HTTP DELETE request the 'workaround' is to use some of the available methods and if you rule out the GET as a 'safe method' it leaves POST. You can of course use GET to delete stuff, many do, but GET request is by convention expected not to change anything.
edit: If you are interested to read more about HTTP methods and browser/HTML support check out this SO question
johhniedoe pointed me to Croogo 1.3 for an example of how they are have done something similar to what I asked in my question. Because 1.3 was targeted to CakePHP prior to 2.x, the code was a little out of date, so I've amended it as follows:
First create the link (in this case a delete link) with the CSRF Token used by the Security Component passed as a parameter called token.
<?php echo $this->Html->link('Delete', array('action'=>'delete','token'=>$this->params['_Token']['key'])); ?>
Next I've created a wildcard Route Connection to handle the token (this isn't essential usually, but because we're not using the NAMED Token and I wanted to keep the URL cleaner):
Router::connect('/:controller/:action/:token',
array('controller' => 'action'),
array(
'pass' => array('token')
)
);
And then finally handle it in your method like so:
public function delete(){
if (!isset($this->params['token']) || ($this->params['token'] != $this->params['_Token']['key'])) {
$this->Security->blackHoleCallback = '__blackhole';
} else {
// do delete
}
}
This basically says if the token doesn't match then use the blackhole callback function __blackhole, which I define in my AppController to show an error.
One last thing to be aware of though is that you must allow the token to be used more than once and last for example an hour, this is because otherwise the token will be reset and no longer match after the GET request is sent.
public $components = array(
'Security' => array(
'csrfExpires' => '+1 hour',
'csrfUseOnce' => false
)
);