I'm writing a custom php code in my Drupal website. I need to load the content of specific pages from PHP.
These pages are visible only for authenticated users, and it seems I cannot access them from php, even if I trigger the script when I'm logged in as user.
Is there a way to simulate "a logged in" user from php, so I have access to all the content of the website ?
update:
global $user;
if (user_access('access content')) {
require_once("dompdf/dompdf_config.inc.php");
$html = file_get_contents('http://mywebsite.com/admin/store/orders/45/invoice/print');
$dompdf = new DOMPDF();
$dompdf->load_html($html);
//$dompdf->load_html_file('invoices/' . $file);
$dompdf->render();
$dompdf->stream("sample.pdf");
}
I've tried with relative path and it is the same...
And this is with impersonating the admin user
//access as administrator
global $user;
$original_user = $user;
session_save_session(FALSE);
$user = user_load(array('uid' => 1));
//generate pdf
require_once("dompdf/dompdf_config.inc.php");
$html = file_get_contents('http://mywebsite/admin/store/orders/45/invoice/print');
$dompdf = new DOMPDF();
$dompdf->load_html($html);
//$dompdf->load_html_file('invoices/' . $file);
$dompdf->render();
$dompdf->stream("sample.pdf");
//logout as administrator
$user = $original_user;
session_save_session(TRUE);
Still I get access denied as resulting page (and generated pdf).
thanks
The code to do so is:
<?php
if (user_access('access content')) {
print "You have the permission 'access content'";
}
?>
Running code that circumvents the permission system might seem simple and easy, but is really a serious security hole.
However, since that is what you ask:
<?php
global $user;
if ($user->uid) {
print "You are a registered user"
}
?>
But again, never use this as a replacement for permissions.
These pages are visible only for authenticated users, and it seems I cannot access them from php, even if I trigger the script when I'm logged in as user.
Drupal checks if the user has permission to view a node using the global variable $user. To do what you are trying to do, if you cannot trust that the currently logged in user have the permission to view the node you are interested in, you should read Safely Impersonating Another User.
I am not saying that you should be doing that. Before to impersonate another user, I would verify if the followed approach is the only possible one.
For example, if you just need to access a field contained in a node, then you can use node_load(), which doesn't verify if the current user can view the loaded node.
If you need to show the body of a node, you can use the following code:
$node = node_load($nid);
if ($node) {
$body = check_markup($node->body, $node->format, FALSE);
}
Showing information for which the current user doesn't have access is considered a security issue, though.
Update
The issue with your code is that you are using file_get_contents('http://mywebsite/admin/store/orders/45/invoice/print'); doing so, you are opening a new connection to the site, and the new connection is opened as anonymous user. That is the reason the node that authenticated users are able to see is not returned.
Even if the code would work, what you get is not the HTML to render the node only, but also the full page, including the blocks Drupal normally show on the top, and to the left/right sides.
If you are interested in rendering a node, then you should use the following code. (It's just a skeleton, and it's not complete.)
// $nid is the node ID.
// Check the result, in the case the node has been deleted, or there are other errors.
$node = node_load($nid);
if ($node) {
// The arguments tell the function that you don't want to render a teaser, that the node is
// rendered as it is the only node in the page, and that you don't want the additional
// links that are usually rendered after the node content.
$html = node_view($node, FALSE, TRUE, FALSE);
// This is your code.
$dompdf = new DOMPDF();
$dompdf->load_html($html);
$dompdf->render();
$dompdf->stream("sample.pdf");
}
About the updated code.
Your file_get_contents will pull in the content as "anonymous user". That is just one reason why your code is a bad idea:
Whenever your code runs, it will open your own site and parse that code: resulting in at least two "Drupals" to be loaded: effectively at least two pageviews to show one page to a user. But many more problems with this approach are possible.
Instead, you should find the code/function that creates the page at http://mywebsite.com/admin/store/orders/45/invoice/print and use that as input for your PDF-creator.
Related
Suppose you want to build a webpage with Facebook PHP SDK, where you want to allow the user to select the information Facebook will return to the server. I've came with the following code to allow an user to either choose from allowing Facebook to send only the basic profile or else to also send the pages managed by this user.
session_start();
// Load the Facebook PHP SDK
require_once __DIR__ . '/facebook-sdk-v5/autoload.php';
define('APP_ID', 'xxxxxxxxxxx');
define('APP_SECRET', 'xxxxxxxxxxxxxxxxxxxx');
$fbProfile = new Facebook\Facebook([
'app_id' => APP_ID,
'app_secret' => APP_SECRET,
'default_graph_version' => 'v2.7'
]);
$fbPages = new Facebook\Facebook([
'app_id' => APP_ID,
'app_secret' => APP_SECRET,
'default_graph_version' => 'v2.7'
]);
$helperProfile = $fbProfile->getRedirectLoginHelper();
$redirectUrlProfile = 'http://www.example.com/link1.php';
$loginUrlProfile = $helperProfile->getLoginUrl($redirectUrlProfile);
echo 'Get profile with Facebook!<br>';
$helperPages = $fbPages->getRedirectLoginHelper();
$permissions = ['pages_show_list']; // Optional permissions
$redirectUrlPages = "http://www.example.com/link2.php";
$loginUrlPages = $helperPages->getLoginUrl($redirectUrlPages, $permissions);
echo 'Get pages with Facebook!';
If I use the above code (commenting the non-relevant parts) with only one facebook object to either retrieve the profile or the pages managed by user (but not both), everything works fine. But if I use both objects concurrently to give a choice to the user, I get a FacebookSDKException. I guess this is due to CRSF cookies.
Is there any way to circunvent this problem?
I guess this is due to CRSF cookies.
Correct. Calling the getLoginUrl method creates a new random state value and writes it into the session, overwriting any previously stored one.
So if you call the method twice (or more times), login will only work if you call the login dialog via the last login URL created, because only that contains a state value that matches the one stored in the session.
If you want to keep using two different redirect URIs, then you need to implement an additional step to create the correct login URL, and only that one.
So you have two links in your page, both pointing to a script on your server, and passing what permissions to ask for via a GET parameter (whether you want to pass permission names directly, or just a flag like ?loginmode=1/?loginmode=2, is up to you.)
In that script, you decide which redirect URI and scope value to call the getLoginUrl method with - once. And then your script just redirects to that login URL.
(But keep in mind that the step that exchanges the code for an access token also requires the redirect URI parameter to be passed, again - and again with the exact same value that was used in the login dialog call.)
So doing it the way #luschn suggested in comments, using the JS SDK for login purposes, is probably much easier. FB.login can be called with different scopes from different points in your client-side JS code without any such problems.
I had to add a WordPress installation to my CodeIgniter system, so I've put it in a submap called blog and excepted that folder in my .htaccess. All good and well.
I've put the all WordPress tables together with in my CodeIgniter databases with prefix _wp.
I've now loaded the WordPress blog header file into the index.php of CodeIgniter, like so;
require('blog/wp-blog-header.php');
add_filter('site_url', 'ci_site_url', 1);
function ci_site_url() {
include(FCPATH.'/application/config/config.php');
return $config['base_url'];
}
And made a registration method in my Account controller to make an actual link to my Customers. I do this because I want to make the WordPress login/registration obsolete and solely control that from the CodeIgniter login page;
protected function register_wp($email_address = FALSE) {
if ($email_address !== FALSE) {
if (username_exists( $email_address ) == NULL) {
$password = wp_generate_password(12, TRUE);
$user_id = wp_create_user($email_address, $password, $email_address);
wp_update_user(array(
'ID' => $user_id,
'nickname' => $email_address
));
$user = new WP_User($user_id);
$user->set_role('subscriber');
$login_data = array(
'user_id' => $user_id,
'password' => $password,
);
return $login_data;
}
else {
// User already exists with that email address
return FALSE;
}
}
else {
// No email_address given
return FALSE;
}
}
And the login method, to give an idea;
protected function login_wp($user_id = FALSE) {
if ($user_id !== FALSE) {
$user_login = 'admin';
$user = get_userdatabylogin($user_login);
$user_id = $user->ID;
wp_set_current_user($user_id, $user_login);
wp_set_auth_cookie($user_id);
do_action('wp_login', $user_login);
}
else {
// No user_id given
return FALSE;
}
}
All still going well. But here comes the clash; something I was very sad about because everything worked very well up until now:
WordPress overtakes the session and kills CodeIgniter's session.
I already tried tons of things;
session_rename('PHPSESSIDWP'); and then starting another session (with another name) for CodeIgniter after WordPress was loaded
COOKIE path (I'm not 100% sure if I done this right, as it didn't change at all. Read some things online it doesn't work well in all browsers either)
COOKIE domain (seemed to have no effect)
The problem is I can't load the require('blog/wp-blog-header.php'); only in the controller method, as I need to be able to control the logged in state of the WordPress part. Besides that I will get complaints about the site_url() function, that's already claimed by the URL helper.
I think the problem is mainly because both CodeIgniter and WordPress use their own unique way of handling Sessions (CI in the Database and WordPress in "super globals") which probably only makes them use the cookie to remember a "state".
My whole CodeIgniter system already runs on the Database-driven Session models so that's an absolute no-go to make a switch. For WordPress it seems it can't even work with session anymore with it's code features (I know session "do" work, but that doesn't seem to count in any way for the WP core system).
Also I quoted out wp_unregister_GLOBALS(); in the wp-settings.php file.
Plus that I also tried to rename my session COOKIE name in CodeIgniter to use something like session_ci
I really hope someone knows a way to being able to tell CodeIgniter or WordPress to only update their values and don't kill the whole session each time. I also read something about splitting up cookies with .htaccess but can't find good resources on it. So if anyone knows how to do that, I would be eternally grateful.
I'm in despair. Finishing it for 98% and then getting such a letdown in the end :(..
Update
Maybe I can do something in the WordPress section that handles the cookies?
http://codex.wordpress.org/Function_Reference/wp_set_auth_cookie
Sadly I'm not really home in the WordPress world. I solely have to use it this one time due to the bought template that the people really wanted to use in the blog.
Also this page states the following;
WordPress uses the two cookies to bypass the password entry portion of wp-login.php. If WordPress recognizes that you have valid, non-expired cookies, you go directly to the WordPress Administration interface. If you don't have the cookies, or they're expired, or in some other way invalid (like you edited them manually for some reason), WordPress will require you to log in again, in order to obtain new cookies.
I wonder tho, how to bypass that "invalid" check, which probably is the reason it kills the CodeIgniter cookie(s)? Weirdly enough tho, it seems the session_ci value stays, although the session still seems killed.
You need to put your session start at the very top of config.php.
This is the only place a session will not be destroyed by WordPress.
if (!session_id())
session_start();
If your PHP installation does not have register_global enabled, the
above code should allow you to use session, however, if it does, you
will not be able to get the data that was set in previous request.
This is because WordPress will destroy all data contained inside
session variable when it does the initialization.
Here's why and troubleshooting on this -> kanasolution.com
EXPANDED ANSWERS:
Source: http://codex.wordpress.org/WordPress_Cookies
On login, wordpress uses the wordpress_[hash] cookie to store your
authentication details. Its use is limited to the admin console area,
/wp-admin/
After login, wordpress sets the wordpress_logged_in_[hash] cookie, which indicates when you're logged in, and who you are, for
most interface use.
So WordPress clearly dislikes the way that you're writing cookies, maybe their lack of 8 pass MD5 hash etc? WordPress encryption methods
The WordPress Environment
The next thing I would try is integrating your custom login page into the WordPress environment instead of just requiring the header. (lets stay away from editing core)
From WordPress & AJAX by Ronald Huereca page 78 explains manually loading the WordPress environment.
The use of the dirname functions depend on the hierarchy of your file. Adjust them as needed. Code should be used before the tag of your file.
$root = dirname(dirname(dirname(dirname(dirname(__FILE__)))));
if (file_exists($root.'/wp-load.php')) {
require_once($root.'/wp-load.php');
/*Run custom WordPress stuff here */
//Output header HTML, queue scripts and styles, and include BODY content
wp_enqueue_script('my_script', get_stylesheet_directory_uri() . '/my_script.js', array('jquery'), '1.0.0');
wp_print_scripts(array('my_script'));
}
This is giving me quite some headache. I have an page-tab-application, where DB-interaction uses the facebook-user-id to assign and save data and also to check user permissions. Until a weak ago everything was working fine, but now with the upcoming december-changes this setup doesnt work anymore:
config.php:
$facebook = new Facebook( array(
'appId' => $app_id,
'secret' => $app_secret,
'cookie' => true
));
index.php:
includes config.php and gets the signed request (not important for the question
javascript.js:
calls the read-user-status.php and handles the data
read-user-status.php:
gives json-response, includes config.php and calls the $facebook -> getUser()-function to get the uid
Even when called from the index.php directly after page-load, I sometimes get the uid and sometimes I don't. Strangly enough I usually have to wait a little until I reload the page and then it works again. But this isn't always the case. This all is just very strange to me.
EDIT: Should have mentioned that this call:
$uid = $facebook -> getUser();
if ($uid) {
try {
// Proceed knowing you have a logged in user who's authenticated.
$user_profile = $facebook -> api('/me');
} catch (FacebookApiException $e) {
error_log($e);
$uid = FALSE;
echo "EXCEPTION $e";
}
}
gives out "EXCEPTION An active access token must be used to query information about the current user".
I know there quite a lot of similar questions out there, but none of the answers were helpful to my particular (and probably to the new breaking changes relied) problem.
EDIT2: I now suppose that it is a sdk-bug (https://developers.facebook.com/bugs/238039849657148 , thanks to CBroe). Any recommendations for a work-around are of course very welcome.
EDIT 3, TEMPORARY SOLUTION
Everytime you make an ajax request, you post the token you get from the FB.getLoginStatus or FB.login and read it out in the php file and set it via $facebook -> setAccessToken. Not suitable in all circumstances (you definately need to use post), is slower and brings some security issues, but still works.
Sounds like you are affected by the bug I reported beginning of November, https://developers.facebook.com/bugs/238039849657148
They’ve confirmed it and say they’re working on a fix – but since the change is only a few days away now, they should hurry up a little …
I got this working by doing the following...
if(!$user){
$loginUrl = $facebook->getLoginUrl(array(
'scope' => 'email',
'redirect_uri' => $app_url
));
header('Location: ' . $loginUrl);
}
I also added my app to be integrated with:
Website with Facebook login
App on Facebook
Page Tab
try by adding access token to request.
$accessToken = $facebook->getAccessToken();
$user_profile = $facebook->api('/me?access_token=' . $accessToken);
I found a work-around for this, until it is fixed (which it seems like, wont be in time until the changes take place).
Everytime you make an ajax request, you post the token you get from the FB.getLoginStatus or FB.login and read it out in the php file and set it via $facebook -> setAccessToken. Not suitable in all circumstances (you definately need to use post), is slower and brings some security issues, but still works.
if it you are lucky and your version of php sdk still registers session variables than right after _graph method declaration:
//find this method below
protected function _graph ($path, $method = 'GET', $params = array ())
{
//paste right after _graph method declaration code below:
if (isset($_SESSION["fb_".$this->getAppId()."_access_token"]))
{
$this->setAccessToken($_SESSION["fb_".$this->getAppId()."_access_token"]);
}
//till here
//and you are good to go
//remember: your version of sdk must be registering access token variable in session
//right after ajax call
//i used git to get version before last commit of sdk published on github
I am currently working on a project based on Symfony 1.4. I am using the sfDoctrineGuardPlugin to authenticate my two kinds of users : users and admins. For each module and each action in a module, I am using credentials to prevent unauthorized actions execution.
But I am facing a problem : if an user wants to edit a project, for example, the URL will look like frontend.php/project/edit/id/1. Here, we suppose that the project #1 belongs to him. Now, let's suppose that project #2 does not belong to him. If he types the URL frontend.php/project/edit/id/2, he will have access to the edit form, and will be able to edit a project that does not belong to him.
How can I prevent that behaviour ?
I would like to avoid verifying the ownership of each editable model before displaying the edit form... But can I do differently ?
Do you have any good practice or advices to prevent this behaviour ?
Thanks a lot !
Since you will have to check in the projet to know if the current user is allowed to edit the project, I don't think you will have other way than verifying before the edit, in the action part. Why don't you want to do it this way?
This check can be done inside the preExcute function:
public function preExecute()
{
$request = $this->getRequest()
if ($request->hasParameter('id'))
{
$project = Doctrine_Core::getTable('Project')->find($request->getParameter('id'));
$user_id = $this->getUser()->getGuardUser()->getId();
$this->forward404If(
$project->getUserId() !== $user_id,
'User #'.$user_id.' is not allowed to edit project #'.$project->getId()
);
}
}
I am trying to redirect any logged user attempts to access /user.
In my module the next code to redirect after login:
function ccmm_user($op, &$edit, &$account, &$category = NULL)
{
switch($op){
case 'login':
$_REQUEST['destination'] = 'admin/';
break;
}
}
This is working. Then I try with case 'view': but it is useless.
It sounds like you want the user to never get to the /user page, whether on login or even by going there manually.
In that case you should do a simple check in a hook_init function like this:
function ccmm_init() {
if ( $_REQUEST['q'] == '/user' ) {
drupal_goto('/admin'); // Or where ever you want to send them
}
}
Of course there are a lot of checks you should do, and you may want to look into using the Global Redirect module, don't worry it's only 8k in size so the concern about adding yet another module is not such a problem in this case.
You could try the Login Destination module rather than writing your own code.
If you're not familiar with the Login Toboggan module, you should be.
A better way could be to use hook_menu_alter in your module to just remove the menu entry for /user/%user_uid_optional, or move it to another URL.
That way you won't only be handling just logins (as you currently do), but ANY access to /user/ (caveat: including those by the admin user). Or you could use the same hook to modify the access check and only grant it to users with higher permissions, like administer users