PHP - Explain steps needed for my SSO authentication - php

I have built a lot of internal applications at my company. And my programming is on the the database/app level. Most of my applications have been using the same login/authentication class for years. However, said company wants everything going through their SSO authentication application. They have sent me their SSO application URL, the fields names they are passing, my sites actionURL, and my appKey.
All of my applications are on the same domain. I understand that I can set cookies to work on entire domain and have that set. Right now I am a bit stuck because I wrote some very simple test code. It is taking users to the SSO server and authenticating and sending the back over to my actionURL which is just my root of my domain.
So I am trying to request a URL that is behind my code, it shows that I need to be authenticated, passes me to the SSO app, and then I get passed back to my actionURL. What do I need on my actionURL to allow me to navigate through my apps.
Current code:
session_start();
if ($_POST && isset($_POST['digest']))
{
$digest = $_POST["digest"];
// set the session variables ...
$_SESSION['username'] = $_POST["firstname"]." ".$_POST["lastname"];
$_SESSION['firstname'] = $_POST["firstname"];
$_SESSION['lastname'] = $_POST["lastname"];
$_SESSION['email'] = $_POST["email"];
$_SESSION['uid'] = $_POST["uid"];
// Needed for key
$uid = $_POST["uid"];
$time = $_POST["time"];
// Read the property file with the key and URL so this won't go into the main code ...
// this sets $appKey and $safeurl
require_once("safe_login_properties.php");
$mykey = "".$uid.$time.$appKey;
$mydigest = md5($mykey);
if ($debugthis) // enable this for some debugging
{
$fp = fopen("DebugInfo.txt", "a");
fprintf($fp, "%s\n", date("H:i:s"));
foreach($_POST as $p => $v)
{
fprintf($fp, "POST: %s: %s\n", $p, $v);
}
foreach($_SESSION as $p => $v)
{
fprintf($fp, "SESSION: %s: %s\n", $p, $v);
}
fprintf($fp, "mykey: %s (uid: %s, time %s, key %s)\n", $mykey, $uid, $time, $appKey);
fprintf($fp, "posted digest: %s\n", $digest);
fprintf($fp, "mydigest: %s\n", $mydigest);
if ($digest == $mydigest)
fprintf($fp, "Digest match\n");
else
fprintf($fp, "***** digest does not match\n");
fclose($fp);
}
if ($digest != $mydigest)
{
die("Invalid login - expected digest does not match");
}
}
if (!isset($_SESSION['username']) || empty($_SESSION['username']))
{
// Read the property file with the key and URL so this won't go into the main code ...
// this sets $appKey and $safeurl
require_once("safe_login.php");
header("Location: ".$safeurl);
}
Additional info - All of my applications are mysql and php based. So I need to bring my mysql piece into it. I need something that says yes you are authorized, your username was passed over, and now we will look you up in the mysql database and use your corresponding row for rights/access. Does that make sense? Trying to write that part right now.

In your case, there has to be a mapping between the users in your MySQL db and the SSO server. A simple one would be an email address. A better one would be a GUID - globally unique ID. Whetever it is, the SSO server should pass a unique ID identifying the user when it calls your actionURL ( in SSO terms... your callback url).
Once you get the unique ID, retrieve the user from your MySQL DB using that ID. If he exists, log him in.
Normally, the SSO server will create a cookie which your applications can see. This should indicate if your user is logged in or not ( so you don't have to keep going back to the Server to authenticate a user).
HTH

Related

how to filter ldap active directory users information based on IIS windows authentication and display that information in php website?

I'm creating an intranet PHP website without any login requirements. I have an IIS application (based on PHP), authentication is done with Windows Authentication (Anonymous Authentication is disabled). I've successfully managed to set up IIS and windows authentication with some GPO tweaks. My simple PHP page contains $_SERVER['REMOTE_USER']; so active directory user without any login prompts can see DOMAIN\User.Name
In my understanding IIS Windows authentication is very limited and can only display the user's name and domain name. So I enabled LDAP to display more information about the user such as display name or phone number. But I'm stuck here because as far as I know, LDAP uses username and password bind to retrieve information. When I use active directory admin credentials it gives me a table of all user's information but how to filter that table to display only current user information (based on windows authentication).
Code:
<?php
$current_user = get_current_user();
$ldap_password = 'AdminPassword';
$ldap_username = 'Administrator#domain.name';
$ldap_connection = ldap_connect("domain.name");
if (FALSE === $ldap_connection){
echo 'ERROR';
}
ldap_set_option($ldap_connection, LDAP_OPT_PROTOCOL_VERSION, 3) or die('Unable to set LDAP protocol version');
ldap_set_option($ldap_connection, LDAP_OPT_REFERRALS, 0);
if (TRUE === ldap_bind($ldap_connection, $ldap_username, $ldap_password)){
$ldap_base_dn = 'OU=Users,DC=domain,DC=name';
$search_filter = '(|(objectCategory=person)(objectCategory=contact))';
$result = ldap_search($ldap_connection, $ldap_base_dn, $search_filter);
if (FALSE !== $result){
$entries = ldap_get_entries($ldap_connection, $result);
echo '<h2>Result</h2></br>';
echo '<table border = "1"><tr><td>Username</td><td>Last Name</td><td>First Name</td></tr>';
for ($x=0; $x<$entries['count']; $x++){
$LDAP_samaccountname = "";
if (!empty($entries[$x]['samaccountname'][0])) {
$LDAP_samaccountname = $entries[$x]['samaccountname'][0];
if ($LDAP_samaccountname == "NULL"){
$LDAP_samaccountname= "";
}
} else {
$LDAP_uSNCreated = $entries[$x]['usncreated'][0];
$LDAP_samaccountname= "CONTACT_" . $LDAP_uSNCreated;
}
//Last Name
$LDAP_LastName = "";
if (!empty($entries[$x]['sn'][0])) {
$LDAP_LastName = $entries[$x]['sn'][0];
if ($LDAP_LastName == "NULL"){
$LDAP_LastName = "";
}
}
//First Name
$LDAP_FirstName = "";
if (!empty($entries[$x]['givenname'][0])) {
$LDAP_FirstName = $entries[$x]['givenname'][0];
if ($LDAP_FirstName == "NULL"){
$LDAP_FirstName = "";
}
}
echo "<tr><td><strong>" . $LDAP_samaccountname ."</strong></td><td>" .$LDAP_LastName."</td><td>".$LDAP_FirstName."</td></tr>";
}
}
ldap_unbind($ldap_connection);
echo("</table>");
}
?>
EDIT: Managed to filter current user by editing LDAP filter:
$search_filter = "(|(objectCategory=persons)(sAMAccountName=*$current_user*))";
Your query is almost right, but it's working in a roundabout way :)
There is no objectCategory called persons. It's just person (no "s"). So objectCategory=persons is always false for every object on your domain. But it's working because you're using an OR (|).
So the only criteria it's really using is sAMAccountName=*$current_user*. But that's asking for any object where sAMAccountName contains $current_user. That has two unintended consequences:
If you have a user called neil, and another called oneil, then whenever neil logs in, you will find both accounts in that search.
Because your search criteria starts with a wildcard (*), it cannot use the index to find the account. That means that it has to look through every object on your domain to find a match. That might not matter if you have a small domain, but the more objects you have on your domain, the longer it will take.
IIS is giving you the exact username, so there is no need to make a "contains" comparison. So your query can be simplified to:
(sAMAccountName=$current_user)
Since sAMAccountName is an indexed attribute, that will be a super fast query.
You will often see the added criteria of limiting the search to user accounts, like this (notice the &):
(&(objectClass=user)(objectCategory=person)(sAMAccountName=$current_user))
But really, only users can authenticate to IIS, and the sAMAccountName is unique across all object types, so it doesn't really matter.

concrete5 programmatically created user can't log in

I've got a C5 site wherein I need to programmatically create user accounts. What I've done is use the register method of the UserInfo class, which seems to work. Problem is none of the users created this way are able to log in, the C5 login page just returns "Invalid email address or password."
I've checked and they are showing up in the users table. I've tried copying the password hash for a user who can log in, but the programmatically created user still can't log in. I've also asked another dev I know and after looking at the code says it's practically identical to how he's done this in the past. At this point I'm a bit stumped on how to move forward with troubleshooting this.
If it makes a difference the site is set to use email addresses instead of usernames for login.
//name = guy incognito \ both passed in
//email = 1234#5678.com \ via POST
function getRandomString($length) {
$seed = chr(mt_rand(97 ,122)).substr(md5(time( )), 1);
$rand = "";
for($y = 0; $y <= $length; $y++) {
$rand .= substr($seed, rand(0, strlen($seed)), 1);
}
return $rand;
}
$pswd = getRandomString(8);
$userData['uName'] = md5($name . rand(1, 1000) . date('Y-m-d H:i:s'));
$userData['uEmail'] = $email;
$userData['uPassword'] = $pswd;
$userData['uPasswordConfirm'] = $pswd;
$ui = UserInfo::register($userData);
Here's a screen capture of the entry created in the user table:
http://i.stack.imgur.com/N1ebw.png (unfortunately I lack the reputation to post images directly)
Edit - 19/09/2014
Having worked on this a little further I've traced the problem down into the checkPassword function of the 3rd party phpass library C5 is using. The hash it's generating when it checks during login is different than the hash generated at the time of account creation.
account creation hash = $2a$12$6UZ//BGdH6sO2AhfykvyHOLfzR2ADOuQVnzcFu6P9FckbJ56Y40WW
login attempt hash = $2a$12$6UZ//BGdH6sO2AhfykvyHOoxM727vGVnxo.3VsFYwDjKUM13SJqtO
After looking at concrete/core/dashboard/users/add.php I noticed they were registering users in a slightly different manner. Here they aren't passing a password confirmation, are specifying a default language, and most noticeably not calling UserInfo::Register but instead UserInfo::Add.
After updating my code to reflect how it was being done there things work. For the record:
$uName = md5($_POST['repName'] . rand(1, 1000) . date('Y-m-d H:i:s'));
$data = array('uName' => $uName, 'uPassword' => $authCode, 'uEmail' => $_POST['repEmail'], 'uDefaultLanguage' => 'En');
$ui = UserInfo::add($data);

transferring data by get, php in the url and security

Here is the first question and I need your help.
I transfer form data from first page using header location method in php to second page.
On the second page I accept the data using get.
Now here the url of 2nd page, after the data is sent (i.e. form is submitted)
http://mydomain.com/site1/form1_conf.php?id=123
When user is on second page, the data on second page is being displayed according the id number from the mysql database.
Now the problem is that when the user is on second page and he changes the number (for ex. 123 to say 78) the data of id=78, from the database is displayed, which is no good.
How can I stop that?
Please Note: I can't use post, nor can I use sessions.
EDITE:
php code on first page, to transfer to second page:
// after all validations are okay
$insert = //insert into database
$result = mysql_query($insert);
if($result)
{
echo("<br>Input data is succeed");
$lastInsertedId = mysql_insert_id();
header('Location:form1_conf.php?id='.$lastInsertedId); //THIS IS THE IMPORTANT LINE
}
else
{
$message = "The data cannot be inserted.";
$message .= "<br />" . mysql_error();
}
Your problem is not with the URLs: to a power user changing cookies or POST-variables is as trivial as editing GET-variables for a regular user. You'll need some way to 'sign' the requests as being valid.
Easiest to do this is with a "pre-shared key", which you use with one-way hashes to validate requests.
Redirector:
$newURL = '/newpage?id='.$id.'&hash='.sha1('mypresharedkey'.$id);
header('HTTP/1.1 303 See other');
header('Location: '.$newURL);
die;
The other page:
$idToShow = $_GET['id'];
$hash = sha1('mypresharedkey'.$id);
if($hash != $_GET['hash'])
die("Tssss, don't play with the address bar!");
else
RenderThePage();
This ensures end users can only access pages they've been allowed to by the submit.
For your specific code:
...all prior code
$lastInsertedId = mysql_insert_id();
$timestamp = time();
header('Location:form1_conf.php?'.http_build_query([
'id' => $lastInsertedId,
'time' => $timestamp,
'hash' => sha1('some-generated-key'.$timestamp.$lastInsertedId)
]);
In the other page, including a timebomb if you want (otherwise just comment it out):
$id = $_GET['id'];
$time = $_GET['time'];
if($_GET['hash'] != sha1('some-generated-key'.$time.$id))
die('URL was tampered with');
if(time() - $time > 300)
die('URL was only valid for 5 minutes');
You need to track the user and the id that they have in your database to make sure that they haven't changed the number. So when you get the information via the GET you make sure that it is legit.
Users can change the id or even attempt to go directly to that page via the url. So you need some sort of server-side check to verify that it is ok.
You could complicate this "cheating" a bit, if you didn't pass the ID number directly, but somehow encrypted it.
Let's say, you define a salt:
define(SALT, 'long weird salt with special characters etc.');
Here comes the first part you want:
$lastInsertedId = mysql_insert_id();
$querytag = base64_encode($lastInsertedId); // just to make it less readable
$checksum = md5($querytag . SALT); // and make a hash
header('Location:form1_conf.php?id=' . $querytag . '&checksum=' . $checksum);
At the beginning of form1_conf.php, you put this:
$encodedId = $_GET['id'];
$oldChecksum = $_GET['checksum'];
$newChecksum = md5($encodedId . SALT);
$id = base64_decode($encodedId);
if($newChecksum != $oldChecksum) {
die('You Are Cheating!');
}
... do something with the $id ...
The point is that since you add SALT to the hash, some user can't simply use md5 on a changed ID, because he's missing the SALT you used.
It'd be even better if the salt wasn't the same every time.
You should never trust the url because there is always a way to manipulate the data.
So you should do validation after retreiving the data. If the result does not fit you: for example the loggedin user with the ID = 1 requests the settings page from the userid = 3 you do not show the result.
<?php
$userID = $_GET['id'];
if($userID != $expectedResult)
{
//show errormessage, redirect or show the page with the users data
}
?>

How can I change username or email or phone number information in LDAP?

I have this script through which I can change my LDAP password but I also want to change my username or full name or email or phone number. How can I do that? When I echo out the records I only get info like my name and email but what do I need to do to make ldap_modify change my full name or phone number or email or userid?
<?php
$server = "ldap://ldap";
$dn = "ou=People,DC=ssdfg,DC=sadad,DC=com";
$message = array();
function changePassword($server,$dn,$user,$oldPassword,$newPassword,$newPasswordCnf){
global $message;
error_reporting(0);
$con=ldap_connect($server);
ldap_set_option($con, LDAP_OPT_PROTOCOL_VERSION, 3);
$findWhat = array ("cn","mail");
$findWhere = $dn;
$findFilter = "(uid=$user)";
#bind anon and find user by uid
$sr = ldap_search($con,$dn,$findFilter,$findWhat);
$records = ldap_get_entries($con, $sr);
echo "<pre>";print_r($records);
/* error if found more than one user */
if ($records["count"] != "1") {
$message[] = "Error E100 - Wrong user.";
return false;
}else {
$message[] = "Found user <b>".$records[0]["cn"][0]."</b>";
}
/* try to bind as that user */
if (ldap_bind($con, $records[0]["dn"], $oldPassword) === false) {
$message[] = "Error E104 - Current password is wrong.";
return false;
}
else { }
if ($newPassword != $newPasswordCnf ) {
$message[] = "Error E101 - New passwords do not match! ";
return false;
}
if (strlen($newPassword) < 8 ) {
$message[] = "Error E102 - Your new password is too short! ";
return false;
}
if (!preg_match("/[0-9]/",$newPassword)) {
$message[] = "Error E103 - Your password must contain at least one digit. ";
return false;
}
if (!preg_match("/[a-zA-Z]/",$newPassword)) {
$message[] = "Error E103 - Your password must contain at least one letter. ";
return false;
}
/* change the password finally */
$entry = array();
$entry["userPassword"] = "{SHA}" . base64_encode( pack( "H*", sha1( $newPassword ) ) );
if (ldap_modify($con,$records[0]["dn"],$entry) === false){
$message[] = "E200 - Your password cannot be change, please contact the administrator.";
}
else {
$message[] = " Your password has been changed. ";
//mail($records[0]["mail"][0],"Password change notice : ".$user,"Your password has just been changed.");
}
}
?>
Change your $findwhat variable to have a * in it and you will get all of the user attributes that your account you are executing the search with can see. Note that anonymous might not be able to see much and it certainly won't be able to update much. My advice is to create an account in your directory that has all the privileges you will need and do all of your operations under that (except for authentication of course).
Modifying the other attributes should just be a matter of including them in your $entry array. You need to use the proper attribute names but you'll see them when you print them out after changing $findwhat to a *.
Also Apache Directory Studio is a nice free tool for working with directories. One thing cool about it is that you can view the search and modification logs and see the ldif operations it is sending to the directory. Then you can replicate that in your code.
Attributes must be requested unless the ALL_ATTRIBUTES value is used, often this is an asterisk, but not always. The directory server must allow clients to retrieve values, and userPassword is often restricted to users with more privileges.
To modify attribute values, construct a modify request with the distinguished name, attribute, and the new values.
There are couple things of note:
The LDAP client should check for response controls when the server transmits a response to a request. Failure to check for response controls will result in the LDAP client possibly missing important information from the server
Why does this client base 64 encode the password? Directory server should never accept pre-encoded passwords (servers should be configured to reject pre-encoded passwords) because the server cannot execute password quality and history checks on pre-encoded passwords. For these and other reasons, transmitting pre-encoded passwords are a terrible idea, and all clients should reject this practice. LDAP clients must use a secure connection (SSL, TLS, or ipsec) and transmit passwords in the clear or use an external SASL bind request. For even better security use the password modify extended request which requires the existing password, and can generate a password for the user.
This client does not appear to be able to respond to an unsolicited notification from the server. Directory Servers may transmit an extended result that is unsolicited, that is, not in response to a client request. Clients must be able to handle these notifications which are often notifications that the server disconnected the client for whatever reason.
For more information, please see "LDAP: Programming Practices".

PHP Session Security Tips

this is what I'm doing currently to create sessions on login page.
if($count==1) {
$_SESSION['username'] = $username;
$_SESSION['password'] = $password;
}
i know this is very basic and I need to protect the user sessions. Can u provide me some basic tips. If you could edit the code and write the secure one, it would be great. Thanks.
Currently, I am using the email address as session username.
Ask your self this question:
Why am I storing the password when the username is unique in the database
After you have answered that you should of come to the conclusion that its pointless, you can either store the username or the user id in the session when it comes to login systems.
How login systems tend to work is that the user sends the username password from a form to the server where its validated, during the validation process you select the user from from the database where username = post_username.
If there is no rows found the user does not exists so you can directly send output at that point, if the user does exist you then compare the password with the post_password.
the reason why we specifically select the row by just the username is that you should be incorporating some sort of hashing system to add extra security.
if you stored the password as (password + hash) which would be a new string, you would also store just the hash aswell, thus if a user is found then you can create a hash from (post_password + db_hash) and check to see if its the same as the db_password.
this way if your database gets leaked somehow your users credentials are more secure.
once the user has been validated you would store the user id within the session, and then on every page load you can check if the id is within the session and if it is the user is currently logged in and you can select the users data by SELECT * FROM users WHERE id = session_id.
This should get you started.
/*
SecureSession class
Written by Vagharshak Tozalakyan <vagh#armdex.com>
Released under GNU Public License
*/
class SecureSession {
// Include browser name in fingerprint?
var $check_browser = true;
// How many numbers from IP use in fingerprint?
var $check_ip_blocks = 0;
// Control word - any word you want.
var $secure_word = 'random_string_here';
// Regenerate session ID to prevent fixation attacks?
var $regenerate_id = true;
// Call this when init session.
function Open()
{
$_SESSION['ss_fprint'] = $this->_Fingerprint();
$this->_RegenerateId();
}
// Call this to check session.
function Check()
{
$this->_RegenerateId();
return (isset($_SESSION['ss_fprint'])
&& $_SESSION['ss_fprint'] == $this->_Fingerprint());
}
function Destroy()
{
// Unset all of the session variables.
$_SESSION = array();
// If it's desired to kill the session, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time()-42000, '/');
}
// Finally, destroy the session.
session_destroy();
}
// Internal function. Returns MD5 from fingerprint.
function _Fingerprint()
{
$fingerprint = $this->secure_word;
if ($this->check_browser)
$fingerprint .= $_SERVER['HTTP_USER_AGENT'];
if ($this->check_ip_blocks)
{
$num_blocks = abs(intval($this->check_ip_blocks));
if ($num_blocks > 4)
$num_blocks = 4;
$blocks = explode('.', $_SERVER['REMOTE_ADDR']);
for ($i=0; $i<$num_blocks; $i++)
{
$fingerprint .= $blocks[$i] . '.';
}
}
return md5($fingerprint);
}
// Internal function. Regenerates session ID if possible.
function _RegenerateId()
{
if ($this->regenerate_id && function_exists('session_regenerate_id'))
session_regenerate_id();
}
}
Common practice is to check the user name and password against the database, then on success store just the user id in the session. Then later, to see if a person is logged in or authorized, you check that user id stored in the session. Though, the session variables are only visible to the server unless you've done something horribly wrong. So its not horrible or insecure but its basically unnecessary.
Edit
Removed bit about cookies, could cause confusion.

Categories