Fix on Resource server banning:

Added some logic to prevent Resource server Banning.
Also refactored a little and added more config params
to Server Admin console.

Change-Id: I6b2961e25bbb67f682188f1a38eedf81ce1ebcf4
This commit is contained in:
Sebastian Marcet 2016-01-07 11:44:00 -03:00
parent a33cc901b2
commit 37e236a2bb
16 changed files with 432 additions and 196 deletions

View File

@ -51,6 +51,6 @@ return array(
'OAuth2SecurityPolicy_MaxBearerTokenDisclosureAttempts' => 5,
'OAuth2SecurityPolicy_MaxInvalidClientExceptionAttempts' => 10,
'OAuth2SecurityPolicy_MaxInvalidRedeemAuthCodeAttempts' => 10,
'OAuth2SecurityPolicy_MaxInvalidInvalidClientCredentialsAttempts' => 5,
'OAuth2SecurityPolicy_MaxInvalidClientCredentialsAttempts' => 5,
'Banning_Enable' => true,
);

View File

@ -321,20 +321,32 @@ class AdminController extends BaseController {
$user = $this->auth_service->getCurrentUser();
$config_values = array();
$dictionary = array
(
'MaxFailed.Login.Attempts',
'MaxFailed.LoginAttempts.2ShowCaptcha',
'OpenId.Private.Association.Lifetime',
'OpenId.Session.Association.Lifetime',
'OpenId.Nonce.Lifetime',
'OAuth2.AuthorizationCode.Lifetime',
'OAuth2.AccessToken.Lifetime',
'OAuth2.IdToken.Lifetime',
'OAuth2.RefreshToken.Lifetime',
'OAuth2.AccessToken.Revoked.Lifetime',
'OAuth2.AccessToken.Void.Lifetime',
'OAuth2.RefreshToken.Revoked.Lifetime',
'OAuth2SecurityPolicy.MaxBearerTokenDisclosureAttempts',
'OAuth2SecurityPolicy.MinutesWithoutExceptions',
'OAuth2SecurityPolicy.MaxInvalidClientExceptionAttempts',
'OAuth2SecurityPolicy.MaxInvalidRedeemAuthCodeAttempts',
'OAuth2SecurityPolicy.MaxInvalidClientCredentialsAttempts',
);
$config_values['MaxFailed.Login.Attempts'] = $this->configuration_service->getConfigValue('MaxFailed.Login.Attempts');
$config_values['MaxFailed.LoginAttempts.2ShowCaptcha'] = $this->configuration_service->getConfigValue('MaxFailed.LoginAttempts.2ShowCaptcha');
foreach($dictionary as $key)
$config_values[$key] = $this->configuration_service->getConfigValue($key);
$config_values['OpenId.Private.Association.Lifetime'] = $this->configuration_service->getConfigValue('OpenId.Private.Association.Lifetime');
$config_values['OpenId.Session.Association.Lifetime'] = $this->configuration_service->getConfigValue('OpenId.Session.Association.Lifetime');
$config_values['OpenId.Nonce.Lifetime'] = $this->configuration_service->getConfigValue('OpenId.Nonce.Lifetime');
$config_values['OAuth2.AuthorizationCode.Lifetime'] = $this->configuration_service->getConfigValue('OAuth2.AuthorizationCode.Lifetime');
$config_values['OAuth2.AccessToken.Lifetime'] = $this->configuration_service->getConfigValue('OAuth2.AccessToken.Lifetime');
$config_values['OAuth2.IdToken.Lifetime'] = $this->configuration_service->getConfigValue('OAuth2.IdToken.Lifetime');
$config_values['OAuth2.RefreshToken.Lifetime'] = $this->configuration_service->getConfigValue('OAuth2.RefreshToken.Lifetime');
return View::make("admin.server-config", array
return View::make("admin.server-config",
array
(
"username" => $user->getFullName(),
"user_id" => $user->getId(),
@ -349,40 +361,60 @@ class AdminController extends BaseController {
$values = Input::all();
$rules = array(
'general-max-failed-login-attempts' => 'required|integer',
'general-max-failed-login-attempts-captcha' => 'required|integer',
'openid-private-association-lifetime' => 'required|integer',
'openid-session-association-lifetime' => 'required|integer',
'openid-nonce-lifetime' => 'required|integer',
'oauth2-auth-code-lifetime' => 'required|integer',
'oauth2-refresh-token-lifetime' => 'required|integer',
'oauth2-access-token-lifetime' => 'required|integer',
'oauth2-id-token-lifetime' => 'required|integer',
$rules = array
(
'general-max-failed-login-attempts' => 'required|integer',
'general-max-failed-login-attempts-captcha' => 'required|integer',
'openid-private-association-lifetime' => 'required|integer',
'openid-session-association-lifetime' => 'required|integer',
'openid-nonce-lifetime' => 'required|integer',
'oauth2-auth-code-lifetime' => 'required|integer',
'oauth2-refresh-token-lifetime' => 'required|integer',
'oauth2-access-token-lifetime' => 'required|integer',
'oauth2-id-token-lifetime' => 'required|integer',
'oauth2-id-access-token-revoked-lifetime' => 'required|integer',
'oauth2-id-access-token-void-lifetime' => 'required|integer',
'oauth2-id-refresh-token-revoked-lifetime' => 'required|integer',
'oauth2-id-security-policy-minutes-without-exceptions' => 'required|integer',
'oauth2-id-security-policy-max-bearer-token-disclosure-attempts' => 'required|integer',
'oauth2-id-security-policy-max-invalid-client-exception-attempts' => 'required|integer',
'oauth2-id-security-policy-max-invalid-redeem-auth-code-attempts' => 'required|integer',
'oauth2-id-security-policy-max-invalid-client-credentials-attempts' => 'required|integer',
);
$dictionary = array(
'general-max-failed-login-attempts' => 'MaxFailed.Login.Attempts',
'general-max-failed-login-attempts-captcha' => 'MaxFailed.LoginAttempts.2ShowCaptcha',
'openid-private-association-lifetime' => 'OpenId.Private.Association.Lifetime',
'openid-session-association-lifetime' => 'OpenId.Session.Association.Lifetime',
'openid-nonce-lifetime' => 'OpenId.Nonce.Lifetime',
'oauth2-auth-code-lifetime' => 'OAuth2.AuthorizationCode.Lifetime',
'oauth2-access-token-lifetime' => 'OAuth2.AccessToken.Lifetime',
'oauth2-id-token-lifetime' => 'OAuth2.IdToken.Lifetime',
'oauth2-refresh-token-lifetime' => 'OAuth2.RefreshToken.Lifetime',
$dictionary = array
(
'general-max-failed-login-attempts' => 'MaxFailed.Login.Attempts',
'general-max-failed-login-attempts-captcha' => 'MaxFailed.LoginAttempts.2ShowCaptcha',
'openid-private-association-lifetime' => 'OpenId.Private.Association.Lifetime',
'openid-session-association-lifetime' => 'OpenId.Session.Association.Lifetime',
'openid-nonce-lifetime' => 'OpenId.Nonce.Lifetime',
'oauth2-auth-code-lifetime' => 'OAuth2.AuthorizationCode.Lifetime',
'oauth2-access-token-lifetime' => 'OAuth2.AccessToken.Lifetime',
'oauth2-id-token-lifetime' => 'OAuth2.IdToken.Lifetime',
'oauth2-refresh-token-lifetime' => 'OAuth2.RefreshToken.Lifetime',
'oauth2-id-access-token-revoked-lifetime' => 'OAuth2.AccessToken.Revoked.Lifetime',
'oauth2-id-access-token-void-lifetime' => 'OAuth2.AccessToken.Void.Lifetime',
'oauth2-id-refresh-token-revoked-lifetime' => 'OAuth2.RefreshToken.Revoked.Lifetime',
'oauth2-id-security-policy-minutes-without-exceptions' => 'OAuth2SecurityPolicy.MinutesWithoutExceptions',
'oauth2-id-security-policy-max-bearer-token-disclosure-attempts' => 'OAuth2SecurityPolicy.MaxBearerTokenDisclosureAttempts',
'oauth2-id-security-policy-max-invalid-client-exception-attempts' => 'OAuth2SecurityPolicy.MaxInvalidClientExceptionAttempts',
'oauth2-id-security-policy-max-invalid-redeem-auth-code-attempts' => 'OAuth2SecurityPolicy.MaxInvalidRedeemAuthCodeAttempts',
'oauth2-id-security-policy-max-invalid-client-credentials-attempts' => 'OAuth2SecurityPolicy.MaxInvalidClientCredentialsAttempts',
);
// Creates a Validator instance and validates the data.
$validation = Validator::make($values, $rules);
if ($validation->fails()) {
if ($validation->fails())
{
return Redirect::action("AdminController@listServerConfig")->withErrors($validation);
}
foreach($values as $field=>$value){
if(array_key_exists($field,$dictionary))
$this->configuration_service->saveConfigValue($dictionary[$field],$value);
foreach($values as $field => $value)
{
if(array_key_exists($field, $dictionary))
$this->configuration_service->saveConfigValue($dictionary[$field], $value);
}
return Redirect::action("AdminController@listServerConfig");

View File

@ -240,6 +240,12 @@ class ValidateBearerTokenGrantType extends AbstractGrantType
$this->log_service->error($ex2);
throw new BearerTokenDisclosureAttemptException($ex2->getMessage());
}
catch(ExpiredAccessTokenException $ex3)
{
$this->log_service->error($ex3);
$this->token_service->expireAccessToken($token_value);
throw $ex3;
}
}
/**

View File

@ -22,7 +22,7 @@ interface IResourceServerService {
* @param int $page_nbr
* @return mixed
*/
public function getAll($page_nbr=1,$page_size=10,array $filters = array(), array $fields=array('*'));
public function getAll($page_nbr = 1,$page_size = 10, array $filters = array(), array $fields=array('*'));
/**
* @param IResourceServer $resource_server
@ -71,4 +71,10 @@ interface IResourceServerService {
* @return string
*/
public function regenerateClientSecret($id);
/**
* @param string $ip
* @return IResourceServer
*/
public function getByIPAddress($ip);
}

View File

@ -156,6 +156,13 @@ interface ITokenService {
*/
public function revokeAccessToken($value, $is_hashed = false);
/**
* @param $value
* @param bool|false $is_hashed
* @return bool
*/
public function expireAccessToken($value, $is_hashed = false);
/**
* @param $value refresh_token value
* @param bool $is_hashed

View File

@ -2,7 +2,7 @@
namespace utils\services;
use utils\exceptions\UnacquiredLockException;
use Closure;
/**
* Interface ILockManagerService
* @package utils\services
@ -14,6 +14,19 @@ interface ILockManagerService {
* @throws UnacquiredLockException
* @return mixed
*/
public function acquireLock($name,$lifetime=3600);
public function acquireLock($name,$lifetime = 3600);
/**
* @param $name
* @return mixed
*/
public function releaseLock($name);
/**
* @param $name
* @param Closure $callback
* @param int $lifetime
* @return mixed
*/
public function lock($name, Closure $callback, $lifetime = 3600);
}

View File

@ -5,7 +5,20 @@
*/
class AccessToken extends Eloquent {
protected $fillable = array('value','user_id', 'from_ip', 'associated_authorization_code','lifetime','scope','audience','created_at','updated_at','client_id','refresh_token_id');
protected $fillable = array
(
'value',
'user_id',
'from_ip',
'associated_authorization_code',
'lifetime',
'scope',
'audience',
'created_at',
'updated_at',
'client_id',
'refresh_token_id'
);
protected $table = 'oauth2_access_token';

View File

@ -17,7 +17,7 @@ use utils\db\ITransactionService;
* Class ResourceServerService
* @package services\oauth2
*/
class ResourceServerService implements IResourceServerService
final class ResourceServerService implements IResourceServerService
{
private $client_service;
@ -29,7 +29,7 @@ class ResourceServerService implements IResourceServerService
public function __construct(IClientService $client_service, ITransactionService $tx_service)
{
$this->client_service = $client_service;
$this->tx_service = $tx_service;
$this->tx_service = $tx_service;
}
/**
@ -186,6 +186,12 @@ class ResourceServerService implements IResourceServerService
$host));
}
if (ResourceServer::where('ip', '=', $ip)->count() > 0)
{
throw new InvalidResourceServer(sprintf('there is already another resource server with that ip (%s).',
$ip));
}
if (ResourceServer::where('friendly_name', '=', $friendly_name)->count() > 0) {
throw new InvalidResourceServer(sprintf('there is already another resource server with that friendly name (%s).',
$friendly_name));
@ -245,4 +251,13 @@ class ResourceServerService implements IResourceServerService
return $res;
}
/**
* @param string $ip
* @return IResourceServer
*/
public function getByIPAddress($ip)
{
return ResourceServer::where('ip', '=', $ip)->first();
}
}

View File

@ -772,7 +772,6 @@ final class TokenService implements ITokenService
) {
//hash the given value, bc tokens values are stored hashed on DB
$hashed_value = !$is_hashed ? Hash::compute('sha256', $value) : $value;
$lock_name = '';
$access_token = null;
try
@ -780,39 +779,33 @@ final class TokenService implements ITokenService
// check cache ...
if (!$cache_service->exists($hashed_value))
{
// check on DB...
$access_token_db = DBAccessToken::where('value', '=', $hashed_value)->first();
if (is_null($access_token_db))
{
if($this_var->isAccessTokenRevoked($hashed_value))
$lock_manager_service->lock('lock.get.accesstoken.' . $hashed_value, function() use($value, $hashed_value, $this_var){
// check on DB...
$access_token_db = DBAccessToken::where('value', '=', $hashed_value)->first();
if (is_null($access_token_db))
{
throw new RevokedAccessTokenException(sprintf('Access token %s is revoked!', $value));
if($this_var->isAccessTokenRevoked($hashed_value))
{
throw new RevokedAccessTokenException(sprintf('Access token %s is revoked!', $value));
}
else if($this_var->isAccessTokenVoid($hashed_value)) // check if its marked on cache as expired ...
{
throw new ExpiredAccessTokenException(sprintf('Access token %s is expired!', $value));
}
else
{
throw new InvalidGrantTypeException(sprintf("Access token %s is invalid!", $value));
}
}
else if($this_var->isAccessTokenVoid($hashed_value)) // check if its marked on cache as expired ...
if ($access_token_db->isVoid())
{
// invalid one ...
throw new ExpiredAccessTokenException(sprintf('Access token %s is expired!', $value));
}
else
{
throw new InvalidGrantTypeException(sprintf("Access token %s is invalid!", $value));
}
}
// lock ...
$lock_name = 'lock.get.accesstoken.' . $hashed_value;
$lock_manager_service->acquireLock($lock_name);
if ($access_token_db->isVoid())
{
// invalid one ...
// add to cache as expired ...
$this_var->markAccessTokenAsVoid($hashed_value);
// and deleted it from db
$access_token_db->delete();
throw new ExpiredAccessTokenException(sprintf('Access token %s is expired!', $value));
}
//reload on cache
$this_var->storesDBAccessTokenOnCache($access_token_db);
//release lock
$lock_manager_service->releaseLock($lock_name);
//reload on cache
$this_var->storesDBAccessTokenOnCache($access_token_db);
});
}
$cache_values = $cache_service->getHash($hashed_value, array
@ -861,19 +854,11 @@ final class TokenService implements ITokenService
$refresh_token = $this_var->getRefreshToken($refresh_token_value, true);
$access_token->setRefreshToken($refresh_token);
}
} catch (UnacquiredLockException $ex1) {
throw new InvalidAccessTokenException("access token %s ", $value);
} catch (ExpiredAccessTokenException $ex2) {
if (!empty($lock_name)) {
$lock_manager_service->releaseLock($lock_name);
}
} catch (\Exception $ex) {
if (!empty($lock_name)) {
$lock_manager_service->releaseLock($lock_name);
}
throw $ex;
}
catch (UnacquiredLockException $ex1)
{
throw new InvalidAccessTokenException("access token %s ", $value);
}
return $access_token;
});
}
@ -1080,6 +1065,7 @@ final class TokenService implements ITokenService
$hashed_value = !$is_hashed ? Hash::compute('sha256', $value) : $value;
$access_token_db = DBAccessToken::where('value', '=', $hashed_value)->first();
if(is_null($access_token_db)) return false;
$client = $access_token_db->client()->first();
//delete from cache
$res = $cache_service->delete($hashed_value);
@ -1099,6 +1085,41 @@ final class TokenService implements ITokenService
}
/**
* @param $value
* @param bool|false $is_hashed
* @return bool
*/
public function expireAccessToken($value, $is_hashed = false)
{
$cache_service = $this->cache_service;
$this_var = $this;
return $this->tx_service->transaction(function () use ($value, $is_hashed, $cache_service, $this_var) {
$res = 0;
//hash the given value, bc tokens values are stored hashed on DB
$hashed_value = !$is_hashed ? Hash::compute('sha256', $value) : $value;
$access_token_db = DBAccessToken::where('value', '=', $hashed_value)->first();
if(is_null($access_token_db)) return false;
$client = $access_token_db->client()->first();
//delete from cache
$res = $cache_service->delete($hashed_value);
$res = $cache_service->deleteMemberSet
(
$client->client_id . TokenService::ClientAccessTokenPrefixList,
$access_token_db->value
);
//check on DB... and delete it
$res = $access_token_db->delete();
$this_var->markAccessTokenAsVoid($hashed_value);
return $res > 0;
});
}
/**
* Revokes all related tokens to a specific client id
* @param $client_id
@ -1587,4 +1608,5 @@ final class TokenService implements ITokenService
if(is_null($db_access_token)) return null;
return $this->getAccessToken($db_access_token->value, true);
}
}

View File

@ -1,17 +1,18 @@
<?php
namespace services\openid;
use openid\exceptions\InvalidAssociation;
use openid\exceptions\OpenIdInvalidRealmException;
use openid\exceptions\ReplayAttackException;
use openid\exceptions\InvalidAssociation;
use openid\helpers\OpenIdErrorMessages;
use openid\model\IAssociation;
use openid\repositories\IOpenIdAssociationRepository;
use openid\services\IAssociationService;
use OpenIdAssociation;
use utils\exceptions\UnacquiredLockException;
use utils\services\ILockManagerService;
use utils\services\ICacheService;
use openid\repositories\IOpenIdAssociationRepository;
use utils\services\ILockManagerService;
/**
* Class AssociationService
@ -22,20 +23,21 @@ class AssociationService implements IAssociationService
private $lock_manager_service;
private $cache_service;
private $repository;
private $repository;
/**
* @param IOpenIdAssociationRepository $repository
* @param ILockManagerService $lock_manager_service
* @param ICacheService $cache_service
*/
public function __construct(IOpenIdAssociationRepository $repository,
ILockManagerService $lock_manager_service,
ICacheService $cache_service)
{
/**
* @param IOpenIdAssociationRepository $repository
* @param ILockManagerService $lock_manager_service
* @param ICacheService $cache_service
*/
public function __construct(
IOpenIdAssociationRepository $repository,
ILockManagerService $lock_manager_service,
ICacheService $cache_service
) {
$this->lock_manager_service = $lock_manager_service;
$this->cache_service = $cache_service;
$this->repository = $repository;
$this->cache_service = $cache_service;
$this->repository = $repository;
}
/**
@ -58,26 +60,29 @@ class AssociationService implements IAssociationService
if (!$this->cache_service->exists($handle)) {
// if not , check on db
$assoc = $this->repository->getByHandle($handle);
if(is_null($assoc))
throw new InvalidAssociation(sprintf('openid association %s does not exists!',$handle));
if (is_null($assoc)) {
throw new InvalidAssociation(sprintf('openid association %s does not exists!', $handle));
}
//check association lifetime ...
$remaining_lifetime = $assoc->getRemainingLifetime();
if ($remaining_lifetime < 0) {
$this->deleteAssociation($handle);
return null;
}
//convert secret to hexa representation
// bin2hex
$secret_unpack = \unpack('H*', $assoc->secret);
$secret_unpack = array_shift($secret_unpack);
//convert secret to hexa representation
// bin2hex
$secret_unpack = \unpack('H*', $assoc->secret);
$secret_unpack = array_shift($secret_unpack);
//repopulate cache
$this->cache_service->storeHash($handle, array(
"type" => $assoc->type,
"type" => $assoc->type,
"mac_function" => $assoc->mac_function,
"issued" => $assoc->issued,
"lifetime" => $assoc->lifetime,
"secret" => $secret_unpack,
"realm" => $assoc->realm),
"issued" => $assoc->issued,
"lifetime" => $assoc->lifetime,
"secret" => $secret_unpack,
"realm" => $assoc->realm
),
$remaining_lifetime);
}
@ -88,32 +93,37 @@ class AssociationService implements IAssociationService
"issued",
"lifetime",
"secret",
"realm"));
"realm"
));
if ($cache_values['type'] == IAssociation::TypePrivate) {
if (is_null($realm) || empty($realm) || $cache_values['realm'] != $realm) {
throw new OpenIdInvalidRealmException(sprintf(OpenIdErrorMessages::InvalidPrivateAssociationMessage, $handle, $realm));
throw new OpenIdInvalidRealmException(sprintf(OpenIdErrorMessages::InvalidPrivateAssociationMessage,
$handle, $realm));
}
// only one time we could use this handle
$this->lock_manager_service->acquireLock($lock_name);
}
//convert hex 2 bin
$secret = \pack('H*', $cache_values['secret']);
$assoc = new OpenIdAssociation();
//convert hex 2 bin
$secret = \pack('H*', $cache_values['secret']);
$assoc = new OpenIdAssociation();
$assoc->type = $cache_values['type'];
$assoc->type = $cache_values['type'];
$assoc->mac_function = $cache_values['mac_function'];
$assoc->issued = $cache_values['issued'];
$assoc->lifetime = intval($cache_values['lifetime']);
$assoc->secret = $secret;
$realm = $cache_values['realm'];
if (!empty($realm))
$assoc->issued = $cache_values['issued'];
$assoc->lifetime = intval($cache_values['lifetime']);
$assoc->secret = $secret;
$realm = $cache_values['realm'];
if (!empty($realm)) {
$assoc->realm = $realm;
}
return $assoc;
} catch (UnacquiredLockException $ex1) {
throw new ReplayAttackException(sprintf(OpenIdErrorMessages::ReplayAttackPrivateAssociationAlreadyUsed, $handle));
throw new ReplayAttackException(sprintf(OpenIdErrorMessages::ReplayAttackPrivateAssociationAlreadyUsed,
$handle));
}
}
@ -125,57 +135,61 @@ class AssociationService implements IAssociationService
{
$this->cache_service->delete($handle);
$assoc = $this->repository->getByHandle($handle);
if (!is_null($assoc)) {
if (!is_null($assoc)) {
return $this->repository->delete($assoc);
}
return false;
}
/**
* @param IAssociation $association
* @return IAssociation|OpenIdAssociation
* @throws \openid\exceptions\ReplayAttackException
*/
public function addAssociation(IAssociation $association)
/**
* @param IAssociation $association
* @return IAssociation|OpenIdAssociation
* @throws \openid\exceptions\ReplayAttackException
*/
public function addAssociation(IAssociation $association)
{
$assoc = new OpenIdAssociation();
try {
$lock_name = 'lock.add.assoc.' . $association->getHandle();
$this->lock_manager_service->acquireLock($lock_name);
$assoc->identifier = $association->getHandle();;
$assoc->secret = $association->getSecret();
$assoc->type = $association->getType();;
$assoc->identifier = $association->getHandle();;
$assoc->secret = $association->getSecret();
$assoc->type = $association->getType();;
$assoc->mac_function = $association->getMacFunction();
$assoc->lifetime = intval($association->getLifetime());
$assoc->issued = $association->getIssued();
$assoc->lifetime = intval($association->getLifetime());
$assoc->issued = $association->getIssued();
if (!is_null($association->getRealm()))
if (!is_null($association->getRealm())) {
$assoc->realm = $association->getRealm();
}
if ($association->getType() == IAssociation::TypeSession) {
$this->repository->add($assoc);
}
//convert secret to hexa representation
// bin2hex
$secret_unpack = \unpack('H*', $association->getSecret());
$secret_unpack = array_shift($secret_unpack);
//convert secret to hexa representation
// bin2hex
$secret_unpack = \unpack('H*', $association->getSecret());
$secret_unpack = array_shift($secret_unpack);
$this->cache_service->storeHash($association->getHandle(),
array(
"type" => $association->getType(),
"mac_function" => $association->getMacFunction(),
"issued" => $association->getIssued(),
"lifetime" => intval($association->getLifetime()),
"secret" => $secret_unpack,
"realm" => !is_null($association->getRealm())?$association->getRealm():''
),
intval($association->getLifetime())
array(
"type" => $association->getType(),
"mac_function" => $association->getMacFunction(),
"issued" => $association->getIssued(),
"lifetime" => intval($association->getLifetime()),
"secret" => $secret_unpack,
"realm" => !is_null($association->getRealm()) ? $association->getRealm() : ''
),
intval($association->getLifetime())
);
} catch (UnacquiredLockException $ex1) {
throw new ReplayAttackException(sprintf(OpenIdErrorMessages::ReplayAttackPrivateAssociationAlreadyUsed, $association->getHandle()));
throw new ReplayAttackException(sprintf(OpenIdErrorMessages::ReplayAttackPrivateAssociationAlreadyUsed,
$association->getHandle()));
}
return $assoc;
}

View File

@ -7,6 +7,7 @@ use DateTime;
use DB;
use Exception;
use Log;
use oauth2\services\IResourceServerService;
use UserExceptionTrail;
use utils\db\ITransactionService;
use utils\exceptions\UnacquiredLockException;
@ -23,21 +24,34 @@ use utils\services\IServerConfigurationService;
class BlacklistSecurityPolicy extends AbstractBlacklistSecurityPolicy
{
/**
* @var array
*/
private $exception_dictionary = array();
/**
* @var IResourceServerService
*/
private $resource_server_service;
/**
* BlacklistSecurityPolicy constructor.
* @param IServerConfigurationService $server_configuration_service
* @param ILockManagerService $lock_manager_service
* @param ICacheService $cache_service
* @param IResourceServerService $resource_server_service
* @param ITransactionService $tx_service
*/
public function __construct(
public function __construct
(
IServerConfigurationService $server_configuration_service,
ILockManagerService $lock_manager_service,
ICacheService $cache_service,
ITransactionService $tx_service
ILockManagerService $lock_manager_service,
ICacheService $cache_service,
IResourceServerService $resource_server_service,
ITransactionService $tx_service
) {
parent::__construct($server_configuration_service, $lock_manager_service, $cache_service, $tx_service);
$this->resource_server_service = $resource_server_service;
// here we configure on which exceptions are we interested and the max occurrence attempts and initial delay on tar pit for
// offending IP address
$this->exception_dictionary = array(
@ -94,13 +108,12 @@ class BlacklistSecurityPolicy extends AbstractBlacklistSecurityPolicy
*/
public function check()
{
$res = true;
$res = true;
$remote_address = IPHelper::getUserIp();
try {
//check if banned ip is on cache ...
if ($this->cache_service->incCounterIfExists($remote_address)) {
$this->counter_measure->trigger();
return false;
}
//check on db
@ -155,14 +168,16 @@ class BlacklistSecurityPolicy extends AbstractBlacklistSecurityPolicy
{
try
{
$remote_ip = IPHelper::getUserIp();
$remote_ip = IPHelper::getUserIp();
$exception_class = get_class($ex);
//check exception count by type on last "MinutesWithoutExceptions" minutes...
$exception_count = intval(UserExceptionTrail::where('from_ip', '=', $remote_ip)
->where('exception_type', '=', $exception_class)
->where('created_at', '>',
DB::raw('( UTC_TIMESTAMP() - INTERVAL ' . $this->server_configuration_service->getConfigValue("BlacklistSecurityPolicy.MinutesWithoutExceptions") . ' MINUTE )'))
->count());
if (array_key_exists($exception_class, $this->exception_dictionary))
{
$params = $this->exception_dictionary[$exception_class];
@ -181,7 +196,7 @@ class BlacklistSecurityPolicy extends AbstractBlacklistSecurityPolicy
);
$initial_delay_on_tar_pit = intval($this->server_configuration_service->getConfigValue($params[1]));
if ($exception_count >= $max_attempts)
if ($exception_count >= $max_attempts && !$this->isIPAddressWhitelisted($remote_ip))
{
Log::warning
(
@ -206,6 +221,16 @@ class BlacklistSecurityPolicy extends AbstractBlacklistSecurityPolicy
}
}
/**
* @param string $ip
* @return bool
*/
private function isIPAddressWhitelisted($ip)
{
$rs = $this->resource_server_service->getByIPAddress($ip);
return !is_null($rs);
}
}

View File

@ -34,7 +34,7 @@ class OAuth2SecurityPolicy implements ISecurityPolicy{
'auth2\exceptions\BearerTokenDisclosureAttemptException' => array('OAuth2SecurityPolicy.MaxBearerTokenDisclosureAttempts'),
'auth2\exceptions\InvalidClientException' => array('OAuth2SecurityPolicy.MaxInvalidClientExceptionAttempts'),
'auth2\exceptions\InvalidRedeemAuthCodeException' => array('OAuth2SecurityPolicy.MaxInvalidRedeemAuthCodeAttempts'),
'auth2\exceptions\InvalidClientCredentials' => array('OAuth2SecurityPolicy.MaxInvalidInvalidClientCredentialsAttempts'),
'auth2\exceptions\InvalidClientCredentials' => array('OAuth2SecurityPolicy.MaxInvalidClientCredentialsAttempts'),
);
}
/**

View File

@ -5,25 +5,78 @@ namespace services\utils;
use utils\services\ICacheService;
use utils\services\ILockManagerService;
use utils\exceptions\UnacquiredLockException;
use Closure;
class LockManagerService implements ILockManagerService {
/**
* Class LockManagerService
* @package services\utils
*/
final class LockManagerService implements ILockManagerService {
/**
* @var ICacheService
*/
private $cache_service;
/**
* LockManagerService constructor.
* @param ICacheService $cache_service
*/
public function __construct(ICacheService $cache_service){
$this->cache_service = $cache_service;
}
public function acquireLock($name,$lifetime=3600)
/**
* @param string $name
* @param int $lifetime
* @throws UnacquiredLockException
*/
public function acquireLock($name,$lifetime = 3600)
{
$success = $this->cache_service->addSingleValue($name,time()+$lifetime+1,time()+$lifetime+1);
if (!$success) { // only one time we could use this handle
$time = time()+$lifetime+1;
$success = $this->cache_service->addSingleValue($name, $time, $time);
if (!$success)
{
// only one time we could use this handle
throw new UnacquiredLockException(sprintf("lock name %s",$name));
}
}
/**
* @param string $name
*/
public function releaseLock($name)
{
$this->cache_service->delete($name);
}
/**
* @param string $name
* @param Closure $callback
* @param int $lifetime
* @return null
* @throws UnacquiredLockException
* @throws \Exception
*/
public function lock($name, Closure $callback, $lifetime = 3600)
{
$result = null;
try
{
$this->acquireLock($name, $lifetime);
$result = $callback($this);
$this->releaseLock($name);
}
catch(UnacquiredLockException $ex1)
{
throw $ex1;
}
catch(\Exception $ex)
{
$this->releaseLock($name);
throw $ex;
}
return $result;
}
}

View File

@ -109,36 +109,24 @@ class ServerConfigurationService implements IOpenIdServerConfigurationService, I
10);
$this->default_config_params["OAuth2.AuthorizationCode.Lifetime"] = Config::get('server.OAuth2_AuthorizationCode_Lifetime',
240);
$this->default_config_params["OAuth2.AccessToken.Lifetime"] = Config::get('server.OAuth2_AccessToken_Lifetime',
3600);
$this->default_config_params["OAuth2.IdToken.Lifetime"] = Config::get('server.OAuth2_IdToken_Lifetime',
3600);
$this->default_config_params["OAuth2.AuthorizationCode.Lifetime"] = Config::get('server.OAuth2_AuthorizationCode_Lifetime', 240);
$this->default_config_params["OAuth2.AccessToken.Lifetime"] = Config::get('server.OAuth2_AccessToken_Lifetime', 3600);
$this->default_config_params["OAuth2.IdToken.Lifetime"] = Config::get('server.OAuth2_IdToken_Lifetime', 3600);
//infinite by default
$this->default_config_params["OAuth2.RefreshToken.Lifetime"] = Config::get('server.OAuth2_RefreshToken_Lifetime',
0);
$this->default_config_params["OAuth2.AccessToken.Revoked.Lifetime"] = Config::get('server.OAuth2_AccessToken_Revoked_Lifetime', 600);
$this->default_config_params["OAuth2.AccessToken.Void.Lifetime"] = Config::get('server.OAuth2_AccessToken_Void_Lifetime', 600);
$this->default_config_params["OAuth2.RefreshToken.Revoked.Lifetime"] = Config::get('server.OAuth2_RefreshToken_Revoked_Lifetime', 600);
$this->default_config_params["OAuth2.RefreshToken.Lifetime"] = Config::get('server.OAuth2_RefreshToken_Lifetime', 0);
//revoked lifetimes
$this->default_config_params["OAuth2.AccessToken.Revoked.Lifetime"] = Config::get('server.OAuth2_AccessToken_Revoked_Lifetime', 3600);
$this->default_config_params["OAuth2.AccessToken.Void.Lifetime"] = Config::get('server.OAuth2_AccessToken_Void_Lifetime', 3600);
$this->default_config_params["OAuth2.RefreshToken.Revoked.Lifetime"] = Config::get('server.OAuth2_RefreshToken_Revoked_Lifetime', 3600);
//oauth2 policy defaults
$this->default_config_params["OAuth2SecurityPolicy.MinutesWithoutExceptions"] = Config::get('server.OAuth2SecurityPolicy_MinutesWithoutExceptions',
2);
$this->default_config_params["OAuth2SecurityPolicy.MaxBearerTokenDisclosureAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxBearerTokenDisclosureAttempts',
5);
$this->default_config_params["OAuth2SecurityPolicy.MaxInvalidClientExceptionAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxInvalidClientExceptionAttempts',
10);
$this->default_config_params["OAuth2SecurityPolicy.MaxInvalidRedeemAuthCodeAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxInvalidRedeemAuthCodeAttempts',
10);
$this->default_config_params["OAuth2SecurityPolicy.MaxInvalidInvalidClientCredentialsAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxInvalidInvalidClientCredentialsAttempts',
5);
$this->default_config_params["OAuth2SecurityPolicy.MinutesWithoutExceptions"] = Config::get('server.OAuth2SecurityPolicy_MinutesWithoutExceptions', 2);
$this->default_config_params["OAuth2SecurityPolicy.MaxBearerTokenDisclosureAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxBearerTokenDisclosureAttempts', 5);
$this->default_config_params["OAuth2SecurityPolicy.MaxInvalidClientExceptionAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxInvalidClientExceptionAttempts', 10);
$this->default_config_params["OAuth2SecurityPolicy.MaxInvalidRedeemAuthCodeAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxInvalidRedeemAuthCodeAttempts', 10);
$this->default_config_params["OAuth2SecurityPolicy.MaxInvalidClientCredentialsAttempts"] = Config::get('server.OAuth2SecurityPolicy_MaxInvalidClientCredentialsAttempts', 5);
//ssl
$this->default_config_params["SSL.Enable"] = Config::get('server.SSL_Enable', true);
}
public function getUserIdentityEndpointURL($identifier)

View File

@ -48,6 +48,40 @@
<label for="oauth2-refresh-token-lifetime">Refresh Token Lifetime&nbsp;<span class="glyphicon glyphicon-info-sign accordion-toggle" aria-hidden="true" title="in seconds - zero value means infinite"></span></label>
<input class="form-control" type="number" min="0" step="1" id="oauth2-refresh-token-lifetime" name="oauth2-refresh-token-lifetime" value="{{$config_values['OAuth2.RefreshToken.Lifetime']}}"/>
</div>
<div class="form-group">
<label for="oauth2-id-access-token-revoked-lifetime">Revoked Access Token Lifetime&nbsp;<span class="glyphicon glyphicon-info-sign accordion-toggle" aria-hidden="true" title="in seconds"></span></label>
<input class="form-control" type="number" min="600" step="1" id="oauth2-id-access-token-revoked-lifetime" name="oauth2-id-access-token-revoked-lifetime" value="{{$config_values['OAuth2.AccessToken.Revoked.Lifetime']}}"/>
</div>
<div class="form-group">
<label for="oauth2-id-access-token-void-lifetime">Void Access Token Lifetime&nbsp;<span class="glyphicon glyphicon-info-sign accordion-toggle" aria-hidden="true" title="in seconds"></span></label>
<input class="form-control" type="number" min="600" step="1" id="oauth2-id-access-token-void-lifetime" name="oauth2-id-access-token-void-lifetime" value="{{$config_values['OAuth2.AccessToken.Void.Lifetime']}}"/>
</div>
<div class="form-group">
<label for="oauth2-id-refresh-token-revoked-lifetime">Revoked Refresh Token Lifetime&nbsp;<span class="glyphicon glyphicon-info-sign accordion-toggle" aria-hidden="true" title="in seconds"></span></label>
<input class="form-control" type="number" min="600" step="1" id="oauth2-id-refresh-token-revoked-lifetime" name="oauth2-id-refresh-token-revoked-lifetime" value="{{$config_values['OAuth2.RefreshToken.Revoked.Lifetime']}}"/>
</div>
<legend>OAUTH2 Security Policies Configuration</legend>
<div class="form-group">
<label for="oauth2-id-security-policy-minutes-without-exceptions">Minutes without exceptions rate&nbsp;<span class="glyphicon glyphicon-info-sign accordion-toggle" aria-hidden="true" title="in minutes"></span></label>
<input class="form-control" type="number" min="1" step="1" id="oauth2-id-security-policy-minutes-without-exceptions" name="oauth2-id-security-policy-minutes-without-exceptions" value="{{$config_values['OAuth2SecurityPolicy.MinutesWithoutExceptions']}}"/>
</div>
<div class="form-group">
<label for="oauth2-id-security-policy-max-bearer-token-disclosure-attempts">Max. Bearer token disclousure exceptions attemps&nbsp;<span class="glyphicon glyphicon-info-sign accordion-toggle" aria-hidden="true" title="in attemps"></span></label>
<input class="form-control" type="number" min="3" step="1" id="oauth2-id-security-policy-max-bearer-token-disclosure-attempts" name="oauth2-id-security-policy-max-bearer-token-disclosure-attempts" value="{{$config_values['OAuth2SecurityPolicy.MaxBearerTokenDisclosureAttempts']}}"/>
</div>
<div class="form-group">
<label for="oauth2-id-security-policy-max-invalid-client-exception-attempts">Max. invalid client exception attemps&nbsp;<span class="glyphicon glyphicon-info-sign accordion-toggle" aria-hidden="true" title="in attemps"></span></label>
<input class="form-control" type="number" min="3" step="1" id="oauth2-id-security-policy-max-invalid-client-exception-attempts" name="oauth2-id-security-policy-max-invalid-client-exception-attempts" value="{{$config_values['OAuth2SecurityPolicy.MaxInvalidClientExceptionAttempts']}}"/>
</div>
<div class="form-group">
<label for="oauth2-id-security-policy-max-invalid-redeem-auth-code-attempts">Max. invalid redeem Auth Code exception attemps&nbsp;<span class="glyphicon glyphicon-info-sign accordion-toggle" aria-hidden="true" title="in attemps"></span></label>
<input class="form-control" type="number" min="3" step="1" id="oauth2-id-security-policy-max-invalid-redeem-auth-code-attempts" name="oauth2-id-security-policy-max-invalid-redeem-auth-code-attempts" value="{{$config_values['OAuth2SecurityPolicy.MaxInvalidRedeemAuthCodeAttempts']}}"/>
</div>
<div class="form-group">
<label for="oauth2-id-security-policy-max-invalid-client-credentials-attempts">Max. invalid client credentials exception attemps&nbsp;<span class="glyphicon glyphicon-info-sign accordion-toggle" aria-hidden="true" title="in attemps"></span></label>
<input class="form-control" type="number" min="3" step="1" id="oauth2-id-security-policy-max-invalid-client-credentials-attempts" name="oauth2-id-security-policy-max-invalid-client-credentials-attempts" value="{{$config_values['OAuth2SecurityPolicy.MaxInvalidClientCredentialsAttempts']}}"/>
</div>
<button type="submit" class="btn btn-default btn-md active">Submit</button>
</form>
</div>

View File

@ -6,15 +6,23 @@ jQuery(document).ready(function($){
var server_config_validation = form_server_config.validate({
rules: {
"general-max-failed-login-attempts" : {required: true, number: true },
"general-max-failed-login-attempts-captcha" : {required: true, number: true },
"openid-private-association-lifetime" : {required: true, number: true },
"openid-session-association-lifetime" : {required: true, number: true },
"openid-nonce-lifetime" : {required: true, number: true },
"oauth2-auth-code-lifetime" : {required: true, number: true },
"oauth2-refresh-token-lifetime" : {required: true, number: true },
"oauth2-access-token-lifetime" : {required: true, number: true },
"oauth2-id-token-lifetime" : {required: true, number: true }
"general-max-failed-login-attempts" : {required: true, number: true },
"general-max-failed-login-attempts-captcha" : {required: true, number: true },
"openid-private-association-lifetime" : {required: true, number: true },
"openid-session-association-lifetime" : {required: true, number: true },
"openid-nonce-lifetime" : {required: true, number: true },
"oauth2-auth-code-lifetime" : {required: true, number: true },
"oauth2-refresh-token-lifetime" : {required: true, number: true },
"oauth2-access-token-lifetime" : {required: true, number: true },
"oauth2-id-token-lifetime" : {required: true, number: true },
"oauth2-id-access-token-revoked-lifetime" : {required: true, number: true },
"oauth2-id-access-token-void-lifetime" : {required: true, number: true },
"oauth2-id-refresh-token-revoked-lifetime" : {required: true, number: true },
"oauth2-id-security-policy-minutes-without-exceptions" : {required: true, number: true },
"oauth2-id-security-policy-max-bearer-token-disclosure-attempts" : {required: true, number: true },
"oauth2-id-security-policy-max-invalid-client-exception-attempts" : {required: true, number: true },
"oauth2-id-security-policy-max-invalid-redeem-auth-code-attempts" : {required: true, number: true },
"oauth2-id-security-policy-max-invalid-client-credentials-attempts" : {required: true, number: true },
}
});