WEBD 236
Web Information Systems Programming
Module 6
Copyright © 2013-2017
Todd Whittaker and Scott Sharkey
(sharkesc@[Link])
Agenda
• This module’s expected outcomes
• This module’s topics
• This module’s homework
• Upcoming deadlines
• Questions and answers
Module 6 Outcomes
• Employ algorithms to work with arrays and
associative arrays.
• Use common array functions.
• Describe the security implications of
session tracking.
• Employ sessions to maintain per-user data
on the server.
Arrays
• Create arrays with the array() function
$arr = array(2, 3, 5, 7, 11, 13, 17, 19);
• Indices are in the range [0, n-1] for an
array of length n.
0 1 2 3 4 5 6 7
2 3 5 7 11 13 17 19
• Use [] to access elements
$arr[3] = $arr[7] + 1;
4
Arrays
• Use the function count() to determine how
long an array is. Can then use a loop to
process it.
$arr = array(2, 3, 5, 7, 11, 13, 17, 19);
$len = count($arr);
for ($i = 0; $i < $len; ++$i) {
$arr[$i] += 1;
}
5
Arrays
• Assign a value “past” the end of the array
to add on to the end.
$arr = array();
$arr[0] = 2;
$arr[1] = 3;
$arr[2] = 5;
•Shortcut to do the same thing:
$arr = array();
$arr[] = 2;
$arr[] = 3;
$arr[] = 5;
6
Arrays
• Remove elements using unset()
$arr = array(2, 3, 5, 7, 11, 13, 17, 19);
unset($arr[3]);
unset($arr[5]);
Notice that the
0 1 2 4 6 7 valid array indices
2 3 5 11 17 19 are no longer
contiguous! This
is a hint that all
arrays are actually
associative.
7
Arrays
• Can also use a foreach loop to iterate:
array_values() is a
function myArrayValues($arr) {
$result = array(); library function that
foreach ($arr as $element) { does this.
$result[] = $element;
} 0 1 2 3 4 5
return $result; 2 3 5 11 17 19
}
$arr = array(2, 3, 5, 7, 11, 13, 17, 19);
unset($arr[3]);
unset($arr[5]);
$arr = myArrayValues($arr);
8
Arrays
• Can also use a foreach loop to iterate:
function myArrayValues($arr) {
$result = array();
foreach ($arr as $element) {
$result[] = $element;
}
return $result;
}
$arr = array(2, 3, 5, 7, 11, 13, 17, 19);
unset($arr[3]);
unset($arr[5]);
$arr = myArrayValues($arr);
9
Associative Arrays
• All PHP arrays are actually associative.
Idea: use strings (as well as integers) as
keys
$arr = array(4 => "four", "four" => 4, '5' => "five");
print_r($arr);
Array (
[4] => four Strings containing
[four] => 4 integers are just
[5] => five interpreted as
) integers.
10
Associative Arrays
• Can then index by an arbitrary string.
$person = array();
$person['lastName'] = 'Smith';
$person['firstName'] = 'Roger';
$person['dob'] = '12-Nov-1968';
print("Hello ${person['firstName']} ${person['lastName']}.");
11
Arrays of Arrays
• Commonly, arrays of associative arrays are
representation of rows and columns in a DB.
$firstNames = array('James', 'John', 'Robert', 'Michael');
$lastNames = array('Smith', 'Johnson', 'Williams', 'Jones');
$people = array();
for ($i = 0; $i < 5; $i++) {
$findex = mt_rand(0, count($firstNames) - 1);
$lindex = mt_rand(0, count($lastNames) - 1);
$person = array(
'firstName' => $firstNames[$findex],
'lastName' => $lastNames[$lindex]
);
$people[] = $person;
}
print_r($people);
12
Arrays of Arrays
• Commonly, arrays of associative arrays are Array
(
[0] => Array
representation of rows and columns in a DB. (
[firstName] => Michael
[lastName] => Johnson
$firstNames = array('James', 'John', 'Robert', 'Michael');
)
$lastNames = array('Smith', 'Johnson', 'Williams', 'Jones');
[1] => Array
$people = array(); (
for ($i = 0; $i < 5; $i++) { [firstName] => James
[lastName] => Johnson
$findex = mt_rand(0, count($firstNames) - 1);)
$lindex = mt_rand(0, count($lastNames) - 1);
$person = array( [2] => Array
(
'firstName' => $firstNames[$findex], [firstName] => John
'lastName' => $lastNames[$lindex] [lastName] => Williams
)
); ...
$people[] = $person;
}
print_r($people);
13
Arrays of Arrays
• Commonly, arrays of associative arrays are Array
(
firstName Michael [0] => Array
representation
0 of
lastName
rows and
Johnson
columns in a DB . (
[firstName] => Michael
[lastName] => Johnson
$firstNames = array('James', 'John', 'Robert', 'Michael');
)
$lastNames = array('Smith', 'Johnson', 'Williams', 'Jones');
[1] => Array
$people = array(); (
firstName James
for ($i = 0;1$i < 5; $i++) { [firstName] => James
[lastName] => Johnson
$findex = mt_rand(0, Johnson“Michael”
count($firstNames)
lastName - 1);) is stored at
$lindex = mt_rand(0, count($lastNames) - 1);
$person = array(
$people[0]['firstName']
[2] => Array
(
'firstName' => $firstNames[$findex], [firstName] => John
'lastName' =>firstName John
$lastNames[$lindex] [lastName] => Williams
2 )
); ...
lastName Williams
$people[] = $person;
}
print_r($people);
14
Arrays of Arrays
• Can then iterate over all those arrays.
foreach($people as $person) {
print("Hello ${person['firstName']} ${person['lastName']}");
}
foreach($people as $person) {
foreach($person as $key => $value) {
print("$key: $value");
}
}
15
Common Array Functions
• Full list [Link]
Function Purpose
range($lo, $hi[, $step]) Returns an array with values between
$lo and $hi, $step apart.
array_slice($arr, Returns a sub-array of $arr starting at
$index[, $len[, $keys]]) $index containing $len elements.
array_splice($arr, Replaces $len elements with $new in
$index[, $len[, $new]]) $arr starting at $index.
in_array($val, $arr) Returns true if $val appears in $arr
array_search($val, $arr) Returns the index of $val in $arr or
false if it doesn’t exist
16
Common Array Functions
• Full list [Link]
Function Purpose
sort($arr[, $compare]) Sorts an array in ascending order,
reindexing. See also rsort, asort, etc.
array_map($callback, Applies the function $callback to
$arr) every element of $arr, returning result
array.
array_shift($arr) Returns the first element, and shifts
every element down one position.
FIFO.
array_pop($arr) Same as array_shift, but with the
last element. LIFO.
17
Sessions
• Problem: HTTP is stateless – each
request/response cycle is completely
independent.
How can your program “remember” things
from one click to the next? Things like:
Who is logged in?
What page in a multi-page “wizard” are you on?
What page should you be redirected to after you
log in?
18
Sessions
• Solution: set a cookie
Cookies are set by the server, stored by the
browser, and are transmitted with every
request.
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: name=value
Set-Cookie: name2=value2; Expires=Wed, 09 Jun 2021 10:18:14 GMT
GET /[Link] HTTP/1.1
Host: [Link]
Cookie: name=value; name2=value2
Accept: */*
Source: [Link] 19
Sessions
• Problem: browser cookies can’t be trusted
GET /[Link] HTTP/1.1
Host: [Link]
Cookie: loggedin=true; rights=admin
Accept: */*
Anybody can send a request with any
cookie they wish. If we rely on cookies
for sensitive data (especially guessable
data), this is a severe security risk.
20
Sessions
• Solution: use an opaque identifier (like a
surrogate key) that references a file on the
server.
HTTP/1.1 200 OK
Content-type: text/html
Set-Cookie: PHPSESSID=jokilcf2qsckfuml9mg73jamv0
GET /[Link] HTTP/1.1
Host: [Link]
Cookie: PHPSESSID=jokilcf2qsckfuml9mg73jamv0
Accept: */*
21
Sessions
• Solution: use an opaque identifier (like a
Thisreferences
surrogate key) that isn’t guessable
a (is
file on the
it?). But, we can use it to
server. “look up” sensitive
HTTP/1.1 200 OK session data we’ve
Content-type: text/html stored on the server.
Set-Cookie: PHPSESSID=jokilcf2qsckfuml9mg73jamv0
GET /[Link] HTTP/1.1
Host: [Link]
Cookie: PHPSESSID=jokilcf2qsckfuml9mg73jamv0
Accept: */*
22
Sessions
• Solution: use an opaque identifier (like a
surrogate key) that references a file
Problem: whaton
if the
server. someone’s session
ID is hijacked? Is
HTTP/1.1 200 OK
Content-type: text/html
that even possible?
Set-Cookie: PHPSESSID=jokilcf2qsckfuml9mg73jamv0
GET /[Link] HTTP/1.1
Host: [Link]
Cookie: PHPSESSID=jokilcf2qsckfuml9mg73jamv0
Accept: */*
23
Sessions
• Problem: Open WiFi connections permit
session hijacking
Even WEP isn’t secure
What about other man-in-the-middle attacks?
Therefore, all sessions must be over an
encrypted connection.
• Solution: force all connections to be
HTTPS.
All data between browser and server is
encrypted.
24
Sessions
• Quick recap:
Force HTTPS connections
Set an unguessable cookie
Use that cookie to reference a data structure
on the server that holds per-user session
data.
25
Sessions
• Forcing HTTPS
Use .htaccess (like we did for URL rewriting)
Options +FollowSymLinks
IndexIgnore */*
# Turn on the RewriteEngine
RewriteEngine On
# Force HTTPS for security of cookies
RewriteCond %{HTTPS} !on
RewriteRule (.*) [Link] [L]
# Handle URL routing
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . [Link]
26
Sessions
• Forcing HTTPS
Use .htaccess (like we did The [L] flagrewriting)
for URL means
that if this rule
Options +FollowSymLinks matches, stop
IndexIgnore */* processing other
# Turn on the RewriteEngine
RewriteEngine On
rules. “Last rule.”
# Force HTTPS for security of cookies
RewriteCond %{HTTPS} !on
RewriteRule (.*) [Link] [L]
# Handle URL routing
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . [Link]
27
Sessions
• Forcing HTTPS
Real HTTPS connections require a certificate
signed by a signing authority (Thawte,
Verisign, etc.)
XAMPP will still encrypt, but the certificate is
self-signed, so the browser will complain.
28
Sessions
• Forcing HTTPS
Real HTTPS connections require a certificate
signed by a signing authority (Thawte,
Verisign, etc.)
XAMPP will still encrypt, but the certificate is
self-signed, so the browser will complain.
Don’t panic. It’s
okay to proceed.
We can trust our
own localhost.
29
Sessions
• Setting a cookie
Use the setcookie() function
setcookie($name, $value, $expire,
$path, $domain, $secure, $httponly);
Can read about all of these parameters in
the book. But this isn’t the path we want to
go down. PHP sessions handle setting the
session cookie for us.
30
Sessions
• Session parameters
How long should the cookie live?
What paths on the server should apply?
What’s the name of the domain to send to?
Should it only be sent on encrypted
connections?
Should only HTTP read it (no JavaScript)?
session_set_cookie_params($seconds, $path, $domain,
$secure, $httponly);
31
Sessions
• Starting a session
session_set_cookie_params(60*60*24*14, '/',
$_SERVER['SERVER_NAME'], true, false);
session_start();
•Storing data in a session
$_SESSION['loggedin'] = true;
$_SESSION['username'] = 'Fred';
32
Sessions
• Reading data from a session
function safeParam($arr, $index, $default) {
if ($arr && isset($arr[$index])) {
return $arr[$index];
}
return $default;
}
$user = safeParam($_SESSION, 'username', false);
if ($user) {
print("Hello $user!");
}
33
Sessions
• Removing data from a session
// remove a single variable
unset($_SESSION['username']);
// delete all variables
$_SESSION = array();
•Ending a session
session_destroy();
34
Sessions
• Getting the current session ID
// can be used to see if a session is active
$sessid = session_id();
•Where is session data kept?
In XAMPP it is C:\xampp\tmp.
On a production server, you schedule
cleanups.
Also possible to store session data in the
database and have a trigger clean it up.
35
Sessions
•Getting the current session ID
// can be used to see if a session is active
$sessid = session_id();
•Where is session data kept?
In XAMPP it is C:\xampp\tmp.
On a production server, you schedule cleanups.
Also possible toThis
store
file session data in the
contains the
database and have a trigger clean it up.
text representation
(serialized) of session
variables
36
Sessions
• Full list of session-related functions:
[Link]
37
Session Example
• In-depth example: adding login/logout and
minimal authentication requirements to our
ToDo List application
Specifications:
Non-logged in users can only see landing page
Logged in users can add, edit, delete ToDos
Don’t permit URL fishing
Provide login/logout capabilities
38
Session Example
Not logged in, only
viewing ToDos
39
Session Example
Logging in.
40
Session Example
Some minimal
error feedback,
keeping form data.
41
Session Example
After logging in:
add, view, edit,
delete, and log out.
42
Session Example
• URL fishing:
When not logged in, navigating directly to
[Link]
should redirect to a login screen and after
logging in, the user should be redirected
back to the todo they attempted to view.
43
Session example
• Set up your .htaccess file
Options +FollowSymLinks
IndexIgnore */*
# Turn on the RewriteEngine
RewriteEngine On
# Force HTTPS for security of cookies
RewriteCond %{HTTPS} !on
RewriteRule (.*) [Link] [L]
# Handle URL routing
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . [Link]
44
Session example
• Sessions should start immediately
// inside the [Link] file
session_set_cookie_params(60*60*24*14, '/',
$_SERVER['SERVER_NAME'], true, false);
session_start();
routeUrl();
45
Session example
• A useful function in Lib/[Link]
function isLoggedIn() {
$inSession = session_id();
if (!empty($inSession)) {
if (isset($_SESSION['loggedin'])) {
return $_SESSION['loggedin'];
}
}
return false;
}
46
Session example
• Changes to the views/[Link]
<!-- snipped top section -->
<body>
<div class="content">
[[ include_once ‘Lib/[Link]'; ]]
[[ if (isLoggedIn()) : ]]
<p class='login'><a href='@@auth/logout@@'>Log out</a></p>
[[ else : ]]
<p class='login'><a href='@@auth/login@@'>Log in</a></p>
[[ endif; ]]
Class login is a float This means we’ll
left style. need an auth
controller
47
Session example
• Changes to the views/[Link]
%% views/[Link] %%
<h1>{{$title}}</h1>
[[if (isLoggedIn()) : ]]
<form action="@@todo/add@@" method="post">
<label for="description">Description:</label>
<input type="text" id="description" name="description" />
<input type="submit" value="Add" />
</form>
[[ endif; ]]
<h2>Current To Do:</h2> Only display form if user is
<ol> logged in. Same for edit,
[[ foreach ($todos as $todo) : ]] delete and view links
<!-- more changes omitted --> (omitted)
48
Session example
• Create the controllers/[Link]
<?php
include_once “Lib/[Link]";
include_once "models/[Link]";
function get_login($params) { Called when the user
renderTemplate( clicks the ‘login’ link in
"views/login_form.inc",
the header. Just
array(
'title' => 'Login', renders the login form.
)
);
}
?>
49
Session example
• Create the views/login_form.inc
%% views/[Link] %%
<h1>{{$title}}</h1>
%% views/[Link] %%
<div class='inputs'>
<form action="@@auth/login@@" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username"
value="{{isset($username) ? $username : ''}}" />
<!– password omitted for space -->
<input type="submit" value="Login" />
<form>
</div>
<p><a href="@@index@@"><< Back</a></p>
%% views/[Link] %%
50
Session example
[Link] pulls errors out
• Create the views/login_form.inc
and reports them to the
%% views/[Link] %% user (if they exist)
<h1>{{$title}}</h1>
%% views/[Link] %%
<div class='inputs'>
<form action="@@auth/login@@" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username"
value="{{isset($username) ? $username : ''}}" />
<!– password omitted for space -->
<input type="submit" value="Login" />
<form>
</div>
<p><a href="@@index@@"><< Back</a></p>
%% views/[Link] %%
51
Session example
• Create the views/[Link]
[[ if (isset($errors)) : ]]
<p>Please correct the following errors:</p>
<ul>
[[ foreach ($errors as $error) : ]]
<li>{{$error}}</li>
[[ endforeach; ]]
</ul>
[[ endif; ]]
52
Session example
• Create the views/login_form.inc
This means we need a
%% views/[Link] %% controllers/[Link] with a
<h1>{{$title}}</h1> function post_login.
%% views/[Link] %%
<div class='inputs'>
<form action="@@auth/login@@" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username"
value="{{isset($username) ? $username : ''}}" />
<!– password omitted for space -->
<input type="submit" value="Login" />
<form>
</div>
<p><a href="@@index@@"><< Back</a></p>
%% views/[Link] %%
53
Session example
• Modify the controllers/[Link]
function post_login($params) {
$username = safeParam($_REQUEST, 'username', false);
$password = safeParam($_REQUEST, 'password', false);
if (isValidUser($username, $password)) {
$_SESSION['loggedin'] = true;
$_SESSION[‘username’] = $username;
if (isset($_SESSION['redirect'])) {
$redirect = $_SESSION['redirect'];
redirect($redirect);
exit();
}
redirectRelative("index");
} else {
// continued
54
Session example
• Modify the controllers/[Link]
function post_login($params) {
$username = safeParam($_REQUEST, 'username', false);
$password = safeParam($_REQUEST, 'password', false);
if (isValidUser($username, $password)) {
$_SESSION['loggedin'] = true;
if (isset($_SESSION['redirect'])) {
$redirect = $_SESSION['redirect'];
redirect($redirect);
exit(); isValidUser should
}
redirectRelative("index");
query the DB based
} else { on username and a
// continued hash of the password.
55
Session example
• Modify the controllers/[Link]
function post_index($params) {
$username = safeParam($_REQUEST, 'username', false);
$password = safeParam($_REQUEST, 'password', false);
if (isValidUser($username, $password)) {
$_SESSION['loggedin'] = true;
$_SESSION[‘username’] = $username;
if (isset($_SESSION['redirect'])) {
$redirect = $_SESSION['redirect'];
redirect($redirect);
exit(); In a “real” login/logout
} situation, we’d want to
redirectRelative("index");
} else {
store a user ID here.
// continued
56
SessionIf example
they attempt to access a
protected resource
without logging in, then
• Modify the controllers/[Link]
this session variable will
function post_login($params) { be set.
$username = safeParam($_REQUEST, 'username', false);
$password = safeParam($_REQUEST, 'password', false);
if (isValidUser($username, $password)) {
$_SESSION['loggedin'] = true;
if (isset($_SESSION['redirect'])) {
$redirect = $_SESSION['redirect'];
redirect($redirect);
exit();
}
redirectRelative("index");
} else {
// continued
57
Session example
• Modify the controllers/[Link]
// continued
} else {
renderTemplate(
"views/login_form.inc",
array(
'title' => 'Login',
'errors' => array("Invalid username/password"),
'username' => $username,
'password' => $password
)
); This lets
us keep the
}
} values the user
entered in already.
58
Session example
• Create models/[Link]
<?php
function isValidUser($username, $password) {
return $username == 'admin' && $password == 'nimda';
}
?>
In a real application, we’d
query the database, and
likely end up storing the
user ID in a session
variable.
59
Session example
• Modify the controllers/[Link]
<?php
include_once “Lib/[Link]";
include_once "models/[Link]"; Just destroy the
function get_logout($params) { session and redirect to
$_SESSION = array(); the home page.
session_destroy();
redirectRelative("index");
}
?>
60
Session example
• Prevent URL fishing in controllers/[Link]
function get_view($params) {
ensureLoggedIn();
$id = safeParam($params, 0, false);
if ($id === false) {
ensureLoggedIn will make
die("No todo id specified"); sure that this function can
} execute only if the user is
authenticated. Call this
$todo = findToDoById($id); first in every method you
if (!$todo) { want protected.
die("No todo with id $id found.");
}
// remainder skipped
61
Session example
• Modify Lib/[Link]
function ensureLoggedIn() {
if (!isLoggedIn()) {
$_SESSION['redirect'] = $_SERVER['REQUEST_URI'];
redirectRelative('login');
exit();
}
}
This updates the redirect
function redirect($url) { session variable to know
session_write_close(); where to go to after logging
header("Location: $url"); in.
exit();
}
62
Session example
• Modify Lib/[Link]
function ensureLoggedIn() {
if (!isLoggedIn()) {
$_SESSION['redirect'] = $_SERVER['REQUEST_URI'];
redirectRelative('login');
exit();
}
}
This ensures that session
function redirect($url) { files are updated on a
session_write_close(); redirection (i.e. no HTML
header("Location: $url"); output).
exit();
}
63
Session example
• Complete source code for the entire
working example is available at
[Link]
64
Upcoming Deadlines
•Readings for next module
Chapters 13 and 14 in PHP and MySQL
•Assignments
Homework 5 due end of Module 6
Lab 2 due end of Module 7
•Next module:
Functions and object-oriented programming
General Q & A
• Questions?
• Comments?
• Concerns?