How to let a user choose different Facebook permissions in the same page? - facebook-php-sdk

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.

Related

How to remove user id on url drupal 9

http://localhost/drupal-9.3.3/user/372#myaccount
How to remove user id on URL . Please suggest how to change these type security isssue.
If you want to change the url structure to remove ids, use pathauto https://www.drupal.org/project/pathauto
You can choose to have an url structure for user profiles that can be generated from any user field.
Someone told me that everything you write on the front end or in your main folder is always just basically a suggestion a.k.a. not secure.
Good ways to secure site:
Authenticate users using backend like firebase read/write permissions
Use randomized urls / keys (which firebase also does)
Also catch exceptions and deal with them, perferably at the the place where that function is created, not when it is executed:
Example
yes:
function() => {
// do something
//catch error
}
function(); // call function;
No:
function().then(error => { //do something})

Adding a button for google signin using f#/fable/asp.net/react

I'm working with the SAFE stack (https://safe-stack.github.io/) and through the example dojo. It's great so far.
I'd like to extend the example to include a button to login/auth via Google. So I looked at an example on the Google website (https://developers.google.com/identity/sign-in/web/build-button). And then I had a look how to do authentication using ASP.NET (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-2.1&tabs=aspnetcore2x) As a result I ended up confused as to how to integrate this into a SAFE project. Can someone tell me what they would do? SHould I be trying to use ASP.NET Identity or should I be using the JWT approach? I don't even know if they are the same since I'm very new to web frameworks.....
The other question I have is how would one inject raw Javascript into the client side of a SAFE project. The google example above shows raw JS/CSS/HTML code? Should I be injecting that as is or should I look in React for some button that does this and map that idea back through Fable?
Setting up OAuth
The easiest way to use Google OAuth is to wait until the next release of Saturn, at which point Saturn will include the use_google_oauth feature that I just added. :-) See the source code if you're interested in how it works, though I'm afraid you can't implement this yourself with use_custom_oauth because you'll run into a type error (the underlying ASP.NET code has a GoogleOptions class, and use_custom_oauth wants an OAuthOptions class, and they aren't compatible).
To use it, add the following to your application CE:
use_google_oauth googleClientId googleClientSecret "/oauth_callback_google" []
The last parameter should be a sequence of string * string pairs that represent keys and values: you could use a list of tuples, or a Map passed through Map.toSeq, or whatever. The keys of that sequence are keys in the JSON structure that Google returns for the "get more details about this person" API call, and the values are the claim types that those keys should be mapped to in ASP.NET's claims system. The default mapping that use_google_oauth already does is:
id → ClaimTypes.NameIdentifier
displayName → ClaimTypes.Name
emails[] (see note) → ClaimTypes.Email
Those three are automatically mapped by ASP.NET. I added a fourth mapping:
avatar.url → `"urn:google:avatar:url"
There's no standard ClaimTypes name for this one, so I picked an arbitrary URN. Caution: this feature hasn't been released yet, and it's possible (though unlikely) that this string might change between now and when the feature is released in the next version of Saturn.
With those four claim types mapped automatically, I found that I didn't need to specify any additional claims, so I left the final parameter to use_google_oauth as an empty list in my demo app. But if you want more (say you want to get the user's preferred language to use in your localization) then just add them to that list, e.g.:
use_google_oauth googleClientId googleClientSecret "/oauth_callback_google" ["language", "urn:google:language"]
And then once someone has logged in, look in the User.Claims seq for a claim of type "urn:google:language".
Note re: the emails[] list in the JSON: I haven't tested this with a Google account that has multiple emails, so I don't know how ASP.NET picks an email to put in the ClaimTypes.Email claim. It might just pick the first email in the list, or it might pick the one with a type of account; I just don't know. Some experimentation might be needed.
Also note that third-party OAuth, including GitHub and Google, has been split into a new Saturn.Extensions.Authorization package. It will be released on NuGet at the same time that Saturn's next version (probably 0.7.0) is released.
Making the button
Once you have the use_google_oauth call in your application, create something like the following:
let googleUserIdForRmunn = "106310971773596475579"
let matchUpUsers : HttpHandler = fun next ctx ->
// A real implementation would match up user identities with something stored in a database, not hardcoded in Users.fs like this example
let isRmunn =
ctx.User.Claims |> Seq.exists (fun claim ->
claim.Issuer = "Google" && claim.Type = ClaimTypes.NameIdentifier && claim.Value = googleUserIdForRmunn)
if isRmunn then
printfn "User rmunn is an admin of this demo app, adding admin role to user claims"
ctx.User.AddIdentity(new ClaimsIdentity([Claim(ClaimTypes.Role, "Admin", ClaimValueTypes.String, "MyApplication")]))
next ctx
let loggedIn = pipeline {
requires_authentication (Giraffe.Auth.challenge "Google")
plug matchUpUsers
}
let isAdmin = pipeline {
plug loggedIn
requires_role "Admin" (RequestErrors.forbidden (text "Must be admin"))
}
And now in your scope (NOTE: "scope" will probably be renamed to "router" in Saturn 0.7.0), do something like this:
let loggedInView = scope {
pipe_through loggedIn
get "/" (htmlView Index.layout)
get "/index.html" (redirectTo false "/")
get "/default.html" (redirectTo false "/")
get "/admin" (isAdmin >=> htmlView AdminPage.layout)
}
And finally, let your main router have a URL that passes things to the loggedInView router:
let browserRouter = scope {
not_found_handler (htmlView NotFound.layout) //Use the default 404 webpage
pipe_through browser //Use the default browser pipeline
forward "" defaultView //Use the default view
forward "/members-only" loggedInView
}
Then your login button can just go to the /members-only route and you'll be fine.
Note that if you want multiple OAuth buttons (Google, GitHub, Facebook, etc) you'll probably need to tweak that a bit, but this answer is long enough already. When you get to the point of wanting multiple OAuth buttons, go ahead and ask another question.

Registering new users via OAuth2 : what to set as user identifier for future log ins?

I have managed to successfully configure this. The problem is, when I change the lines below :
//I have set all requested data with the user's username
//modify here with relevant data
$user->setUsername($username);
$user->setEmail($username);
$user->setPassword($username);
into the information I want to retrive, such as real name, email, my generated password etc, when I click the Login button for Facebook per say, I am asked again if I want to connect with my local testing site.
From what I understand, in the documentation I linked above, this :
$user = $this->userManager->findUserBy(array($this->getProperty($response) => $username));
is the line that checks if the user exists or not, and the initial code by itself, sets either facebook_id or twitter_id (this is how I save them) as a new User *username*. If I change the line
$user->setUsername($username); //same as facebook/twitter _id
into
$user->setUsername(setProperUsername()); //sets a proper unique username
Then everytime I try to login I get the "Register" message. So, I have a general idea of how it works but I am having a hard time understanding some things:
1. When I have registered with Facebook and I login with twitter, I register again, no knew row is created, but missing twitter_id fields are updated/populated, username stays intact. How come HWI/FOSUB knows I am the same person when my previous data were from Facebook not Twitter?
2. If there is a global way of knowing I am the same person, what data from the $response object should I use as a key to identify already registered users?
After testing a lot with this, I have the answer if anyone runs into this type of situation
Check your default_target path, make it so it is /profile, /account etc, don't default to login again. Also, if a user is already logged in, do not make him access your login page. This was why my data was being updated. I was basically logged in with my Facebook account and registering with my Twitter account too.
No, there is no global way of knowing I am the same person. The $response object sent me a unique ID for that specific user according to the provider policy. You might use this to identify already registered users and log them in.

"An active access token must be used" (Dec 2012) && The proper way to set up Facebooks PHP-SDK for Ajax calls to get the user-id

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

Drupal: access permissions for php scripts?

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.

Resources