Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
W
webbasics
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Taddeüs Kroes
webbasics
Commits
e7456e97
Commit
e7456e97
authored
Oct 08, 2012
by
Taddeus Kroes
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implemented first version of security and user classes
parent
b91f9709
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
441 additions
and
77 deletions
+441
-77
security.php
security.php
+195
-77
session.php
session.php
+4
-0
users.php
users.php
+236
-0
webbasics.php
webbasics.php
+6
-0
No files found.
security.php
View file @
e7456e97
...
@@ -7,7 +7,7 @@
...
@@ -7,7 +7,7 @@
* try {
* try {
* $security = webbasics\Security::getInstance();
* $security = webbasics\Security::getInstance();
*
*
* //
Authentication
: can the origin of the request be trusted?
* //
SecureUser
: can the origin of the request be trusted?
*
*
* // Verify that a user is logged in
* // Verify that a user is logged in
* $security->requireLogin();
* $security->requireLogin();
...
@@ -24,11 +24,11 @@
...
@@ -24,11 +24,11 @@
*
*
* // Pass token to template so that it can be used in a submitted form or
* // Pass token to template so that it can be used in a submitted form or
* // AJAX request
* // AJAX request
* $template->set('token', $
auth
->generateToken());
* $template->set('token', $
user
->generateToken());
*
*
* ...
* ...
*
*
* } catch(webbasics\
Authentication
Failed $e) {
* } catch(webbasics\
SecureUser
Failed $e) {
* die('Get lost hacker!');
* die('Get lost hacker!');
* } catch(webbasics\AuthorizationFailed $e) {
* } catch(webbasics\AuthorizationFailed $e) {
* http_response_code(403);
* http_response_code(403);
...
@@ -44,86 +44,55 @@
...
@@ -44,86 +44,55 @@
* if (!$user)
* if (!$user)
* die('Invalid username');
* die('Invalid username');
*
*
* // Current user is part of the
* $security = webbasics\Security::getInstance();
* $security = webbasics\Security::getInstance();
* $security->setUser($user);
*
*
* // Simple: use a plain password
* // Simple: use a plain password
* if (!$security->attemptPassword($user, $_POST['password']))
* if (!$security->attemptPassword
Login
($user, $_POST['password']))
* die('Invalid password');
* die('Invalid password');
*
*
* // More secure: hash the password in a javascript function before
* // More secure: hash the password in a javascript function before
* // submitting the login form
* // submitting the login form
* if (!$security->attemptPasswordHash($user, $_POST['password_hash']))
* if (!$security->attemptPasswordHash
Login
($user, $_POST['password_hash']))
* die('Invalid password');
* die('Invalid password');
* </code>
* </code>
*
*
* And the User model implementation used in the example above:
* And the User model implementation used in the example above:
* <code>
* <code>
* use ActiveRecord\Model;
* use webbasics\AuthenticatedUser;
* use webbasics\AuthorizedUser;
*
*
* class User extends Model implements AuthenticatedUser, AuthorizedUser {
* use webbasics\ActiveRecordUser;
* function getUsername() {
* class User extends webbasics\ActiveRecordUser {}
* return $this->username;
* }
*
* function getPasswordHash() {
* return $this->password;
* }
*
* function getCookieToken() {
* return $this->cookie_token;
* }
*
* function setCookieToken($token) {
* $this->update_attribute('cookie_token', $token);
* }
*
* function getRegistrationToken() {
* return $this->registration_token;
* }
*
* function setRegistrationToken($token) {
* $this->update_attribute('registration_token', $token);
* }
*
* function getRole() {
* return $this->role;
* }
* }
* </code>
* </code>
*
*
* WebBasics provides the {@link AuthenticatedUser} and {@link AuthorizedUser}
* classes, which extend ActiveRecord\Model and implement both security
* interfaces.
*
* @author Taddeus Kroes
* @author Taddeus Kroes
* @date 05-10-2012
* @date 06-10-2012
* @since 0.2
* @todo Documentation, unit tests
*/
*/
namespace
webbasics
;
namespace
webbasics
;
require_once
'base.php'
;
require_once
'session.php'
;
interface
AuthenticatedUser
{
function
getUsername
();
function
getPasswordHash
();
function
getCookieToken
();
function
setCookieToken
(
$token
);
function
getRegistrationToken
();
function
setRegistrationToken
(
$token
);
}
interface
AuthorizedUser
{
function
getRole
();
}
class
Security
implements
Singleton
{
class
Security
implements
Singleton
{
const
SESSION_TOKEN_NAME
=
'auth_token'
;
/**
const
SESSION_NAME_USERDATA
=
'auth_userdata'
;
* All alphanumeric characters.
* @var string
*/
const
ALNUM_CHARS
=
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUWXYZ01234567890123456789'
;
const
TOKEN_NAME
=
'auth_token'
;
const
USERDATA_NAME
=
'auth_userdata'
;
private
static
$instance
;
private
static
$instance
;
/**
* User that is currently logged in.
* @var BaseUser
*/
private
$user
;
private
$user
;
static
function
getInstance
()
{
static
function
getInstance
()
{
...
@@ -138,58 +107,207 @@ class Security implements Singleton {
...
@@ -138,58 +107,207 @@ class Security implements Singleton {
function
generateToken
()
{
function
generateToken
()
{
$session
=
Session
::
getInstance
();
$session
=
Session
::
getInstance
();
$token
=
sha1
(
self
::
generateRandomString
(
10
));
$token
=
sha1
(
self
::
generateRandomString
(
10
));
$session
->
set
(
self
::
SESSION_
TOKEN_NAME
,
$token
);
$session
->
set
(
self
::
TOKEN_NAME
,
$token
);
return
$token
;
return
$token
;
}
}
function
requireToken
(
$request_token
)
{
function
requireToken
(
$request_token
)
{
if
(
$request_token
!=
$this
->
getSavedToken
())
if
(
$request_token
!=
$this
->
getSavedToken
())
throw
new
Authentication
Failed
(
'invalid token "%s"'
,
$request_token
);
throw
new
SecureUser
Failed
(
'invalid token "%s"'
,
$request_token
);
}
}
private
function
getSavedToken
()
{
private
function
getSavedToken
()
{
$session
=
Session
::
getInstance
();
$session
=
Session
::
getInstance
();
if
(
!
$session
->
isRegistered
(
self
::
SESSION_
TOKEN_NAME
))
if
(
!
$session
->
isRegistered
(
self
::
TOKEN_NAME
))
throw
new
Authentication
Error
(
'no token saved in session'
);
throw
new
SecureUser
Error
(
'no token saved in session'
);
return
$session
->
get
(
self
::
SESSION_
TOKEN_NAME
);
return
$session
->
get
(
self
::
TOKEN_NAME
);
}
}
function
sessionDataExists
()
{
function
requireLogin
()
{
return
Session
::
getInstance
()
->
areRegistered
(
array
(
if
(
$this
->
user
===
null
&&
!
$this
->
loadUserFromSession
())
self
::
SESSION_TOKEN_NAME
,
self
::
SESSION_NAME_USERDATA
)
);
throw
new
SecureUserFailed
(
'no user is logged in'
);
}
}
function
requireLogin
()
{
private
function
loadUserFromSession
()
{
$session
=
Session
::
getInstance
();
if
(
!
$session
->
isRegistered
(
self
::
USERDATA_NAME
))
return
false
;
// Load session data
$user
=
$session
->
get
(
self
::
USERDATA_NAME
);
// Verify session data
if
(
$user
->
getSessionHash
()
!=
$this
->
getSessionHash
())
throw
new
SecureUserFailure
(
'session data could not be verified'
);
$this
->
user
=
$user
;
return
true
;
}
function
getSessionHash
()
{
return
self
::
hash
(
Session
::
getInstance
()
->
getId
());
}
}
function
requireUserRole
()
{
function
requireUserRole
(
$required_role
)
{
if
(
!
(
$this
->
user
instanceof
RoleUser
))
throw
new
AuthorizationError
(
'user must implement interface RoleUser'
);
if
(
$this
->
user
->
getRole
()
!=
$required_role
)
throw
new
AuthorizationFailed
(
'page requires user role "%s"'
,
$required_role
);
}
}
//function setUser(AuthenticatedUser $user
) {
function
attemptPasswordLogin
(
BaseUser
$user
,
$password
)
{
// $this->user = $user
;
return
$this
->
attemptPasswordHashLogin
(
$user
,
self
::
hash
(
$password
))
;
//
}
}
static
function
generateRandomString
(
$lengt
h
)
{
function
attemptPasswordHashLogin
(
BaseUser
$user
,
$password_has
h
)
{
$CHARS
=
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUWXYZ01234567890123456789'
;
if
(
$password_hash
!=
$user
->
getPasswordHash
())
$string
=
''
;
return
false
;
$this
->
loginUser
(
$user
);
return
true
;
}
private
function
loginUser
(
BaseUser
$user
)
{
// Create a new session id so that a hijacked session will not become
// logged in as well
Session
::
getInstance
()
->
regenerateId
();
// Save a hash of the new session id in the database for verification
$user
->
setSessionHash
(
$this
->
getSessionHash
());
$this
->
user
=
clone
$user
;
Session
::
getInstance
()
->
set
(
self
::
USERDATA_NAME
,
$this
->
user
);
}
/**
* Get the hash value of a string.
*
* THe hash function used is SHA-256.
*
* @param string $string The string to hash.
* @return string A 64-byte hash.
*/
static
function
hash
(
$string
)
{
return
hash
(
'sha256'
,
$string
);
}
/**
* Generate a random string of the specified length.
*
* @param int $lengh The length of the string to generate.
* @param string $chars The caracters to choose from, defaults to {@link ALNUM_CHARS}.
* @return string The generated string.
*/
static
function
generateRandomString
(
$length
,
$chars
=
self
::
ALNUM_CHARS
)
{
srand
(
time
());
srand
(
time
());
$string
=
''
;
for
(
$i
=
0
;
$i
<
$length
;
$i
++
)
for
(
$i
=
0
;
$i
<
$length
;
$i
++
)
$string
.=
$
CHARS
[
rand
(
0
,
strlen
(
$CHARS
)
-
1
)];
$string
.=
$
chars
[
rand
(
0
,
strlen
(
$chars
)
-
1
)];
return
$string
;
return
$string
;
}
}
}
}
class
AuthenticationError
extends
FormattedException
{}
interface
BaseUser
{
class
AuthenticationFailed
extends
FormattedException
{}
function
getUsername
();
function
getPasswordHash
();
}
interface
SecureUser
extends
BaseUser
{
function
getSessionHash
();
function
setSessionHash
(
$hash
);
}
interface
RoleUser
{
function
getRole
();
}
/**
* Exception, thrown when an error occurs during authentication.
*/
class
SecureUserError
extends
FormattedException
{}
/**
* Exception, thrown when the current user cannot be authenticated.
*/
class
SecureUserFailed
extends
FormattedException
{}
/**
* Exception, thrown when an error occurs during authorization.
*/
class
AuthorizationError
extends
FormattedException
{}
class
AuthorizationError
extends
FormattedException
{}
/**
* Exception, thrown when the current user is unauthorized to perform the
* current request.
*/
class
AuthorizationFailed
extends
FormattedException
{}
class
AuthorizationFailed
extends
FormattedException
{}
/**
* The StaticUser class is meant for websites without databases, it allows for
* hard-coded usernames and passwords to be specified in user objects.
*
* Example usage:
* <code>
* class JohnDoe extends webbasics\StaticUser {
* function getUsername() {
* return 'john';
* }
*
* function getPassword() {
* return 'foobar';
* }
* }
*
* // Login controller:
* $security = webbasics\Security::getInstance();
*
* switch ($_POST['username']) {
* case 'john':
* $success = $security->attemptPasswordLogin(new JohnDoe, $_POST['password']);
* break;
* ...
* }
*
* // And this might be handy during debugging:
* $security->loginUser(new JohnDoe);
* </code>
*
* Note that this simple method of authentication even allows role-based authorization:
* <code>
* abstract class User extends webbasics\StaticUser implements webbasics\RoleUser {}
*
* class JohnDoe extends User {
* function getUsername { return 'john'; }
* function getPassword { return 'foobar'; }
* function getRole { return 'admin'; }
* }
*
* class JaneDoe extends User {
* function getUsername { return 'jane'; }
* function getPassword { return 'barfoo'; }
* function getRole { return 'member'; }
* }
* </code>
*/
abstract
class
StaticUser
implements
BaseUser
{
/**
* @see BaseUser::getPasswordHash()
*/
function
getPasswordHash
()
{
return
Security
::
hash
(
$this
->
getPassword
());
}
/**
*
*
* @return string
*/
abstract
function
getPassword
();
}
?>
?>
\ No newline at end of file
session.php
View file @
e7456e97
...
@@ -77,6 +77,10 @@ class Session implements Singleton {
...
@@ -77,6 +77,10 @@ class Session implements Singleton {
return
true
;
return
true
;
}
}
function
getId
()
{
return
session_id
();
}
function
regenerateId
()
{
function
regenerateId
()
{
session_regenerate_id
();
session_regenerate_id
();
}
}
...
...
users.php
0 → 100644
View file @
e7456e97
<?php
/**
* User model implementation linking PHPActiveRecord to the Security class.
*
* @author Taddeus Kroes
* @date 06-10-2012
* @since 0.2
*/
namespace
webbasics
;
require_once
'security.php'
;
require_once
'php-activerecord/ActiveRecord.php'
;
abstract
class
ActiveRecordUser
extends
\ActiveRecord\Model
implements
SecureUser
,
RoleUser
{
/**
* Name of the cookie holding user data.
* @var string
*/
const
COOKIE_NAME
=
'auth_userdata'
;
/**
* Keep authentication cookies for one month.
* @var int
*/
const
COOKIE_EXPIRE
=
2592000
;
/**
* Length of the salt prepended to the password.
* @var int
*/
const
SALT_LENGTH
=
6
;
/**
* Name of the table users are saved in.
* @var string
*/
static
$table_name
=
'users'
;
/**
*
* @var string[5]
*/
static
$attr_protected
=
array
(
'username'
,
'password_hash'
,
'salt'
,
'session_hash'
,
'role'
);
/**
* Plain password, optional and used only during registration process.
* @var string
*/
public
$password
;
/**
*
*/
function
before_create
()
{
$this
->
hashPassword
();
$this
->
saltPasswordHash
();
}
/**
*
*/
function
hashPassword
()
{
if
(
!
$this
->
password_hash
&&
$this
->
password
)
$this
->
password_hash
=
Security
::
hash
(
$this
->
password
);
}
/**
*
*/
function
saltPasswordHash
()
{
$this
->
password_hash
=
Security
::
hash
(
self
::
generateSalt
()
.
$this
->
password_hash
);
}
/**
* @see BaseUser::getUsername()
*/
function
getUsername
()
{
return
$this
->
username
;
}
/**
* @see BaseUser::getPasswordHash()
*/
function
getPasswordHash
()
{
return
$this
->
password_hash
;
}
/**
* @see SecureUser::getSessionHash()
*/
function
getSessionHash
()
{
return
$this
->
session_hash
;
}
/**
* @see SecureUser::setSessionHash()
*/
function
setSessionHash
(
$hash
)
{
$this
->
update_attribute
(
'session_hash'
,
$hash
);
}
/**
*
*
* @return string A 6-byte salt.
*/
private
static
function
generateSalt
()
{
$class_name
=
get_called_class
();
return
Security
::
generateRandomString
(
$class_name
::
SALT_LENGTH
);
}
/**
* @see SecureUser::getRole()
*/
function
getRole
()
{
return
$this
->
role
;
}
function
saveCookie
()
{
$class_name
=
get_class
(
$this
);
$data
=
array
(
$this
->
getUsername
(),
$this
->
getSessionHash
());
setcookie
(
$class_name
::
COOKIE_NAME
,
implode
(
','
,
$data
),
$class_name
::
COOKIE_EXPIRE
);
}
/**
* @see
*/
static
function
loadFromCookie
()
{
$class_name
=
get_called_class
();
if
(
!
isset
(
$_COOKIE
[
$class_name
::
COOKIE_NAME
]))
return
false
;
list
(
$username
,
$session_hash
)
=
explode
(
','
,
$_COOKIE
[
$class_name
::
COOKIE_NAME
]);
$user
=
$class_name
::
first
(
array
(
'conditions'
=>
compact
(
'username'
,
'session_hash'
)
));
Security
::
getInstance
()
->
loginUser
(
$user
);
$user
->
saveCookie
();
return
true
;
}
}
abstract
class
RegisteredUser
extends
ActiveRecordUser
{
/**
* Length of passwords generated after registration.
* @var int
*/
const
PASSWORD_LENGTH
=
8
;
/**
* Attributes protected against mass assignment.
* @var string[6]
*/
static
$attr_protected
=
array
(
'username'
,
'password_hash'
,
'salt'
,
'session_hash'
,
'role'
,
'registration_token'
);
/**
* Send a confirmation e-mail after registration.
* @var string[1]
*/
static
$after_create
=
array
(
'composeConfirmationMail'
);
function
before_create
()
{
parent
::
before_create
();
$this
->
registration_token
=
sha1
(
$this
->
getUsername
()
.
time
());
}
static
function
confirmRegistrationToken
(
$obfuscated_token
)
{
$token
=
self
::
deobfuscateToken
(
$obfuscated_token
);
$user
=
self
::
first
(
array
(
'conditions'
=>
array
(
'registration_token'
=>
$token
)
));
if
(
!
$user
||
$token
!=
$user
->
registration_token
)
return
false
;
$user
->
confirmRegistration
();
return
true
;
}
function
confirmRegistration
()
{
$this
->
clearRegistrationToken
();
$this
->
composeWelcomeMail
();
}
function
clearRegistrationToken
()
{
$this
->
update_attribute
(
'registration_token'
,
null
);
}
function
composeWelcomeMail
()
{
$this
->
sendWelcomeMail
(
$this
->
username
,
$this
->
generatePassword
());
}
function
generatePassword
()
{
$class_name
=
get_class
(
$this
);
$password
=
Security
::
generateRandomString
(
$class_name
::
PASSWORD_LENGTH
);
$this
->
update_attribute
(
'password_hash'
,
Security
::
hash
(
$password
));
return
$password
;
}
function
composeConfirmationMail
()
{
$this
->
sendConfirmationMail
(
$this
->
username
,
$this
->
obfuscateToken
());
}
function
obfuscateToken
()
{
$obfuscated
=
''
;
foreach
(
range
(
0
,
strlen
(
$this
->
registration_token
)
-
1
)
as
$i
)
$obfuscated
.=
$this
->
registration_token
[
$i
]
.
chr
(
rand
(
97
,
122
));
return
$obfuscated
;
}
static
function
deobfuscateToken
(
$obfuscated
)
{
$token
=
''
;
foreach
(
range
(
0
,
strlen
(
$obfuscated
)
-
1
,
2
)
as
$i
)
$token
.=
$obfuscated
[
$i
];
return
$token
;
}
function
isConfirmed
()
{
return
$this
->
registration_token
===
null
;
}
abstract
function
sendConfirmationMail
(
$username
,
$token
);
abstract
function
sendWelcomeMail
(
$username
,
$password
);
}
?>
\ No newline at end of file
webbasics.php
View file @
e7456e97
...
@@ -15,6 +15,12 @@ require_once 'collection.php';
...
@@ -15,6 +15,12 @@ require_once 'collection.php';
require_once
'router.php'
;
require_once
'router.php'
;
require_once
'template.php'
;
require_once
'template.php'
;
require_once
'session.php'
;
require_once
'session.php'
;
require_once
'security.php'
;
if
(
defined
(
'WB_INCLUDE_PHPACTIVERECORD'
)
&&
WB_INCLUDE_PHPACTIVERECORD
)
{
require_once
'php-activerecord/ActiveRecord.php'
;
require_once
'user.php'
;
}
// @codeCoverageIgnoreEnd
// @codeCoverageIgnoreEnd
?>
?>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment