omniauth-saml nil values in info hash - ruby-on-rails

I'm working with devise and omniauth-saml, following the instructions from https://github.com/PracticallyGreen/omniauth-saml and the omniauth Facebook example https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview
I have a simplesamlphp server running, using the metadata provided by omniauth, and everything seems to be connecting properly, but the response from the simplesaml server has nil values for email, first_name, last_name and name. The weird thing is that it is returning the email and name values, just not in a useful part of the response (see below - super#man.com is the email, Clark Kent is the name)
The weird thing is that I get these nil values with both omniauth-saml and devise-saml-authenticatable gems, but I think my simplesamlphp server is configured properly.
The response from omniauth looks like this:
#<OmniAuth::AuthHash credentials=#<OmniAuth::AuthHash> extra=#<OmniAuth::AuthHash raw_info=#<OneLogin::RubySaml::Attributes:0x007fc695850148 #attributes={"urn:oid:0.9.2342.19200300.100.1.1"=>["super#man.com"], "urn:oid:2.16.840.1.113730.3.1.241"=>["Clark Kent"], "fingerprint"=>"FINGERPRINT REMOVED"}>> info=#<OmniAuth::AuthHash::InfoHash email=nil first_name=nil last_name=nil name=nil> provider="saml" uid="_ca76f49ccb6ccce7827111ae1ff0563f534f0a4d1a">
The simplesamlphp config looks like this:
$metadata['http://localhost:3000/saml/users/auth/saml/metadata'] = array(
'AssertionConsumerService' => 'http://localhost:3000/saml/users/auth/saml/callback',
'NameIDFormat' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:email',
'simplesaml.nameidattribute' => 'email',
);
I am sure that there are good values for the user in the database, and when I test authentication on the IDP itself, everything works as expected.

I solved this. The problem was that I was trying to use values that didn't use the ldap attribute names.
I changed my sp metadata as follows (after modifying my model to use first_name and last_name instead of just name)
$metadata['http://localhost:3000/saml/users/auth/saml/metadata'] = array(
'AssertionConsumerService' => 'http://localhost:3000/saml/users/auth/saml/callback',
'NameIDFormat' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:email',
'authproc' => array(
95 => array(
'class' => 'core:AttributeMap',
'givenName' => 'first_name',
'sn' => 'last_name',
'mail' => 'email',
),
96 => array(
'class' => 'core:AttributeLimit',
'email', 'first_name', 'last_name'
),
),
'simplesaml.nameidattribute' => 'email',
);

Related

Check if the user is authenticated on not secured route

I'm trying to build a simple login using symofny/security package in Silex, but I have a small problem with authentication checking.
The structure:
/
/login
/admin
/login_check
/logout
In order to get to the /admin route user needs to be authenticated, if he's not, he gets redirected to /login form, which then, as recommended, is asking admin/login_check for authentication (that's all provided by symofny's security firewalls).
The firewalls configuration:
'security.firewalls' => array(
'login' => array(
'pattern' => '^/login$',
),
'admin' => array(
'pattern' => '^/admin',
'http' => true,
'form' => array(
'login_path' => '/login',
'check_path' => '/admin/login'
),
'logout' => array(
'logout_path' => '/admin/logout',
'invalidate_session' => true
),
'users' => ...
),
)
Everything works fine, but the user can enter the /login route even-though he's already authenticated, which is not that bad, but I'd like to avoid it. I tired to check the user authentication status, but I guess it does not work as I'm checking it on /login controller, which is not in "secured" area of the website.
Here's the code:
public function index(Request $request, Application $app)
{
if ($app['security.authorization_checker']->isGranted('ROLE_ADMIN')) {
return $app->redirect($app['url_generator']->generate('admin'));
}
return $app['twig']->render('login/index.twig', array(
'error' => $app['security.last_error']($request),
'last_username' => $app['session']->get('_security.last_username'),
));
}
That throws an error: The token storage contains no authentication token. One possible reason may be that there is no firewall configured for this URL. So, the question, is there any way to do this using symfony/security natively (without any walk-arounds)? or is it somehow possible to create the /login route inside secured area with possibility to access it even-though the user is not logged in (e.g. make an exception for GET request) which would solve the problem?
UPDATE
I've also added the firewall configuration for /login page with an option anonymous: true, which resulted in not throwing an error anymore, but yet, when I'm logged in on the /admin route, method isGranted('ROLE_ADMIN') results in true, whereas on /login route it results in false (I'm still logged in there).
You can easily understand the behavior of the security component by dumping the current token.
public function index(Request $request, Application $app)
{
var_dump($application['security.token_storage']->getToken());
}
When you don't set anonymous option for login page (by default it is false):
null
When you set anonymous option for login page to true:
object(Symfony\Component\Security\Core\Authentication\Token\AnonymousToken)
private 'secret' => string 'login' (length=5)
private 'user' (Symfony\Component\Security\Core\Authentication\Token\AbstractToken) => string 'anon.' (length=5)
private 'roles' (Symfony\Component\Security\Core\Authentication\Token\AbstractToken) =>
array (size=0)
empty
private 'authenticated' (Symfony\Component\Security\Core\Authentication\Token\AbstractToken) => boolean true
private 'attributes' (Symfony\Component\Security\Core\Authentication\Token\AbstractToken) =>
array (size=0)
empty
Following example describes why you are getting error in your initial code example.
How to share security token?
To share security token between multiple firewalls you have to set the same Firewall Context.
SecurityServiceProvider registers context listener for protected firewall you have declared with pattern security.context_listener.<name>. In your example it is registered as security.context_listener.admin. Thus context you want to use is named admin.
Also a firewall context key is stored in session, so every firewall using it must set its stateless option to false.
What is more, to invoke authentication on login page the anonymous option must be set to true
'security.firewalls' => array(
'login' => array(
'context' => 'admin',
'stateless' => false,
'anonymous' => true,
'pattern' => '^/login$',
),
'admin' => array(
'context' => 'admin',
'stateless' => false,
'pattern' => '^/admin',
'http' => true,
'form' => array(
'login_path' => '/login',
'check_path' => '/admin/login'
),
'logout' => array(
'logout_path' => '/admin/logout',
'invalidate_session' => true
),
'users' => ...
),
)
After the change, if you are logged in on admin panel and login page you will get an instance of Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken as a token and
Symfony\Component\Security\Core\Authentication\Token\AnonymousToken if you are not logged in.
Because of that, you can safely check permission using $app['security.authorization_checker']->isGranted('ROLE_ADMIN') and get the same results on both firewalls.

ZF2 mail error Too many RSET commands

I have to send a lot of emails distributed over the whole day with ZF2 and I'm using Zend\Mail\Transport\Smtp. I have the mails in a queue table and once a minute I want to send them. Everything works fine until 5th email, where I receive an error : Zend\Mail\Protocol\Exception\RuntimeException
4.7.0 Too many RSET commands; closing connection
I guess this is coming from the smtp server. But why does ZF2-Smtp reset each message? I don't know where to start debugging the problem and if you need more informations just let me know. Thx for any help...
What I'm using is:
$transport = new SmtpTransport();
$options = new SmtpOptions(array(
'host' => $config['mail_options']['server'],
'port' => $config['mail_options']['port'],
'connection_class' => 'login',
'connection_config' => array(
'username' => $config['mail_options']['smtp_user'],
'password' => $config['mail_options']['smtp_password'],
'ssl' => $config['mail_options']['ssl'],
),
));
foreach ($queuedMails as $queuedMail) {
$message = new Message();
$message->addTo($data['to'])
->addFrom($config['mail_options']['from'], $config['mail_options']['from_name'])
->setSubject($data['subject'])
->setBody(utf8_decode($data['body']))
->setEncoding('utf-8');
$transport->setOptions($options);
$transport->send($message);enter code here
}
Should I place the $transport into the foreach?

OAuth signature generation using HMAC-SHA1?

I apologize for asking so many questions but none of them seem to be getting answered and I really need help on this. I'm using LTI to integrate my program into a learning management system, and I need to authenticate using OAuth. I have no trouble generating a signature following the guidelines here but the signature I generate never matches the one passed to me by the LMS, and I can't figure out for the life of me why they never match. I'm hoping that it's something I'm oblivious to, but I really need some assistance on this.
When I launch my program from the LMS, I am sent this array via POST in what is called the LTI launch:
array(
'launch_presentation_locale' => 'EN-US__',
'tool_consumer_instance_guid' => 'key',
'tool_consumer_instance_name' => 'MyProgram',
'tool_consumer_instance_description' => 'MyProgram',
'tool_consumer_instance_contact_email' => 'johndoe#email.com',
'tool_consumer_info_version' => '10.3.0 SP5',
'tool_consumer_info_product_family_code' => 'desire2learn',
'context_id' => '2440554',
'context_title' => 'ContextTitle',
'context_label' => 'ContextTitle',
'context_type' => '',
'user_id' => 'USER_ID',
'roles' => 'None',
'lis_person_name_given' => 'John',
'lis_person_name_family' => 'Doe',
'lis_person_name_full' => 'John Doe',
'lis_person_contact_email_primary' => 'johndoe#email.com',
'ext_tc_profile_url' => 'https://profileurl.com',
'ext_d2l_token_id' => '123456789',
'ext_d2l_link_id' => '1234',
'ext_d2l_token_digest' => 'AbCdEfGhIjKlMnOpQrStUvWxYzi=',
'resource_link_id' => '',
'resource_link_title' => 'MyProgram',
'resource_link_description' => 'MyProgram',
'lis_result_sourcedid' => 'abcdefgh-ijkl-mnop-qrst-uvwxyz012345',
'lis_outcome_service_url' => 'https://outcomeserviceurl.com',
'lti_version' => 'LTI-1p0',
'lti_message_type' => 'basic-lti-launch-request',
'oauth_version' => '1.0',
'oauth_nonce' => '123456789',
'oauth_timestamp' => '1234567890',
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_consumer_key' => 'key',
'oauth_callback' => 'about:blank',
'oauth_signature' => 'eFUR8O5xVydLrj4PDj37nF4cq6A=',
'basiclti_submit' => 'Launch Endpoint with BasicLTI Data'
);
Here is what I'm trying. I've added comments to clarify the steps:
// Set variables that are required for the signature to be generated.
$OAUTH_KEY = 'key';
$OAUTH_SECRET = 'secret';
$httpMethod = 'POST';
$SITE_URL = 'https://localhost/test.php';
// make array copy of entire POST data, remove the 'oauth_signature' field as specified in the oauth spec from the copy array, then sort alphabetically. After that, url encode the key/value of each item in the copy array and store into a string for later use.
$request_parameter_array = $_POST;
unset($request_parameter_array['oauth_signature']);
ksort($request_parameter_array);
$request_parameter_str = '';
foreach($request_parameter_array as $key => $value) {
$request_parameter_str .= rawurlencode($key) . '=' . rawurlencode($value) . '&';
}
// create the signature base string (string variable that the actual signature is created from) by following these steps from the OAuth documentation:
// 1. The HTTP request method in uppercase. For example: "HEAD",
// "GET", "POST", etc. If the request uses a custom HTTP method, it
// MUST be encoded (Section 3.6).
// 2. An "&" character (ASCII code 38).
// 3. The base string URI from Section 3.4.1.2, after being encoded
// (Section 3.6).
// 4. An "&" character (ASCII code 38).
// 5. The request parameters as normalized in Section 3.4.1.3.2, after
// being encoded (Section 3.6).
$key = rawurlencode($OAUTH_SECRET) . '&';
$signature_base = strtoupper($httpMethod) . '&' . rawurlencode($SITE_URL) . '&';
$signature_base .= rawurlencode($request_parameter_str);
$signature = base64_encode(hash_hmac("sha1", $signature_base, $key, true));
echo $signature;
I guess my own stupidity was the issue, here. The issue was arising from D2L itself because I misunderstood what the difference was between using a tool link vs. a tool provider for my integrations. I literally deleted my tool provider and went with a tool link and now I'm able to authenticate every time.
Turns out there wasn't a problem with the code at all here.

hybridauth's social_hub/login.php doesn't work

I install the hybridauth on windows IIS, and setup the hybridauth\config.php as below:
array(
"base_url" => "http:///hybridauth/",
"providers" => array (
.....
"Google" => array (
"enabled" => true,
"keys" => array ( "id" => "<myappkey>", "secret" => "<myappsecret>" ),
"scope" => "email"
),
)
But when I click "Sign-in with Google" in http:///examples/social_hub/login.php, it just redirect me to http:///hybridauth/?hauth.start=Google&hauth.time=1401608000 which show the files under "localhost - /hybridauth/"
Anyone know how to fix it?
Thank you!
1) i am not sure if your scope is properly set up (you should have probably set it to "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email"
2) did you set up the return address in google api correct?

HowTo Write tests for Devise & Omniauth in my Rails 3 app?

I have a Rails 3 app with Omniauth and Devise. I haven't been able to find a solid tutorial showing how I should write specs for testing the functionality.
What I want to achieve is to ensure that the Sign Up and Sign In forms are working... meaning users can create accounts. I also want to be sure that the Omniauth FB Connect works as well at all times.
What/how can I write test for these scenarios above?
Thanks
The tests were very useful to test the pretty complex method I have to process authentication from Yahoo, Facebook, Google and AOL. Especially the edge cases.
This should get you started, it is the first of my 10+ scenarios.
.feature file
Feature: Signinin with third party service: Google, Aol, Facebook and Yahoo
In order to use the site
As a User
I want to sign in using my Facebook or Gmail accounts
Before do
OmniAuth.config.test_mode = true
end
After do
OmniAuth.config.test_mode = false
end
Scenario Outline: Sign with valid names and emails
Given that I have a valid "<provider>" account with email "<email>" and name "<name>"
And that I am not signed in
And I go to the sign in page
When I follow image link "<provider>"
Then I should see "You signed in using your <provider> account ("
And I should see "Welcome to Death Star"
Examples: valid name and email variations
| provider | email | name |
| Google | lukeskywaler#gmail.com | luke skywlaker |
| Facebook | darth.vader#aol.com | darth vader |
my_step.rb helper:
Given /^that I have a valid "([^"]*)" account with email "([^"]*)" and name "([^"]*)"$/ do |provider, email, name|
OmniAuth.config.test_mode = true
if provider.downcase == "yahoo" or provider.downcase == "google" or provider.downcase == "aol"
# the symbol passed to mock_auth is the same as the name of the provider set up in the initializer
OmniAuth.config.mock_auth[:open_id] =
{
'provider' => "#{provider}",
'uid' => "#{provider}.com",
'user_info' => { 'email' => "#{email}", 'name' => "#{name}"}
}
else
# the symbol passed to mock_auth is the same as the name of the provider set up in the initializer
OmniAuth.config.mock_auth[:facebook] = {
'provider' => 'facebook',
'uid' => "531564247",
'credentials' => {
'token'=> "18915faketoken22|2.NKt21XnznTNEDTTERDGYXI2UUw__.3600.1302234329200-531564247|Mi0DhWREl6g-T9bMZnL82u7s4MI"
},
'user_info' => {
'nickname' => "profile.php?id=53232564247",
'email' => "#{email}",
'first_name' => "Luke",
'last_name' => "Skywalker",
'name' => "Luke Skywalker",
'image' => "http://graph.facebook.com/5asd54247/picture?type=square",
'urls' => {
'facebook' => "http://www.facebook.com/profile.php?id=5asd54247",
'website' => ""
}
},
'extra' => {
'user_hash' => {
'id' => "5asd54247",
'name' => "#{name}",
'first_name' => "#{name.split(' ')[0]}",
'last_name' => "#{name.split(' ')[1]}",
'link' => "http://www.facebook.com/profile.php?id=5asd54247",
'birthday' => "12/7/1932",
'hometown' => {
'id' => "104048449631599",
'name' => "Menlo Park, California"
},
'location' => {
'id' => "104048449631599",
'name' => "Menlo Park, California"
},
'gender' => "male",
'email' => "#{email}",
'timezone' => "-7",
'locale' => "en_US",
'verified' => true
}
}
}
end
end
Hope this help.

Resources