Rails Tutorial - ERROR MESSAGE: expected response to be a <redirect>, but was <200> - railstutorial.org

The test in "Listing 8.20: A test for user logging in with valid information" is giving me a persistent error message:
FAIL["test_login_with_valid_information", UsersLoginTest, 1.051300315]
test_login_with_valid_information#UsersLoginTest (1.05s)
Expected response to be a <redirect>, but was <200>
test/integration/users_login_test.rb:22:in `block in <class:UsersLoginTest>'
The test is:
19 test "login with valid information" do
20 get login_path
21 post login_path, session: { email: #user.email, password: 'password' }
22 assert_redirected_to #user
23 follow_redirect!
24 assert_template 'users/show'
25 ....
The Sessions controller is what I am imagining is causing this error message
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to user # What I want to happen
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new' # What is happening
end
end
If the user is authenticated, it should redirect to the users profile page
else the login form will render
So the problem I believe is that my test user is not being authenticated. The application login and redirect functions just fine when I login manually on the site.
test/fixtures/users.yml has this to pass the password 'password'
password_digest: <%= User.digest('password') %>
and the user model has this to digest the password 'password'
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
Where does the problem lie? and further more how can I make another test that shows me why this test is failing?

I had a similar (identical) issue if I'm reading this right and for the sake of clarity for future browsers I'll add the following: As Shonin pointed out the issue has to do with the controller taking downcase email addresses and so apparently in users.yml the code
michael:
name: Michael Example
email: Michael#example.com
password_digest: <%= User.digest('password') %>
Will not pass while
michael:
name: Michael Example
email: michael#example.com
password_digest: <%= User.digest('password') %>
Will pass.

after a good nights sleep I realized it was likely due to a database error. Low and behold my database had a capitalized email and my controller submits a downcased email.
Solved.

Related

The "state" param from the URL and session do not match

In facebook documantion
require('include/facebook/autoload.php'); //SDK directory
$fb = new Facebook\Facebook([
'app_id' => '***********',
'app_secret' => '***********************'
]);
$helper = $fb->getRedirectLoginHelper();
$permissions = ['email', 'public_profile']; // optional
$loginUrl = $helper->getLoginUrl('http://www.meusite.com.br/login-callback.php', $permissions);
When direct it to the url $loginUrl, the return is:
Facebook SDK returned an error: Cross-site request forgery validation failed. The "state" param from the URL and session do not match
I had the same error.
The problem occurred because I did getLoginUrl(...) before getAccessToken()
So rid of getLoginUrl(...) in redirected URL and code should works.
I had the same issue and for me that error was occurring because I did not put session_start(); in my login.php page code before calling getLoginUrl(..) and also at the top of login-callback.php page.
Just put session_start(); in your "login" page and "login-callback" page and it will work surely just like it is working for me now.
There could be 2 reason for this error:
you didn't call session_start(); before getLoginUrl call
You executed getLoginUrl again in login-callback.php, so state value regenerated and mismatched with the redirected value
Possible Fixes : I used the following configuration settings .
Enable WebAuthLogin under the advanced tab . Provide the url in the WebAuthLogin settins as same as that you provide in $loginUrl ;
For example if you use $loginUrl as https://example.com/ use that same in the WebAuthlogin Url
$loginUrl = $helper->getLoginUrl('https://example.com/', $permissions);
This problem occures also in case that you generate 2 or more login links on the same page (e.g. one for login and other for registration - even both point to the same url, they have just different labels).
Facebook SDK creates/updates $_SESSION[FBRLH_state] for each new generated loginURL. So if there are 2 generated URLs (using $helper->getLoginUrl()) then the $_SESSION[FBRLH_state] is 2-times rewritten and valid only for the last generated URL. Previous login URL becomes invalid. It means that it is not possible to generate 2 valid loginURLs. In case that 2 same URLs are generated then return the first one and avoid call of Facebook SDK for generation of second one.
I had the same problem.
The reason for this error is because --->
When "$helper->getLoginUrl" calls, it create a session variable "FB_State", and this is something to FB uses to match the token. Every-time getLoginUrl calls, it create new state. Then after user authorized and redirect back, if you codes cannot detect this event and re-run "$helper->getLoginUrl", then this error will occur.
The solution ->
refine your coding, stop run "$helper->getLoginUrl" again if authorized.
if you already rerun, then set the session variable for the token to NULL if you have, then User can re-authorize again.
when user tries re-authorize, they can remove the authorized APP once or you need to generate new link with "$helper->getReRequestUrl"
Yet, token has be called by "getAccessToken()" before the "$helper->getLoginUrl" or "$helper->getReRequestUrl" runs.
Good Luck!!!!!
Finally, looking into FB code, I discovered that the problem "Cross-site request forgery validation failed. Required param “state” missing" and similars are caused by PHP variable $_SESSION['FBRLH_state'] that for some "strange" reason when FB call the login-callback file.
To solve it I store this variable "FBRLH_state" AFTER the call of function $helper->getLoginUrl(...). Is very important to do only after the call of this function due to is inside this function when the variable $_SESSION['FBRLH_state'] is populated.
Below an example of my code in the login.php:
$uri=$helper->getLoginUrl($uri, $permissions);
foreach ($_SESSION as $k=>$v) {
if(strpos($k, "FBRLH_")!==FALSE) {
if(!setcookie($k, $v)) {
//what??
} else {
$_COOKIE[$k]=$v;
}
}
}
var_dump($_COOKIE);
And in the login-callback.php before calling all FB code:
foreach ($_COOKIE as $k=>$v) {
if(strpos($k, "FBRLH_")!==FALSE) {
$_SESSION[$k]=$v;
}
}
Last, but not least, remember also to include code for PHP session so..
if(!session_id()) {
session_start();
}
...
...
...
...
<?php session_write_close() ?>
I hope this response can help you to save 8-10 hours of work :)
Bye, Alex.
This issue was a bit confusing for me, because I had to change a line at the facebook src file:
src/Facebook/Helpers/FacebookRedirectLoginHelper.php
at the function: "validateCsrf" like this:
if ($result !== 0) {
throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.');
}
And change it into:
if ($result === 0) {
throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.');
}
I don't know if this makes a violation to the facebook SDK security, so I truly opened to any exlanation or recommendation for this answer.
You may also make the following changes at the facebook app manager:
add your site and callback-url into your facebook app account at:
setting->advanced:Valid OAuth redirect URIs
Don't forget to add another url with slash (/) at the end of each url and check all 4 checkboxes at Client OAuth Settings.
I had the same error. Are you using 1 file or 2? I was trying to get by using 1 file but my error was resolved when I split into login.php & fb-callback.php as the documentation recommended. My sessions were being re-written so the state was never saved properly.
Good luck!
Happens when the session in missing a needed variable.
might be caused by several things.
In my case I left the "www" out of the callback URL
You could actually be parsing the data from another domain... for example:
website.com is different from www .website.com
If you're parsing data from http ://website.com/login.php to http://www.website.com/fb-callback.php this would be a cross-domain problem and the error you are receiving would be because of that....
http ://website.com and http ://www.website.com are the same but the script identifies them as different..... hope that gives insight to the problem.

Facebook page tab app session across subpages PHP SDK 4

See the full original question further down
Using the latest Facebook PHP SDK 4.4.0, in my main app page I can do the following to get a user id etc.
<?php
FacebookSession::setDefaultApplication(APP_ID, SECRET);
$helper = new FacebookRedirectLoginHelper( PAGE_URL );
$pageHelper = new FacebookPageTabHelper();
$session = $pageHelper->getSession();
echo '<p>You are currently viewing page: '. $pageHelper->getPageId() . '</p>';
// get user_id
echo '<p>User Id: ' . $pageHelper->getUserId() . '</p>';
// **depcrecated** get like status - use for likegates
echo '<p>You have '. ( $pageHelper->isLiked() ? 'LIKED' : 'NOT liked' ) . ' this page</p>';
// get admin status
echo '<p>You are '. ( $pageHelper->isAdmin() ? 'an ADMIN' : 'NOT an ADMIN' ) . '</p>';
?>
This does not work on sub pages of my app ... Why is the session (and amongst other things, the signed request) lost? How can I get them back and how can I get methods such as getUserId() from the the FacebookPageTabHelper to continue to work on sub pages?
full original question
I'm fairly new to Facebook app development and I'm having problems with session management and I just can't seem to be able to wrap my head around it. Of course it doesn't help that the official documentation is almost useless.
My problem is that the page session get lost when moving away from the apps main page to a subpage within the Facebook page tab app iframe.
I use the following PHP code to obtain the session and user id on the main (initial) app page and it works great:
<?php
FacebookSession::setDefaultApplication(APP_ID, SECRET);
$helper = new FacebookRedirectLoginHelper( PAGE_URL );
$pageHelper = new FacebookPageTabHelper();
$session = $pageHelper->getSession();
?>
But it doesn't work on sub pages :( when a user clicks on a menu item (or any other link inside the app/iframe), the session goes bye bye. Which is not ideal as I need the user id of the user to track whether or not that user has completed certain actions. Of course I could send the ID along with every request, but there must be a way to have a persisting session, no?
Is there a way to retrieve the session on a sub page in PHP? If so, how? Or do I have to load additional content using javascript? And how would that work, if I can't keep the session between requests and therefore have no way of identifying which user a request came from? How do others handle this?
What I'd like to avoid is to write my own user session management, which would solve the problem but is simply not in the budget and I was hoping I could work with what Facebook already had on offer. Especially since my app doesn't require user information/permissions of any kind.
Thanks a lot in advance for any info on this topic, greatly appreciated, going in circles here.
Edit to clarify: I thought of just saving the Facebook session in a PHP session cookie, but how would I use that to reconnect with Facebook after changing the page?
I finally managed to solve this problem. I'm not sure whether this is considered the right way or can even be a recommended way of doing this, but it works and since time is of the essence, I don't have much of a choice.
If anybody has any further ideas or suggestions, please comment.
Here's how I did it:
// store the signed request
if(isset($_REQUEST['signed_request'])) {
$_SESSION['signed_request'] = $_REQUEST['signed_request'];
} elseif($_SESSION['signed_request']) {
$_REQUEST['signed_request'] = $_GET['signed_request'] = $_POST['signed_request'] = $_SESSION['signed_request'];
}
// assign the stored signed request to REQUEST, GET and POST vars (the unsavory bit, imo)
$_REQUEST['signed_request'] = $_GET['signed_request'] = $_POST['signed_request'] = $_SESSION['signedRequest'];
FacebookSession::setDefaultApplication(APP_ID, APP_SECRET);
$accessToken = APP_ID . '|' . APP_SECRET;
$this->session = new FacebookSession($accessToken);
$pageHelper = new FacebookPageTabHelper();
$isAdmin = ($this->pageHelper->getPageData('admin')) ? $this->pageHelper->getPageData('admin') : 0;
// get pade id
echo '<p>You are currently viewing page: '. $pageHelper->getPageId() . '</p>';
// get user_id
echo '<p>User Id: ' . $pageHelper->getUserId() . '</p>';
// get admin status
echo '<p>You are '. ( $isAdmin ? 'an ADMIN' : 'NOT an ADMIN' ) . '</p>';

why facebook email is not retrieved?

After user accept to sign in with his facebook account, my app callback will be called by facebook, I've noticed that facebook didn't retrieved user email, although in the configuration of omniauth, I've listed email in the permission list like this:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, omniauth_app_id, omniauth_app_secret_id,
:scope => 'email,user_birthday,read_stream', :display => 'popup'
end
I am expecting to find email in request.env['omniauth.auth']['extra']['raw_info']['email'] or request.env['omniauth.auth']['info']['raw_info']['email']
But, its not there .. in fact, its not in any attribute within the request object!
Any idea ? is it related to my app facebook settings ?
EDIT
Here is the call back result from Facebook:
puts auth.inspect
#<OmniAuth::AuthHash credentials=#<Hashie::Mash expires=true expires_at=1353164400 token="***"> extra=#<Hashie::Mash raw_info=#<Hashie::Mash first_name="***" id="***" last_name="***" link="***" locale="ar_AR" name="***" timezone=2 updated_time="2012-11-17T13:01:59+0000" username="***" verified=true>> info=#<OmniAuth::AuthHash::InfoHash first_name="***" image="***" last_name="***" name="***" nickname="***" urls=#<Hashie::Mash Facebook="***"> verified=true> provider="facebook" uid="***">
I have replaced reall data with *, but, you can see that the email data is missing ..
EDIT2
Here is my gems used at Gemfile
gem 'devise'
gem 'omniauth'
gem 'omniauth-facebook', '1.4.0'
gem 'oauth2'
Ok, I found it! Here is what facebook says here
By default, calling FB.login will attempt to authenticate the user
with only the basic permissions. If you want one or more additional
permissions, call FB.login with an option object, and set the scope
parameter with a comma-separated list of the permissions you wish to
request from the user.
So, I have to add 'email' like this:
FB.login(function(response) {
// handle the response
}, {scope: 'email,user_likes'});
Thanks very much for all people who tried to help :-)
It's possible to have an facebook account without confirm the email. In this case, the request.env['omniauth.auth']['info'] has no 'email' key.
We use devise and omniauth for facebook - following the following railscast. I don't know if you can adapt but we have successfully pulled the email and some other info. from the callback.
http://asciicasts.com/episodes/241-simple-omniauth
Our controller action looks like this (condensed):
def create
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authentication
sign_in_and_redirect(:user, authentication.user)
elsif current_user
current_user.authentications.create(:provider => omniauth ['provider'], :uid => omniauth['uid'])
redirect_to authentications_url
else
user = User.new
user.apply_omniauth(omniauth)
if user.save
sign_in_and_redirect(:user, user)
else
session[:omniauth] = omniauth.except('extra')
redirect_to new_user_registration_url
end
end
end
User.apply_omniauth:
def apply_omniauth(omniauth)
authentications.build(:provider => omniauth['provider'], :uid => omniauth['uid'])
end
We get the email, as advise by someone else, using this:
#omniauth_email = session[:omniauth][:info][:email]
I hope this helps, I know it's not quiet what you're looking for.
-- edit --
My initialiser is different to yours. Try removing all that xs stuff and use something like this:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, 'xxxxxx', 'xxxxxx'
...
end
How about fetching it from the info hash?
request.env['omniauth.auth']['info']['email']
I think that it is what omniauth-facebook recomends in the Auth Hash section

How can I redirect to login_path if email uniqueness validation fails?

I am creating my very first Rails and MVC app. It is a website for my wedding guests to create their RSVPs.
I have a single form that is deeply nested. An RSVP has_one User and has_many Guests.
This form creates a User, RSVP, and Guests all in one go. There is also a link to edit an existing RSVP through a login_path.
I have validates_uniqueness_of :email on the User model. I would like to redirect to the login_path if a user attempts to create a new RSVP when they've already created one, i.e. the :email :uniqueness validation fails.
How can I redirect to the login_path if the :email :uniqueness validation fails?
You would need to test in your controller action if the unique validation failed for the that email and redirect if that's the case. That said, I'm not sure if there is better way to know if a certain validation failed for an specific field than just comparing the error message, as follows:
if #user.save
# do success actions
else
if #user.errors[:email] == 'has already been taken' # ugh
respond_to |format|
format.html { redirect_to login_path(email: #user.email) }
end
else
# handle other errors
end
end

Facebook Credits Example on App Engine?

Are there any examples of using facebook credits on Google App Engine?
I found this blog post , but it's not complete
http://blog.suinova.com/2011/01/integrating-facebook-credits-api-into.html
I got the sample runwithfriends example working on the App Engine, tried to expand it with Credits, no luck so far.
Also searched for the FB developer forums, got nothing.
Any resources you can point me to?
What's not working:
1) When I click on the "pay with Facebook" button, I get an "Application Error" , without any error code.
-Checked the javascript console
-Checked the fb app settings
-Tried on local server and production server
2) The callback.py isn't complete, because i could not parse the signed request (no code available in py for me to learn from)
3) What I basically did was to add code from Suinova Designs (link above) to the existing Run With Friends app code. Didn't turn out as expected.
my code so far:
//payment_page.html
<html>
<table>
<tr><th>Name</th><th>Price</th><th> </th></tr>
<tr><td>Something to buy</td><td>10 FC</td><td><a href="" onclick="return buyit();">
<img src="http://www.facebook.com/connect/button.php?app_id=215638625132268&feature=payments&type=light_l" />
</a></td></tr>
</table>
// javascript
function buyit(){
FB.ui({
method:'pay',
purchase_type:'item',
order_info:{
item_id:'myitem',
title:'Something to buy',
price:2,
description:'Whatever',
image_url:'http://www.facebook.com/images/gifts/21.png',
product_url:'http://www.facebook.com/images/gifts/21.png'}
},
function(resp){
if(resp.order_id) window.top.location='http://apps.facebook.com/runwithfriends trial'; else alert(resp.error_message);
});
return false;
}
//callback.py
class FacebookPaymentRequest(webapp.RequestHandler):
def post(self):
signed_request = parse_signed_request(self.request.get('signed_request'),conf.FACEBOOK_APP_SECRET)
payload = signed_request['credits'] #credits:{buyer:int,order_id:int,order_info:{},receiver:int}
order_id = payload['order_id']
method = web.request.get('method')
return_data = {'method':method}
if method == 'payments_get_items':
order_info = payload['order_info'] #order_info:{item_id:'',title:'',description:'',price:0,image_url:'',product_url:''}
item = simplejson.loads(order_info) #needs to convert JSON string to dict
#check item,price,etc and reply
return_data['content'] = [item]
elif method == 'payments_status_update':
status = payload['status']
return_data['content'] = {'status':'settled','order_id':order_id}
if status == 'placed':
#just return settled status to Facebook, this may be called twice
order_details = simplejson.loads(payload['order_details'])
#check and log here
elif status == 'settled':
order_details = simplejson.loads(payload['order_details'])
#order_details:{order_id:0,buyer:0,app:0,receiver:0,amount:0,update_time:0,time_placed:0,data:'',items:[{}],status:'placed'}
buyer = order_details['buyer']
item = order_details['items'][0]
#now save this transaction into our database
else:
#may be refunded, canceled, log the activity here
return_data['content']['status'] = status
self.response.out.write(simplejson.dumps(return_data))
Your python code looks fairly normal so I would guess that you are simply having trouble with your authorization. Depending upon how you authorize (a process a fair amount more complicated that the credits system), you are likely being given a signed request that is only partially authorized... meaning you are authorized to access only certain parts of facebook, but generally not authorized to access the active/logged-in user (i.e. me).
You can verify this by determining if you signed_request is a full 80+ characters (as opposed to around 40). Generally I try to authenticate by deciphering the profile (signed_request), if that fails then I try to use a previously stored cookie, then if that fails I try to relogin the user. I determine failure by placing try/except around my calls to get a "me" object through the GraphAPI.