I have a class with contact form validation and a trait with a function which creates a token which is used for the validation.
I have tried to follow some advice from other stackoverflow posts but still can't figure out how to pass that token to the validation class. If I remove the token from the validation process it works fine.
My trait:
trait TokenTrait
{
public $token;
function __construct ()
{
if (!isset($token)) {
return $this->token = bin2hex(random_bytes(64));
}
}
}
which is used in a class:
class Token
{
use TokenTrait;
}
Below a class with contact form validation. I have included only validation of the 'name' field for sake of simplicity. Also, I'm not including code for the function test_input() which is used here as it is irrelevant.
class ContactForm
{
use TokenTrait;
public $name;
public $name_error;
public $token;
function __construct()
{
$this->name = isset($_POST['name']) ? $_POST['name'] : null;
$this->name_error = isset($_POST['name_error']) ? $_POST['name_error'] : null;
$this->token = isset($_POST['token']) ? $_POST['token'] : null;
}
function validateContactForm()
{
if (!empty($this->token)) {
// Validate the token.
if (hash_equals($_SESSION['token'], $_POST['token'])) {
// If the token is valid proceed.
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if (isset($_POST['submit'])) {
// Rest of the validating code which works if I remove above token
// or if I validate $_SESSION['token'] against itself.
}
}
}
}
}
}
And this is what i have in my view file:
$token_obj = new Token();
$token = $token_obj->token; // Variable used in a hidden input field in the contact `<form>` .
//The same token is stored in two different superglobals: POST and SESSION, which are validated against each other and if they don't match no message is not sent.
$_SESSION['token'] = $token;
// Declare variables used in the contact form.
$name = $email = $message = '';
$name_error = $email_error = $message_error = $result = '';
$contact_form_obj = new ContactForm();
$contact_form_obj->validateContactForm();
When I do var_dump($contact_form_obj), name is NULL, token is NULL.
When I press SUBMIT in my contact form nothing happens bacuse $_POST['token'] = NULL .
Like the comments pointed out, I think there are some problems with the approach that would likely rectify your issues. Try this:
trait TokenTrait
{
public $token = null;
function setToken()
{
if ($this->token === null) {
$this->token = bin2hex(random_bytes(64));
}
return $this->token;
}
}
class Token
{
use TokenTrait;
public function __construct() {
$this->setToken();
}
}
class ContactForm
{
use TokenTrait;
public $name;
public $name_error;
public $token;
public function __construct()
{
$this->name = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING) ?: null;
$this->name_error = filter_input(INPUT_POST, 'name_error', FILTER_SANITIZE_STRING) ?: null;
$this->token = filter_input(INPUT_POST, 'token', FILTER_UNSAFE_RAW) ?: null;
}
public function validateContactForm()
{
if (!empty($this->token)) {
// Validate the token. No need to pull from post since you did that in construct
if (hash_equals($_SESSION['token'], $this->token)) {
// If the token is valid proceed.
if (getenv('REQUEST_METHOD') == 'POST') {
if (filter_input(INPUT_POST, 'submit') !== null) {
// Rest of the validating code which works if I remove above token
// or if I validate $_SESSION['token'] against itself.
}
}
}
}
}
}
Granted, I can't see where all the pieces, such as your TokenTrait, fit into your larger Web App, but I'd question it's usefulness in the ContactForm class.
I think I may have found the answer.
class Token
{
public $token;
function __construct()
{
$this->token = bin2hex(random_bytes(64));
}
}
$token_obj = new Token();
if (empty($_SESSION['token'])) {
$_SESSION['token'] = $token_obj->token;
}
$token = $_SESSION['token'];
If you don't validate $_SESSION['token'] first, it won't work.
Thank you guys for clarifying all this for me.
Related
I'm new to PHP. I'll like to check the scope of the variables I've used. In particular $model.
$model = new LoginModel();
$controller = new LoginController($model);
$view = new LoginView($controller, $model);
Attached below are codes I have written for logging in. A user would visit the page via GET /login.php then submits the form to POST /login.php?action=login. In this process the LoginModel is updated accordingly by LoginController.
I would like to use the $model that I have updated in later parts of the execution of the page. However, I noticed that $model is "reset" once the call returns from LoginController.login().
I'm not sure if it is because $model was passed by value in my case. Or if there is something else I'm doing wrong but I'm not aware of. Can anyone enlighten me on this?
<?php
class LoginModel {
public $username = "";
public $password = "";
public $message = "";
public $loginSuccess = false;
public function __construct() {
}
}
class LoginView {
private $model;
private $controller;
public function __construct($controller, $model) {
$this->controller = $controller;
$this->model = $model;
}
public function getUsernameField() {
return $this->makeInput("text", "username", "");
}
public function getPasswordField() {
return $this->makeInput("password", "password", "");
}
private function makeInput($type, $name, $value) {
$html = "<input type=\"$type\" name=\"$name\" value=\"$value\" />";
return $html;
}
}
class LoginController {
const HOME_URL = "http://localhost/";
private $model;
public function __construct($model) {
$this->model = $model;
}
public function login() {
$username = $_POST['username'];
$password = $_POST['password'];
if ($username === $password) {
$_SESSION['username'] = $username;
$model->username = $username;
$model->password = "";
$model->message = "Hello, $username!";
$model->loginSuccess = true;
header("Refresh: 3; URL=" + LoginController::HOME_URL);
} else {
$model->message = "Sorry, you have entered an invalid username-password pair.";
$model->loginSuccess = false;
}
}
public function handleHttpPost() {
if (isset($_GET['action'])) {
if ($_GET['action'] === 'login') {
$this->login();
}
} else {
// invalid request.
http_response_code(400);
die();
}
}
public function handleHttpGet() {
if (isset($_GET['action'])) {
// request for controller action
// No controller actions for HTTP GET
} else {
// display login page
}
}
public function redirectToHome() {
header("Location: " + LoginController::HOME_URL);
die();
}
}
$model = new LoginModel();
$controller = new LoginController($model);
$view = new LoginView($controller, $model);
if (isset($_SESSION['username'])) {
$controller->redirectToHome();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$controller->handleHttpPost();
} else if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$controller->handleHttpGet();
}
?>
<html>
<body>
<?php if ($model->loginSuccess) { ?>
<h1>Login Successful, Redirecting...</h1>
<p><?= $model->message; ?></p>
<?php
} else { ?>
<form action="login.php?action=login" method="POST">
Username: <br />
<?php echo $view->getUsernameField(); ?> <br/><br/>
Password: <br />
<?php echo $view->getPasswordField(); ?><br/><br/>
<input type="submit" value="Log In"/>
</form>
<p><?= $model->message; ?></p>
<?php
}?>
</body>
</html>
Update: Solved. Thanks #RiggsFolly for pointing it out.
Well, I got the wrong$model.
public function login() {
....
$model->username = $username; //referenced the wrong variable
$this->model->username = $username //should have done this.
}
Also, Thanks #Fred -ii-
(sorry I left you out)
public function login() {
....
header("Refresh: 3; URL=" + LoginController::HOME_URL); //not the right way to concat
header("Refresh: 3; URL=" . LoginController::HOME_URL); //should have been this.
}
Only a simple statement
for because $model was passed by value
We have an object in $model and pass it to the constructor
$controller = new LoginController($model);
it will be bound as reference to
$controller->model = $model.
Now we have an reference of the model in the controller.
If you know do (if possible, lets say yes) this: unset($controller->model);
you dont have killed the $model instance, you have just removed the reference to $model that was set before.
But now the other way around:
#create an object
$model = new stdClass();
#create the holder
$b=new stdClass();
#bind the first obj
$b->model=$model;
#unset the first object
$model=null;
unset($model);
#oooh, what it is still there
print_r($b->model);
Here we have not unset($model) for real, because php knows that the instance is used later. So php goes and kills the reference between $model and the real object, but not the reference between $b->model and the real object.
In a way the reference has moved vom one ref-pointer to the next.
Last thing about
By default, function arguments are passed by value (so that if the value of the argument within the function is changed, it does not get changed outside of the function).
that comes from php documentaion.
When here is written passed by value it means, how it will work.
But in the real process, it will copied in the moment when it is manipulated.
so it is passed by ref but act like passed by value and only objects will be always an reference until clone.
To keep the most important Infos in this answer:
For one thing, header("Refresh: 3; URL=" + LoginController::HOME_URL);
you're trying to concatenate with a + which in PHP it's a dot that
needs to be used. You seem to be coming from a C background, hence the
error.
Thanks to Fred -ii-
So I made a registration page and i'm also using sessions to flash messages.
The session is called 'flash' and I made an abstraction class for sessions to make it easier.
This is my Session class:
public static function exists($name)
{
return (isset($_SESSION[$name]) ? (true) : (false));
}
public static function set($name, $value)
{
return $_SESSION[$name] = $value;
}
public static function delete($name)
{
if(self::exists($name)) {
unset($_SESSION[$name]);
}
}
public static function get($name)
{
if(self::exists($name)) {
return $_SESSION[$name];
}
return '';
}
public static function hasFlash()
{
return (Session::exists('flash') ? (true) : (false));
}
public static function addFlash($message)
{
if(Session::exists('flash'))
{
$msgArray = (Array) Session::get('flash');
array_push($msgArray, $message);
Session::set('flash', (Array) $msgArray);
}
else
{
$msgArray = array();
array_push($msgArray, $message);
Session::set('flash', (Array) $msgArray);
}
}
public static function flash($message = null)
{
if(self::hasFlash()) {
$msgArray = (Array) Session::get('flash');
Session::delete('flash');
foreach($msgArray as $message) {
echo $message . "<br>";
}
}
return '';
}
And this is my registration page:
$hasError = false;
$username = Functions::escape(Input::get('user'));
$password = Functions::escape(Input::get('password'));
$email = Functions::escape(Input::get('email'));
$nick = Functions::escape(Input::get('nick'));
if(User::userExists($username, $db)) {
Session::addFlash("User $username is taken, try another one.");
$hasError = true;
}
if(User::emailExists($email, $db)) {
Session::addFlash("Email $email is taken, try another one.");
$hasError = true;
}
if(!$hasError)
{
User::addUser($username, $email, $password, $nick, $db);
Session::addFlash("You have successfully registered.");
Session::addFlash("You can now login with $username.");
Functions::location("index.php");
}
That's the code i'm using to display the flash messages:
<?php if(Session::hasFlash()) : ?>
<div class="form-group">
<?php Session::flash(); ?>
</div>
<?php endif; ?>
However it only works on the registration page for instance when a user types in a username/email that is taken, the code above will show the message though when I register the user and send him to the index page, the 2 success messages won't show up. I do have session_start at the top of the page.
The issues seemed to resolve by adding the following:
// server should keep session data for AT LEAST 1 hour
ini_set('session.gc_maxlifetime', 3600);
// each client should remember their session id for EXACTLY 1 hour
session_set_cookie_params(3600);
I have a problem here on PHP OOP. I try to do something that I always do in .NET - pass the whole object to the function. Unfortunately, the script didn't appear to work and when I try to debug (using Netbeans) it stopped here:
$ud = new userdetails($fullname, $email, $contact, $username, $password, $password2);
Can somebody tell me what did I do wrongly? Thanks in advance!
My script:
<?php
include 'class/registration.php';
$fullname = $_POST['fullname'];
$email = $_POST['email'];
$contact = $_POST['contact'];
$username = $_POST['username'];
$password = $_POST['password'];
$password2 = $_POST['password2'];
$ud = new userdetails($fullname, $email, $contact, $username, $password, $password2);
if (registration::checkEmptyField($ud)==true){
$error = "Please don't leave any field empty";
}
userdetail class:
<?php
class userdetails {
protected $_fullname;
protected $_email;
protected $_contact;
protected $_username;
protected $_password;
protected $_password2;
public function __construct($fullname,$email,$contact,$username,$password,$password2) {
$this->_fullname = $fullname;
$this->_email = $email;
$this->_contact = $contact;
$this->_username = $username;
$this->_password = $password;
$this->_password2 = $password2;
}
public function get_fullname() {
return $this->_fullname;
}
public function get_email() {
return $this->_email;
}
public function get_contact() {
return $this->_contact;
}
public function get_username() {
return $this->_username;
}
public function get_password() {
return $this->_password;
}
public function get_password2() {
return $this->_password2;
}
}
registration class:
<?php
class registration {
function checkEmptyField(&$userdetails){
if ($userdetails-> get_fullname == ''){
return true;
}
elseif ($userdetails->get_email == ''){
return true;
}
elseif ($userdetails->get_contact == ''){
return true;
}
elseif ($userdetails->get_username == ''){
return true;
}
elseif ($userdetails->get_password == ''){
return true;
}
elseif ($userdetails->get_password2 == ''){
return true;
}
}
}
You ask for a property, not a method here: $userdetails-> get_fullname
Correct way: $userdetails-> get_fullname()
You should always turn on the error reporting, because this should have been reported by php.
The way you call registration::checkEmptyField() requires it to be declared as static.
<?php
class registration {
static function checkEmptyField(userdetails $userdetails) {
...
}
}
There is no need to prepend $userdetails with &, in PHP the objects are always passed by reference. It's better to use type hinting: prepend the parameter name ($userdetails) with its expected type (class userdetails in this case).
I would like to know that if there is a method in the controller which require arguments and the user changes the argument in the URL by hand, and presses enter it should display the default page. Below is my bootstrap, then I have already created a error controller for URL error. So please give some coding guide or if there is some thing in my code, change it. Thanks in advance.
<?php
class App
{
protected $controller = 'indexController';
protected $method = 'index';
protected $params = array();
public function __construct()
{
$url = $this->parseUrl();
//print_r($url);
if (isset($url[0]))
{
if (file_exists('app/controllers/'.$url[0].'.php'))
{
$this->controller = $url[0];
unset($url[0]);
}
require_once('app/controllers/'.$this->controller.'.php');
$this->controller = new $this->controller;
}
else
{
$error = new errorController();
$error->setError("Page Not Found");
echo $error->getError();
}
if (isset($url[1]))
{
if (method_exists($this->controller,$url[1]))
{
$this->method = $url[1];
unset($url[1]);
}
else
{
$error = new errorController();
$error->setError("Page Not Found");
echo $error->getError();
}
}
else
{
$error = new errorController();
$error->setError("Page Not Found");
echo $error->getError();
}
$this->params = $url ? array_values($url) : array();
call_user_func_array(array($this->controller,$this->method),$this->params);
}
public function parseUrl()
{
if (isset($_GET['url']))
{
return $url =explode('/',filter_var(rtrim($_GET['url'],'/'),FILTER_SANITIZE_URL));
}
}
}
The method itself must check if the parameters provided are valid and throw an exception if not. Afterwards just catch the exception and trigger and error page to be displayed.
Seems like you could use POST and $_POST, instead of GET. Then it won't matter if the user includes or alters parameters because your PHP will ignore them.
Are there any libraries to protect against CSRF(PHP5.1/5.2) or do I need to create on myself? I use this snippet from Chris, but without a library I am getting a lot of duplication on every page.
I found this library for PHP5.3, but I am wondering if there are any on PHP5.1/5.2 because I don't believe yet all hosting support PHP5.3.
Since I use Kohana - I've just extended couple of its core classes. It can be used in any code with a little changes though:
class Form extends Kohana_Form
{
public static function open($action = NULL, array $attributes = null)
{
if (is_null($action))
{
$action = Request::current()->uri . ($_SERVER['QUERY_STRING'] ? '?' . $_SERVER['QUERY_STRING'] : '');
}
$open = parent::open($action, $attributes);
$open .= parent::hidden(self::csrf_token_field(), self::csrf_token());
return $open;
}
public static function csrf_token_field()
{
return 'csrf_token';
}
public static function csrf_token()
{
$session = Session::instance();
$token = $session->get(self::csrf_token_field());
if (!$token)
{
$session->set(self::csrf_token_field(), $token = md5(uniqid()));
}
return $token;
}
}
class Validate extends Kohana_Validate
{
public function __construct(array $array, $csrf = true)
{
parent::__construct($array);
if ($csrf)
$this->add_csrf();
}
public static function factory(array $array, $csrf = true)
{
return new Validate($array, $csrf);
}
private function add_csrf()
{
$this->rules(form::csrf_token_field(), array(
'not_empty' => array(),
'csrf' => array()
));
}
protected function csrf($token)
{
return $token == form::csrf_token();
}
}