[smarcet] - Refs #4860 - Improve security measures

This commit is contained in:
smarcet 2013-11-13 14:52:52 -03:00
parent 2c8b3f2550
commit 0849f03b3c
23 changed files with 492 additions and 232 deletions

View File

@ -103,7 +103,6 @@ return array(
'default' => array(
'host' => '127.0.0.1',
'port' => 6379,
'database' => 0,
),
),

View File

@ -98,7 +98,6 @@ return array(
'default' => array(
'host' => '127.0.0.1',
'port' => 6379,
'database' => 1,
),
),

View File

@ -88,7 +88,7 @@ class UserController extends BaseController
public function postLogin()
{
try {
$max_login_attempts_2_show_captcha = $this->server_configuration_service->getMaxFailedLoginAttempts2ShowCaptcha();
$max_login_attempts_2_show_captcha = $this->server_configuration_service->getConfigValue("MaxFailed.LoginAttempts.2ShowCaptcha");
$data = Input::all();
$login_attempts = intval(Input::get('login_attempts'));
// Build the validation constraint set.

View File

@ -53,6 +53,114 @@ class ServerConfigurationSeeder extends Seeder {
'value' => 'http://www.openstack.org/',
)
);
//blacklist policy config values
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.BannedIpLifeTimeSeconds',
'value' => '21600',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.MinutesWithoutExceptions',
'value' => '5',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.ReplayAttackExceptionInitialDelay',
'value' => '10',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.MaxInvalidNonceAttempts',
'value' => '10',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.InvalidNonceInitialDelay',
'value' => '10',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.MaxInvalidOpenIdMessageExceptionAttempts',
'value' => '10',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.InvalidOpenIdMessageExceptionInitialDelay',
'value' => '10',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.MaxOpenIdInvalidRealmExceptionAttempts',
'value' => '10',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.OpenIdInvalidRealmExceptionInitialDelay',
'value' => '10',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.MaxInvalidOpenIdMessageModeAttempts',
'value' => '10',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.InvalidOpenIdMessageModeInitialDelay',
'value' => '10',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.MaxInvalidOpenIdAuthenticationRequestModeAttempts',
'value' => '10',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.InvalidOpenIdAuthenticationRequestModeInitialDelay',
'value' => '10',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.MaxAuthenticationExceptionAttempts',
'value' => '10',
)
);
ServerConfiguration::create(
array(
'key' => 'BlacklistSecurityPolicy.AuthenticationExceptionInitialDelay',
'value' => '20',
)
);
}
}

View File

@ -2,7 +2,6 @@
namespace auth;
use auth\exceptions\AuthenticationException;
use Exception;
use Illuminate\Auth\UserInterface;
@ -13,6 +12,7 @@ use openid\helpers\OpenIdErrorMessages;
use openid\requests\OpenIdAuthenticationRequest;
use openid\services\Registry;
use openid\services\ServiceCatalog;
use auth\exceptions\AuthenticationInvalidPasswordAttemptException;
class CustomAuthProvider implements UserProviderInterface
{
@ -64,17 +64,17 @@ class CustomAuthProvider implements UserProviderInterface
$user = OpenIdUser::where('external_id', '=', $identifier)->first();
//check user status...
if (!is_null($user) && ($user->lock || !$user->active))
if (!is_null($user) && ($user->lock || !$user->active)){
Log::warning(sprintf("user %s is on lock state",$identifier));
return null;
}
//get SS member
$member = Member::where('Email', '=', $identifier)->first();
if (is_null($member)) //member must exists
throw new AuthenticationException(sprintf("member %s does not exists!", $identifier));
$user_service = Registry::getInstance()->get(ServiceCatalog::UserService);
$valid_password = $member->checkPassword($password);
//if user does not exists, then create it
if (is_null($user)) {
//create user
@ -84,28 +84,26 @@ class CustomAuthProvider implements UserProviderInterface
$user->Save();
}
$user_service = Registry::getInstance()->get(ServiceCatalog::UserService);
$valid_password = $member->checkPassword($password);
if(!$valid_password)
throw new AuthenticationInvalidPasswordAttemptException($identifier,sprintf("invalid login attempt for user %s ",$identifier));
$user_name = $member->FirstName . "." . $member->Surname;
//do association between user and member
$user_service->associateUser($user->id, strtolower($user_name));
$server_configuration = Registry::getInstance()->get(ServiceCatalog::ServerConfigurationService);
if (!$valid_password) {
//apply lock policy
if ($user->login_failed_attempt < $server_configuration->getMaxFailedLoginAttempts())
$user_service->updateFailedLoginAttempts($user->id);
else {
$user_service->lockUser($user->id);
}
$user = null;
} else {
//update user fields
$user->last_login_date = gmdate("Y-m-d H:i:s", time());
$user->login_failed_attempt = 0;
$user->active = true;
$user->lock = false;
$user->Save();
}
//update user fields
$user->last_login_date = gmdate("Y-m-d H:i:s", time());
$user->login_failed_attempt = 0;
$user->active = true;
$user->lock = false;
$user->Save();
//reload user...
$user = OpenIdUser::where('external_id', '=', $identifier)->first();
$user->setMember($member);
@ -116,6 +114,7 @@ class CustomAuthProvider implements UserProviderInterface
if (is_null($msg) || !$msg->isValid() || !OpenIdAuthenticationRequest::IsOpenIdAuthenticationRequest($msg))
return $user;
else {
//check if current user is has the same identity that the one claimed on openid message
$auth_request = new OpenIdAuthenticationRequest($msg);
if ($auth_request->isIdentitySelectByOP())
return $user;
@ -124,8 +123,8 @@ class CustomAuthProvider implements UserProviderInterface
$current_identity = $server_configuration->getUserIdentityEndpointURL($user->getIdentifier());
if ($claimed_id == $current_identity || $identity == $current_identity)
return $user;
Log::warning(sprintf(OpenIdErrorMessages::AlreadyExistSessionMessage, $current_identity, $identity));
//if not return fail ( we cant log in with a different user that the one stated on the authentication message!
return null;
}

View File

@ -186,7 +186,7 @@ class OpenIdUser extends \Eloquent implements UserInterface, IOpenIdUser
if (!is_null($photoId) && is_numeric($photoId) && $photoId > 0) {
$photo = MemberPhoto::where('ID', '=', $photoId)->first();
$server_configuration_service = Registry::getInstance()->get(ServiceCatalog::ServerConfigurationService);
$url = $server_configuration_service->getAssetsUrl($photo->Filename);
$url = $server_configuration_service->getConfigValue("Assets.Url").$photo->Filename;
return $url;
}
return '';

View File

@ -0,0 +1,29 @@
<?php
/**
* Created by PhpStorm.
* User: smarcet
* Date: 11/13/13
* Time: 12:32 PM
*/
namespace auth\exceptions;
use Exception;
class AuthenticationInvalidPasswordAttemptException extends Exception
{
private $identifier;
public function __construct($identifier,$message = "")
{
$message = "AuthenticationInvalidPasswordAttemptException : " . $message;
$this->identifier = $identifier;
parent::__construct($message, 0, null);
}
public function getIdentifier(){
return $this->identifier;
}
}

View File

@ -288,7 +288,7 @@ class OpenIdAuthenticationRequestHandler extends OpenIdMessageHandler
// if not present or if it already void then enter on dumb mode
$new_secret = OpenIdCryptoHelper::generateSecret(OpenIdProtocol::SignatureAlgorithmHMAC_SHA256);
$new_handle = AssocHandleGenerator::generate();
$lifetime = $this->server_configuration_service->getPrivateAssociationLifetime();
$lifetime = $this->server_configuration_service->getConfigValue("Private.Association.Lifetime");
$issued = gmdate("Y-m-d H:i:s", time());
//create private association ...
$this->association_service->addAssociation($new_handle, $new_secret, OpenIdProtocol::SignatureAlgorithmHMAC_SHA256, $lifetime, $issued, IAssociation::TypePrivate, $realm);

View File

@ -62,7 +62,7 @@ class SessionAssociationDHStrategy implements ISessionAssociationStrategy
$server_public_key = base64_encode($dh->getPublicKey(DiffieHellman::FORMAT_BTWOC));
$enc_mac_key = base64_encode($HMAC_secret_handle ^ $hashed_shared_secret);
$assoc_handle = AssocHandleGenerator::generate();
$expires_in = $this->server_configuration_service->getSessionAssociationLifetime();
$expires_in = $this->server_configuration_service->getConfigValue("Session.Association.Lifetime");
$response = new OpenIdDiffieHellmanAssociationSessionResponse($assoc_handle, $session_type, $assoc_type, $expires_in, $server_public_key, $enc_mac_key);
$issued = gmdate("Y-m-d H:i:s", time());
$this->association_service->addAssociation($assoc_handle, $HMAC_secret_handle, $assoc_type, $expires_in, $issued, IAssociation::TypeSession, null);

View File

@ -72,7 +72,7 @@ class OpenIdNonce
public function isValid()
{
$server_configuration_service = Registry::getInstance()->get("openid\\services\\IServerConfigurationService");
$allowed_skew = $server_configuration_service->getNonceLifetime();
$allowed_skew = $server_configuration_service->getConfigValue("Nonce.Lifetime");
$now = time();
// Time after which we should not use the nonce
$past = $now - $allowed_skew;

View File

@ -24,4 +24,6 @@ interface ISecurityPolicy {
* @return mixed
*/
public function apply(Exception $ex);
public function setCounterMeasure(ISecurityPolicyCounterMeasure $counter_measure);
}

View File

@ -11,5 +11,5 @@ namespace openid\services;
*/
interface ISecurityPolicyCounterMeasure
{
public function trigger();
public function trigger(array $params);
}

View File

@ -6,19 +6,18 @@ namespace openid\services;
interface IServerConfigurationService
{
/**
*
* @return mixed
*/
public function getOPEndpointURL();
/**
*
* @return mixed
*/
public function getUserIdentityEndpointURL($identifier);
public function getPrivateAssociationLifetime();
public function getConfigValue($key);
public function getSessionAssociationLifetime();
public function getMaxFailedLoginAttempts();
public function getMaxFailedLoginAttempts2ShowCaptcha();
public function getNonceLifetime();
public function getAssetsUrl($asset_path);
}

View File

@ -10,6 +10,8 @@ use openid\helpers\OpenIdErrorMessages;
use openid\model\IAssociation;
use openid\services\IAssociationService;
use OpenIdAssociation;
use Exception;
use Log;
class AssociationService implements IAssociationService
{
@ -30,6 +32,7 @@ class AssociationService implements IAssociationService
*/
public function getAssociation($handle, $realm = null)
{
// check if association is on redis cache
if ($this->redis->exists($handle)) {
$values = $this->redis->hmget($handle, array(

View File

@ -3,13 +3,14 @@
namespace services;
use BannedIP;
use DateTime;
use DB;
use Exception;
use Log;
use openid\services\ISecurityPolicy;
use openid\services\ISecurityPolicyCounterMeasure;
use openid\services\IServerConfigurationService;
use UserExceptionTrail;
use \DateTime;
/**
* Class BlacklistSecurityPolicy
@ -19,37 +20,28 @@ use \DateTime;
class BlacklistSecurityPolicy implements ISecurityPolicy
{
const BannedIpLifeTimeSeconds = 21600;// 6 hs
const MinutesWithoutExceptions = 5;
const ReplayAttackExceptionInitialDelay = 10;
const MaxInvalidNonceAttempts = 10;
const InvalidNonceInitialDelay = 10;
const MaxInvalidOpenIdMessageExceptionAttempts = 10;
const InvalidOpenIdMessageExceptionInitialDelay = 10;
const MaxOpenIdInvalidRealmExceptionAttempts = 10;
const OpenIdInvalidRealmExceptionInitialDelay = 10;
const MaxInvalidOpenIdMessageModeAttempts = 10;
const InvalidOpenIdMessageModeInitialDelay = 10;
const MaxInvalidOpenIdAuthenticationRequestModeAttempts = 10;
const InvalidOpenIdAuthenticationRequestModeInitialDelay = 10;
const MaxAuthenticationExceptionAttempts = 10;
const AuthenticationExceptionInitialDelay = 20;
private $server_configuration_service;
private $redis;
private $counter_measure;
public function __construct(ISecurityPolicyCounterMeasure $counter_measure)
public function __construct(IServerConfigurationService $server_configuration_service)
{
$this->counter_measure = $counter_measure;
$this->redis = \RedisLV4::connection();
$this->server_configuration_service = $server_configuration_service;
}
/**
* Check policy
* @return bool
*/
public function check()
{
$res = true;
$remote_address = IPHelper::getUserIp();
try {
//check if banned ip is on redis ...
if ($this->redis->exists($remote_address)) {
$this->redis->incr($remote_address);
$res = false;
@ -61,18 +53,19 @@ class BlacklistSecurityPolicy implements ISecurityPolicy
//set lock
$success = $this->redis->setnx("lock." . $remote_address, 1);
if (!$success)
throw new Exception("BlacklistSecurityPolicy->check : lock already taken!");
if (!$success) // if we cant get lock, then error
throw new Exception(sprintf("BlacklistSecurityPolicy->check : lock already taken for banned ip %s!", $banned_ip->ip));
try {
$issued = $banned_ip->created_at;
$utc_now = gmdate("Y-m-d H:i:s", time());
$utc_now = DateTime::createFromFormat("Y-m-d H:i:s", $utc_now);
$issued = $banned_ip->created_at;
$utc_now = gmdate("Y-m-d H:i:s", time());
$utc_now = DateTime::createFromFormat("Y-m-d H:i:s", $utc_now);
//get time lived on seconds
$time_lived_seconds = abs($utc_now->getTimestamp()-$issued->getTimestamp());
$time_lived_seconds = abs($utc_now->getTimestamp() - $issued->getTimestamp());
if ($time_lived_seconds >= self::BannedIpLifeTimeSeconds) {
if ($time_lived_seconds >= $this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.BannedIpLifeTimeSeconds")) {
//void banned ip
$banned_ip->delete();
} else {
@ -82,7 +75,7 @@ class BlacklistSecurityPolicy implements ISecurityPolicy
$success = $this->redis->setnx($banned_ip->ip, $banned_ip->hits);
if ($success) {
//set remaining time to live
$this->redis->expire($remote_address, (self::BannedIpLifeTimeSeconds - $time_lived_seconds) );
$this->redis->expire($remote_address, ($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.BannedIpLifeTimeSeconds") - $time_lived_seconds));
}
$res = false;
//release lock
@ -105,6 +98,11 @@ class BlacklistSecurityPolicy implements ISecurityPolicy
return $res;
}
/**
* Apply security policy
* @param Exception $ex
* @return mixed|void
*/
public function apply(Exception $ex)
{
try {
@ -113,50 +111,50 @@ class BlacklistSecurityPolicy implements ISecurityPolicy
//check exception count by type on last "MinutesWithoutExceptions" minutes...
$exception_count = UserExceptionTrail::where('from_ip', '=', $remote_ip)
->where('exception_type', '=', $exception_class)
->where('created_at', '>', DB::raw('( UTC_TIMESTAMP() - INTERVAL ' . self::MinutesWithoutExceptions . ' MINUTE )'))
->where('created_at', '>', DB::raw('( UTC_TIMESTAMP() - INTERVAL ' . $this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.MinutesWithoutExceptions") . ' MINUTE )'))
->count();
switch ($exception_class) {
case 'openid\exceptions\ReplayAttackException':
{
//on replay attack , ban ip..
$this->createBannedIP(self::ReplayAttackExceptionInitialDelay, $exception_class);
$this->createBannedIP($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.ReplayAttackExceptionInitialDelay"), $exception_class);
}
break;
case 'openid\exceptions\InvalidNonce':
{
if ($exception_count >= self::MaxInvalidNonceAttempts)
$this->createBannedIP(self::InvalidNonceInitialDelay, $exception_class);
if ($exception_count >= $this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.MaxInvalidNonceAttempts"))
$this->createBannedIP($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.InvalidNonceInitialDelay"), $exception_class);
}
break;
case 'openid\exceptions\InvalidOpenIdMessageException':
{
if ($exception_count >= self::MaxInvalidOpenIdMessageExceptionAttempts)
$this->createBannedIP(self::InvalidOpenIdMessageExceptionInitialDelay, $exception_class);
if ($exception_count >= $this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.MaxInvalidOpenIdMessageExceptionAttempts"))
$this->createBannedIP($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.InvalidOpenIdMessageExceptionInitialDelay"), $exception_class);
}
break;
case 'openid\exceptions\OpenIdInvalidRealmException':
{
if ($exception_count >= self::MaxOpenIdInvalidRealmExceptionAttempts)
$this->createBannedIP(self::OpenIdInvalidRealmExceptionInitialDelay, $exception_class);
if ($exception_count >= $this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.MaxOpenIdInvalidRealmExceptionAttempts"))
$this->createBannedIP($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.OpenIdInvalidRealmExceptionInitialDelay"), $exception_class);
}
break;
case 'openid\exceptions\InvalidOpenIdMessageMode':
{
if ($exception_count >= self::MaxInvalidOpenIdMessageModeAttempts)
$this->createBannedIP(self::InvalidOpenIdMessageModeInitialDelay, $exception_class);
if ($exception_count >= $this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.MaxInvalidOpenIdMessageModeAttempts"))
$this->createBannedIP($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.InvalidOpenIdMessageModeInitialDelay"), $exception_class);
}
break;
case 'openid\exceptions\InvalidOpenIdAuthenticationRequestMode':
{
if ($exception_count >= self::MaxInvalidOpenIdAuthenticationRequestModeAttempts)
$this->createBannedIP(self::InvalidOpenIdAuthenticationRequestModeInitialDelay, $exception_class);
if ($exception_count >= $this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.MaxInvalidOpenIdAuthenticationRequestModeAttempts"))
$this->createBannedIP($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.InvalidOpenIdAuthenticationRequestModeInitialDelay"), $exception_class);
}
break;
case 'auth\exceptions\AuthenticationException':
{
if ($exception_count >= self::MaxAuthenticationExceptionAttempts)
$this->createBannedIP(self::AuthenticationExceptionInitialDelay, $exception_class);
if ($exception_count >= $this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.MaxAuthenticationExceptionAttempts"))
$this->createBannedIP($this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.AuthenticationExceptionInitialDelay"), $exception_class);
}
break;
}
@ -177,7 +175,7 @@ class BlacklistSecurityPolicy implements ISecurityPolicy
//try to create on redis
$success = $this->redis->setnx($remote_address, $initial_hits);
if ($success) {
$this->redis->expire($remote_address, self::BannedIpLifeTimeSeconds);
$this->redis->expire($remote_address, $this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.BannedIpLifeTimeSeconds"));
}
Log::warning(sprintf("BlacklistSecurityPolicy: Banning ip %s by Exception %s", $remote_address, $exception_type));
@ -194,4 +192,9 @@ class BlacklistSecurityPolicy implements ISecurityPolicy
Log::error($ex);
}
}
public function setCounterMeasure(ISecurityPolicyCounterMeasure $counter_measure)
{
$this->counter_measure = $counter_measure;
}
}

View File

@ -2,8 +2,8 @@
namespace services;
use \Exception;
use \Log;
use Exception;
use Log;
use openid\services\ICheckPointService;
use openid\services\ISecurityPolicy;
@ -15,18 +15,18 @@ class CheckPointService implements ICheckPointService
public function __construct(ISecurityPolicy $policy)
{
$this->policies = array();
array_push($this->policies,$policy);
array_push($this->policies, $policy);
}
public function check(){
public function check()
{
$res = false;
try{
foreach($this->policies as $policy){
try {
foreach ($this->policies as $policy) {
$res = $policy->check();
if(!$res) break;
if (!$res) break;
}
}
catch(Exception $ex){
} catch (Exception $ex) {
Log::error($ex);
}
return $res;
@ -47,7 +47,7 @@ class CheckPointService implements ICheckPointService
$user_trail->exception_type = $class_name;
$user_trail->Save();
//applying policies
foreach($this->policies as $policy){
foreach ($this->policies as $policy) {
$policy->apply($ex);
}
} catch (Exception $ex) {
@ -57,6 +57,6 @@ class CheckPointService implements ICheckPointService
public function addPolicy(ISecurityPolicy $policy)
{
array_push($this->policies,$policy);
array_push($this->policies, $policy);
}
}

View File

@ -4,6 +4,7 @@ namespace services;
use \Log;
use openid\services\ISecurityPolicyCounterMeasure;
use Exception;
class DelayCounterMeasure implements ISecurityPolicyCounterMeasure
{
@ -13,7 +14,7 @@ class DelayCounterMeasure implements ISecurityPolicyCounterMeasure
$this->redis = \RedisLV4::connection();
}
public function trigger()
public function trigger(array $params)
{
try {
$remote_address = IPHelper::getUserIp();

View File

@ -0,0 +1,40 @@
<?php
/**
* Created by PhpStorm.
* User: smarcet
* Date: 11/13/13
* Time: 2:15 PM
*/
namespace services;
use Log;
use openid\services\ISecurityPolicyCounterMeasure;
use openid\services\Registry;
use openid\services\ServiceCatalog;
use auth\OpenIdUser;
use Exception;
class LockUserCounterMeasure implements ISecurityPolicyCounterMeasure
{
public function trigger(array $params)
{
try {
if (!isset($params["user_identifier"])) return;
$user_identifier = $params["user_identifier"];
$server_configuration = Registry::getInstance()->get(ServiceCatalog::ServerConfigurationService);
$user_service = Registry::getInstance()->get(ServiceCatalog::UserService);
$user = OpenIdUser::where('external_id', '=', $user_identifier)->first();
//apply lock policy
if ($user->login_failed_attempt < $server_configuration->getConfigValue("MaxFailed.Login.Attempts"))
$user_service->updateFailedLoginAttempts($user->id);
else {
$user_service->lockUser($user->id);
}
} catch (Exception $ex) {
Log::error($ex);
}
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace services;
use DB;
use Exception;
use Log;
use openid\services\ISecurityPolicy;
use openid\services\ISecurityPolicyCounterMeasure;
class LockUserSecurityPolicy implements ISecurityPolicy
{
/**
* Check if current security policy is meet or not
* @return boolean
*/
public function check()
{
return true;
}
public function setCounterMeasure(ISecurityPolicyCounterMeasure $counter_measure)
{
$this->counter_measure = $counter_measure;
}
/**
* Apply security policy on a exception
* @param Exception $ex
* @return mixed
*/
public function apply(Exception $ex)
{
try {
$exception_class = get_class($ex);
switch ($exception_class) {
case 'auth\exceptions\AuthenticationInvalidPasswordAttemptException':
{
$user_identifier = $ex->getIdentifier();
if (!is_null($user_identifier) && !empty($user_identifier))
$this->counter_measure->trigger(array('user_identifier' => $user_identifier));
}
break;
}
} catch (Exception $ex) {
Log::error($ex);
}
}
}

View File

@ -2,6 +2,8 @@
namespace services;
use Exception;
use Log;
use openid\exceptions\ReplayAttackException;
use openid\helpers\OpenIdErrorMessages;
use openid\model\OpenIdNonce;
@ -25,7 +27,7 @@ class NonceService implements INonceService
{
$raw_nonce = $nonce->getRawFormat();
$cur_time = time();
$lock_lifetime = \ServerConfigurationService::getNonceLifetime();;
$lock_lifetime = \ServerConfigurationService::getConfigValue("Nonce.Lifetime");
return $this->redis->setnx('lock.' . $raw_nonce, $cur_time + $lock_lifetime + 1);
}
@ -77,8 +79,12 @@ class NonceService implements INonceService
*/
public function associateNonce(OpenIdNonce $nonce, $signature, $realm)
{
$raw_nonce = $nonce->getRawFormat();
$lifetime = \ServerConfigurationService::getNonceLifetime();
$this->redis->setex($raw_nonce . $signature, $lifetime, $realm);
try {
$raw_nonce = $nonce->getRawFormat();
$lifetime = \ServerConfigurationService::getConfigValue("Nonce.Lifetime");
$this->redis->setex($raw_nonce . $signature, $lifetime, $realm);
} catch (Exception $ex) {
Log::error($ex);
}
}
}

View File

@ -2,11 +2,11 @@
namespace services;
use BannedIP;
use Exception;
use openid\services\IServerConfigurationService;
use ServerConfiguration;
class ServerConfigurationService implements IServerConfigurationService
{
@ -22,15 +22,46 @@ class ServerConfigurationService implements IServerConfigurationService
private $max_failed_login_attempts_2_show_captcha;
private $nonce_lifetime;
private $assets_url;
private $redis;
private $default_config_params;
public function __construct()
{
//todo: remove all specific methods per key and use getConfigValue
$this->private_association_lifetime = null;
$this->session_association_lifetime = null;
$this->max_failed_login_attempts = null;
$this->max_failed_login_attempts_2_show_captcha = null;
$this->nonce_lifetime = null;
$this->assets_url = null;
$this->redis = \RedisLV4::connection();
//default config values
$this->default_config_params = array();
$this->default_config_params["Private.Association.Lifetime"] = 240;
$this->default_config_params["Session.Association.Lifetime"] = 21600;
$this->default_config_params["MaxFailed.Login.Attempts"] = 10;
$this->default_config_params["MaxFailed.LoginAttempts.2ShowCaptcha"] = 3;
$this->default_config_params["Nonce.Lifetime"] = 360;
$this->default_config_params["Assets.Url"] = 'http://www.openstack.org/';
$this->default_config_params["BlacklistSecurityPolicy.BannedIpLifeTimeSeconds"] = 21600;
$this->default_config_params["BlacklistSecurityPolicy.MinutesWithoutExceptions"] = 5;
$this->default_config_params["BlacklistSecurityPolicy.ReplayAttackExceptionInitialDelay"] = 10;
$this->default_config_params["BlacklistSecurityPolicy.MaxInvalidNonceAttempts"] = 10;
$this->default_config_params["BlacklistSecurityPolicy.InvalidNonceInitialDelay"] = 10;
$this->default_config_params["BlacklistSecurityPolicy.MaxInvalidOpenIdMessageExceptionAttempts"] = 10;
$this->default_config_params["BlacklistSecurityPolicy.InvalidOpenIdMessageExceptionInitialDelay"] = 10;
$this->default_config_params["BlacklistSecurityPolicy.MaxOpenIdInvalidRealmExceptionAttempts"] = 10;
$this->default_config_params["BlacklistSecurityPolicy.OpenIdInvalidRealmExceptionInitialDelay"] = 10;
$this->default_config_params["BlacklistSecurityPolicy.MaxInvalidOpenIdMessageModeAttempts"] = 10;
$this->default_config_params["BlacklistSecurityPolicy.InvalidOpenIdMessageModeInitialDelay"] = 10;
$this->default_config_params["BlacklistSecurityPolicy.MaxInvalidOpenIdAuthenticationRequestModeAttempts"] = 10;
$this->default_config_params["BlacklistSecurityPolicy.InvalidOpenIdAuthenticationRequestModeInitialDelay"] = 10;
$this->default_config_params["BlacklistSecurityPolicy.MaxAuthenticationExceptionAttempts"] = 10;
$this->default_config_params["BlacklistSecurityPolicy.AuthenticationExceptionInitialDelay"] = 20;
}
public function getUserIdentityEndpointURL($identifier)
@ -45,94 +76,33 @@ class ServerConfigurationService implements IServerConfigurationService
return $url;
}
public function getPrivateAssociationLifetime()
/**
* get config value from redis and if not in redis check for it on table server_configuration
* @param $key
* @return mixed
*/
public function getConfigValue($key)
{
$res = null;
try {
if (is_null($this->private_association_lifetime)) {
$conf = ServerConfiguration::where('key', '=', 'Private.Association.Lifetime')->first();
if (!$conf || !is_numeric($conf->value)) $this->private_association_lifetime = self::DefaultPrivateAssociationLifetime;
else $this->private_association_lifetime = intval($conf->value);
}
return $this->private_association_lifetime;
} catch (Exception $ex) {
Log::error($ex);
return self::DefaultPrivateAssociationLifetime;
}
}
public function getSessionAssociationLifetime()
{
try {
if (is_null($this->session_association_lifetime)) {
$conf = ServerConfiguration::where('key', '=', 'Session.Association.Lifetime')->first();
if (!$conf || !is_numeric($conf->value)) $this->session_association_lifetime = self::DefaultSessionAssociationLifetime;
else $this->session_association_lifetime = intval($conf->value);
if (!$this->redis->exists($key)) {
$conf = ServerConfiguration::where('key', '=', $key)->first();
if ($conf)
$this->redis->setnx($key, $conf->value);
else if (isset($this->default_config_params[$key]))
$this->redis->setnx($key, $this->default_config_params[$key]);
else return null;
}
return $this->session_association_lifetime;
$res = $this->redis->get($key);
} catch (Exception $ex) {
Log::error($ex);
return self::DefaultSessionAssociationLifetime;
}
}
if (isset($this->default_config_params[$key])) {
$res = $this->default_config_params[$key];
}
public function getMaxFailedLoginAttempts()
{
try {
if (is_null($this->max_failed_login_attempts)) {
$conf = ServerConfiguration::where('key', '=', 'MaxFailed.Login.Attempts')->first();
if (!$conf || !is_numeric($conf->value)) $this->max_failed_login_attempts = self::DefaultMaxFailedLoginAttempts;
else $this->max_failed_login_attempts = intval($conf->value);
}
return $this->max_failed_login_attempts;
} catch (Exception $ex) {
Log::error($ex);
return self::DefaultMaxFailedLoginAttempts;
}
}
public function getMaxFailedLoginAttempts2ShowCaptcha()
{
try {
if (is_null($this->max_failed_login_attempts_2_show_captcha)) {
$conf = ServerConfiguration::where('key', '=', 'MaxFailed.LoginAttempts.2ShowCaptcha')->first();
if (!$conf || !is_numeric($conf->value)) $this->max_failed_login_attempts_2_show_captcha = self::DefaultMaxFailedLoginAttempts2ShowCaptcha;
else $this->max_failed_login_attempts_2_show_captcha = intval($conf->value);
}
return $this->max_failed_login_attempts_2_show_captcha;
} catch (Exception $ex) {
Log::error($ex);
return self::DefaultMaxFailedLoginAttempts2ShowCaptcha;
}
}
public function getNonceLifetime()
{
try {
if (is_null($this->nonce_lifetime)) {
$conf = ServerConfiguration::where('key', '=', 'Nonce.Lifetime')->first();
if (!$conf || !is_numeric($conf->value)) $this->nonce_lifetime = self::DefaultNonceLifetime;
else $this->nonce_lifetime = intval($conf->value);
}
return $this->nonce_lifetime;
} catch (Exception $ex) {
Log::error($ex);
return self::DefaultNonceLifetime;
}
}
public function getAssetsUrl($asset_path)
{
try {
if (is_null($this->assets_url)) {
$conf = ServerConfiguration::where('key', '=', 'Assets.Url')->first();
if (!$conf) $this->assets_url = self::DefaultAssetsUrl;
else $this->assets_url = $conf->value;
}
return $this->assets_url . $asset_path;
} catch (Exception $ex) {
Log::error($ex);
return self::DefaultAssetsUrl . $asset_path;
}
return $res;
}
}

View File

@ -24,10 +24,29 @@ class ServicesProvider extends ServiceProvider
$this->app->singleton(ServiceCatalog::UserService, 'services\\UserService');
$this->app->singleton(ServiceCatalog::NonceService, 'services\\NonceService');
$this->app->singleton(ServiceCatalog::LogService, 'services\\LogService');
$this->app->singleton("openid\\services\\ISecurityPolicyCounterMeasure", 'services\\DelayCounterMeasure');
$this->app->singleton("openid\\services\\ISecurityPolicy", 'services\\BlacklistSecurityPolicy');
$this->app->singleton("services\\DelayCounterMeasure", 'services\\DelayCounterMeasure');
$this->app->singleton("services\\LockUserCounterMeasure", 'services\\LockUserCounterMeasure');
$this->app->singleton("services\\BlacklistSecurityPolicy", 'services\\BlacklistSecurityPolicy');
$this->app->singleton("services\\LockUserSecurityPolicy", 'services\\LockUserSecurityPolicy');
$this->app->singleton('services\\IUserActionService', 'services\\UserActionService');
$this->app->singleton(ServiceCatalog::CheckPointService, 'services\\CheckPointService');
$this->app->singleton(ServiceCatalog::CheckPointService,
function(){
//set security policies
$delay_counter_measure = $this->app->make("services\\DelayCounterMeasure");
$blacklist_security_policy = $this->app->make("services\\BlacklistSecurityPolicy");
$blacklist_security_policy->setCounterMeasure($delay_counter_measure);
$lock_user_counter_measure = $this->app->make("services\\LockUserCounterMeasure");
$lock_user_security_policy = $this->app->make("services\\LockUserSecurityPolicy");
$lock_user_security_policy->setCounterMeasure($lock_user_counter_measure);
$checkpoint_service = new CheckPointService($blacklist_security_policy);
$checkpoint_service->addPolicy($lock_user_security_policy);
return $checkpoint_service;
});
Registry::getInstance()->set(ServiceCatalog::MementoService, $this->app->make(ServiceCatalog::MementoService));
Registry::getInstance()->set(ServiceCatalog::AuthenticationStrategy, $this->app->make(ServiceCatalog::AuthenticationStrategy));

View File

@ -3,65 +3,84 @@
namespace services;
use auth\OpenIdUser;
use Log;
use openid\services\IUserService;
use Exception;
class UserService implements IUserService
{
public function associateUser($id, $proposed_username)
{
$user = OpenIdUser::where('id', '=', $id)->first();
if (!empty($user->identifier)) return $user->identifier;
if (!is_null($user)) {
\DB::transaction(function () use ($id, $proposed_username) {
$done = false;
$fragment_nbr = 1;
do {
$old_user = \DB::table('openid_users')->where('identifier', '=', $proposed_username)->first();
if (is_null($old_user)) {
\DB::table('openid_users')->where('id', '=', $id)->update(array('identifier' => $proposed_username));
$done = true;
} else {
$proposed_username = $proposed_username . "." . $fragment_nbr;
$fragment_nbr++;
}
try {
$user = OpenIdUser::where('id', '=', $id)->first();
if (!empty($user->identifier)) return $user->identifier;
if (!is_null($user)) {
\DB::transaction(function () use ($id, $proposed_username) {
$done = false;
$fragment_nbr = 1;
do {
$old_user = \DB::table('openid_users')->where('identifier', '=', $proposed_username)->first();
if (is_null($old_user)) {
\DB::table('openid_users')->where('id', '=', $id)->update(array('identifier' => $proposed_username));
$done = true;
} else {
$proposed_username = $proposed_username . "." . $fragment_nbr;
$fragment_nbr++;
}
} while (!$done);
return $proposed_username;
});
} while (!$done);
return $proposed_username;
});
}
} catch (Exception $ex) {
Log::error($ex);
}
return false;
}
public function updateLastLoginDate($identifier)
{
$user = OpenIdUser::where('id', '=', $identifier)->first();
if (!is_null($user)) {
\DB::transaction(function () use ($identifier) {
\DB::table('openid_users')->where('id', '=', $identifier)->update(array('last_login_date' => gmdate("Y-m-d H:i:s", time())));
});
try {
$user = OpenIdUser::where('id', '=', $identifier)->first();
if (!is_null($user)) {
\DB::transaction(function () use ($identifier) {
\DB::table('openid_users')->where('id', '=', $identifier)->update(array('last_login_date' => gmdate("Y-m-d H:i:s", time())));
});
}
} catch (Exception $ex) {
Log::error($ex);
}
}
public function updateFailedLoginAttempts($identifier)
{
$user = OpenIdUser::where('id', '=', $identifier)->first();
if (!is_null($user)) {
$attempts = $user->login_failed_attempt;
++$attempts;
\DB::transaction(function () use ($identifier, $attempts) {
\DB::table('openid_users')->where('id', '=', $identifier)->update(array('login_failed_attempt' => $attempts));
});
try {
$user = OpenIdUser::where('id', '=', $identifier)->first();
if (!is_null($user)) {
$attempts = $user->login_failed_attempt;
++$attempts;
\DB::transaction(function () use ($identifier, $attempts) {
\DB::table('openid_users')->where('id', '=', $identifier)->update(array('login_failed_attempt' => $attempts));
});
}
} catch (Exception $ex) {
Log::error($ex);
}
}
public function lockUser($identifier)
{
$user = OpenIdUser::where('id', '=', $identifier)->first();
if (!is_null($user)) {
\DB::transaction(function () use ($identifier) {
\DB::table('openid_users')->where('id', '=', $identifier)->update(array('lock' => 1));
});
try {
$user = OpenIdUser::where('id', '=', $identifier)->first();
if (!is_null($user)) {
\DB::transaction(function () use ($identifier) {
\DB::table('openid_users')->where('id', '=', $identifier)->update(array('lock' => 1));
});
Log::warning(sprintf("User %d locked ", $identifier));
}
} catch (Exception $ex) {
Log::error($ex);
}
}
@ -77,32 +96,44 @@ class UserService implements IUserService
public function activateUser($identifier)
{
$user = OpenIdUser::where('id', '=', $identifier)->first();
if (!is_null($user)) {
\DB::transaction(function () use ($identifier) {
\DB::table('openid_users')->where('id', '=', $identifier)->update(array('active' => 1));
});
try {
$user = OpenIdUser::where('id', '=', $identifier)->first();
if (!is_null($user)) {
\DB::transaction(function () use ($identifier) {
\DB::table('openid_users')->where('id', '=', $identifier)->update(array('active' => 1));
});
}
} catch (Exception $ex) {
Log::error($ex);
}
}
public function deActivateUser($identifier)
{
$user = OpenIdUser::where('id', '=', $identifier)->first();
if (!is_null($user)) {
\DB::transaction(function () use ($identifier) {
\DB::table('openid_users')->where('id', '=', $identifier)->update(array('active' => 0));
});
try {
$user = OpenIdUser::where('id', '=', $identifier)->first();
if (!is_null($user)) {
\DB::transaction(function () use ($identifier) {
\DB::table('openid_users')->where('id', '=', $identifier)->update(array('active' => 0));
});
}
} catch (Exception $ex) {
Log::error($ex);
}
}
public function saveProfileInfo($identifier, $show_pic, $show_full_name, $show_email)
{
$user = OpenIdUser::where('id', '=', $identifier)->first();
if (!is_null($user)) {
$user->public_profile_show_photo = $show_pic;
$user->public_profile_show_fullname = $show_full_name;
$user->public_profile_show_email = $show_email;
$user->Save();
try {
$user = OpenIdUser::where('id', '=', $identifier)->first();
if (!is_null($user)) {
$user->public_profile_show_photo = $show_pic;
$user->public_profile_show_fullname = $show_full_name;
$user->public_profile_show_email = $show_email;
$user->Save();
}
} catch (Exception $ex) {
Log::error($ex);
}
}
}