
In order to migrate IDP from LV 4.x to latest LV version, following task were performed: * Updated namespace to be complain with PSR-4 * General Refactoring: moved all DB access code from services to repositories. * Migration to LV 5.X: these migration guides were applied - https://laravel.com/docs/5.3/upgrade#upgrade-5.0 - https://laravel.com/docs/5.3/upgrade#upgrade-5.1.0 - https://laravel.com/docs/5.3/upgrade#upgrade-5.2.0 * Improved caching: added repositories decorators in order to add REDIS cache to queries, entities Change-Id: I8edf9f5fce6585129701c88bb88332f242307534
768 lines
19 KiB
PHP
768 lines
19 KiB
PHP
<?php namespace Models\OAuth2;
|
|
/**
|
|
* Copyright 2016 OpenStack Foundation
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
**/
|
|
use jwa\cryptographic_algorithms\ContentEncryptionAlgorithms_Registry;
|
|
use jwa\cryptographic_algorithms\DigitalSignatures_MACs_Registry;
|
|
use jwa\cryptographic_algorithms\KeyManagementAlgorithms_Registry;
|
|
use OAuth2\Models\IClient;
|
|
use OAuth2\Models\IClientPublicKey;
|
|
use OAuth2\Models\JWTResponseInfo;
|
|
use OAuth2\Models\TokenEndpointAuthInfo;
|
|
use Utils\Model\BaseModelEloquent;
|
|
use OAuth2\Models\IApiScope;
|
|
use Exception;
|
|
use DateTime;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Config;
|
|
/**
|
|
* Class Client
|
|
* @package Models
|
|
*/
|
|
class Client extends BaseModelEloquent implements IClient
|
|
{
|
|
|
|
|
|
protected $fillable = array
|
|
(
|
|
'app_name',
|
|
'app_description',
|
|
'app_logo',
|
|
'client_id',
|
|
'client_secret',
|
|
'client_type',
|
|
'active',
|
|
'locked',
|
|
'user_id',
|
|
'created_at',
|
|
'updated_at',
|
|
'max_auth_codes_issuance_qty',
|
|
'max_auth_codes_issuance_basis',
|
|
'max_access_token_issuance_qty',
|
|
'max_access_token_issuance_basis',
|
|
'max_refresh_token_issuance_qty',
|
|
'max_refresh_token_issuance_basis',
|
|
'use_refresh_token',
|
|
'rotate_refresh_token',
|
|
'resource_server_id',
|
|
'website',
|
|
'application_type',
|
|
'client_secret_expires_at',
|
|
'contacts',
|
|
'logo_uri',
|
|
'tos_uri',
|
|
'post_logout_redirect_uris',
|
|
'logout_uri',
|
|
'logout_session_required',
|
|
'logout_use_iframe',
|
|
'policy_uri',
|
|
'jwks_uri',
|
|
'default_max_age',
|
|
'require_auth_time',
|
|
'token_endpoint_auth_method',
|
|
'token_endpoint_auth_signing_alg',
|
|
'subject_type',
|
|
'userinfo_signed_response_alg',
|
|
'userinfo_encrypted_response_alg',
|
|
'userinfo_encrypted_response_enc',
|
|
'id_token_signed_response_alg',
|
|
'id_token_encrypted_response_alg',
|
|
'id_token_encrypted_response_enc',
|
|
'redirect_uris',
|
|
'allowed_origins',
|
|
'edited_by_id',
|
|
);
|
|
|
|
public static $valid_app_types = array
|
|
(
|
|
IClient::ApplicationType_Service,
|
|
IClient::ApplicationType_JS_Client,
|
|
IClient::ApplicationType_Web_App,
|
|
IClient::ApplicationType_Native
|
|
);
|
|
|
|
public static $valid_subject_types = array
|
|
(
|
|
IClient::SubjectType_Public,
|
|
IClient::SubjectType_Pairwise
|
|
);
|
|
|
|
protected $table = 'oauth2_client';
|
|
|
|
public function getActiveAttribute()
|
|
{
|
|
return (bool) $this->attributes['active'];
|
|
}
|
|
|
|
public function getIdAttribute()
|
|
{
|
|
return (int) $this->attributes['id'];
|
|
}
|
|
|
|
public function getLockedAttribute()
|
|
{
|
|
return (int) $this->attributes['locked'];
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
|
*/
|
|
public function public_keys()
|
|
{
|
|
return $this->hasMany('Models\OAuth2\ClientPublicKey','oauth2_client_id','id');
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
|
*/
|
|
public function admin_users()
|
|
{
|
|
return $this->belongsToMany('Auth\User','oauth2_client_admin_users','oauth2_client_id','user_id');
|
|
}
|
|
|
|
/**
|
|
* @param string $value
|
|
*/
|
|
public function setApplicationTypeAttribute($value)
|
|
{
|
|
$this->attributes['application_type'] = strtolower($value);
|
|
$this->attributes['client_type'] = $this->infereClientTypeFromAppType($value);
|
|
}
|
|
|
|
/**
|
|
* @param string $app_type
|
|
* @return string
|
|
*/
|
|
private function infereClientTypeFromAppType($app_type)
|
|
{
|
|
switch($app_type)
|
|
{
|
|
case IClient::ApplicationType_JS_Client:
|
|
return IClient::ClientType_Public;
|
|
break;
|
|
default:
|
|
return IClient::ClientType_Confidential;
|
|
break;
|
|
}
|
|
}
|
|
|
|
public function access_tokens()
|
|
{
|
|
return $this->hasMany('Models\OAuth2\AccessToken');
|
|
}
|
|
|
|
public function refresh_tokens()
|
|
{
|
|
return $this->hasMany('Models\OAuth2\RefreshToken');
|
|
}
|
|
|
|
public function user()
|
|
{
|
|
return $this->belongsTo('Auth\User');
|
|
}
|
|
|
|
public function edited_by()
|
|
{
|
|
return $this->belongsTo('Auth\User','edited_by_id');
|
|
}
|
|
|
|
public function resource_server()
|
|
{
|
|
return $this->belongsTo('Models\OAuth2\ResourceServer');
|
|
}
|
|
|
|
public function scopes()
|
|
{
|
|
return $this->belongsToMany('Models\OAuth2\ApiScope','oauth2_client_api_scope','client_id','scope_id');
|
|
}
|
|
|
|
/**
|
|
* @return $this
|
|
*/
|
|
public function removeAllScopes()
|
|
{
|
|
$this->scopes()->detach();
|
|
return $this;
|
|
}
|
|
|
|
public function getClientId()
|
|
{
|
|
return $this->client_id;
|
|
}
|
|
|
|
public function getClientSecret()
|
|
{
|
|
return $this->client_secret;
|
|
}
|
|
|
|
public function getClientType()
|
|
{
|
|
return $this->client_type;
|
|
}
|
|
|
|
public function getClientScopes()
|
|
{
|
|
$scopes = $this->scopes()
|
|
->with('api')
|
|
->with('api.resource_server')
|
|
->where('active','=',true)
|
|
->orderBy('api_id')->get();
|
|
|
|
$res = array();
|
|
|
|
foreach($scopes as $db_scope)
|
|
{
|
|
$api = !is_null($db_scope)?$db_scope->api()->first():null;
|
|
$resource_server = !is_null($api) ? $api->resource_server()->first():null;
|
|
if(!is_null($resource_server) && $resource_server->active && !is_null($api) && $api->active)
|
|
array_push($res,$db_scope);
|
|
}
|
|
return $res;
|
|
}
|
|
|
|
public function getRedirectUris()
|
|
{
|
|
return explode(',',$this->redirect_uris);
|
|
}
|
|
|
|
public function isScopeAllowed($scope)
|
|
{
|
|
$res = true;
|
|
$desired_scopes = explode(" ",$scope);
|
|
foreach($desired_scopes as $desired_scope){
|
|
//check if desired scope belongs to application given scopes
|
|
$db_scope = $this->getActiveScope($desired_scope);
|
|
$api = !is_null($db_scope) ? $db_scope->getApi() : null;
|
|
$resource_server = !is_null($api) ? $api->getResourceServer() : null;
|
|
|
|
if(is_null($db_scope) ||(!is_null($api) && !$api->active) || (!is_null($resource_server) && !$resource_server->active)){
|
|
$res = false;
|
|
break;
|
|
}
|
|
}
|
|
return $res;
|
|
}
|
|
|
|
/**
|
|
* @param string $name
|
|
* @return IApiScope
|
|
*/
|
|
public function getActiveScope($name){
|
|
return Cache::remember
|
|
(
|
|
'api_scope_'.$this->id.'_'.$name,
|
|
Config::get("cache_regions.region_api_scope_lifetime", 1140),
|
|
function() use($name){
|
|
return $this->scopes()->where('name', '=', $name)->where('active', '=', true)->first();
|
|
}
|
|
);
|
|
}
|
|
|
|
public function isUriAllowed($uri)
|
|
{
|
|
if(!filter_var($uri, FILTER_VALIDATE_URL)) return false;
|
|
$parts = @parse_url($uri);
|
|
if ($parts == false)
|
|
{
|
|
return false;
|
|
}
|
|
if(($this->application_type !== IClient::ApplicationType_Native && $parts['scheme']!=='https') && (ServerConfigurationService::getConfigValue("SSL.Enable")))
|
|
return false;
|
|
//normalize uri
|
|
$normalized_uri = $parts['scheme'].'://'.strtolower($parts['host']);
|
|
if(isset($parts['port'])) {
|
|
$normalized_uri .= ':'.strtolower($parts['port']);
|
|
}
|
|
if(isset($parts['path'])) {
|
|
$normalized_uri .= strtolower($parts['path']);
|
|
}
|
|
// normalize url and remove trailing /
|
|
$normalized_uri = rtrim($normalized_uri, '/');
|
|
|
|
return str_contains($this->redirect_uris, $normalized_uri);
|
|
}
|
|
|
|
public function getApplicationName()
|
|
{
|
|
return $this->app_name;
|
|
}
|
|
|
|
public function getApplicationLogo()
|
|
{
|
|
$app_logo = $this->app_logo;
|
|
if(is_null($app_logo) || empty($app_logo))
|
|
$app_logo = asset('assets/img/oauth2.default.logo.png');
|
|
$app_logo_url = $this->logo_uri;
|
|
if(!empty($app_logo_url))
|
|
$app_logo = $app_logo_url;
|
|
return $app_logo;
|
|
}
|
|
|
|
public function getApplicationDescription()
|
|
{
|
|
return $this->app_description;
|
|
}
|
|
|
|
public function getDeveloperEmail()
|
|
{
|
|
$user = $this->user()->first();
|
|
$email = $user->getEmail();
|
|
return $email;
|
|
}
|
|
|
|
public function getUserId(){
|
|
$user = $this->user()->first();
|
|
return $user->id;
|
|
}
|
|
|
|
public function getId()
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function isLocked()
|
|
{
|
|
return $this->locked;
|
|
}
|
|
|
|
public function isActive()
|
|
{
|
|
return $this->active;
|
|
}
|
|
|
|
public function isResourceServerClient()
|
|
{
|
|
$id = $this->resource_server_id;
|
|
return !is_null($id);
|
|
}
|
|
|
|
public function getResourceServer()
|
|
{
|
|
return Cache::remember
|
|
(
|
|
'resource_server_'.$this->resource_server_id,
|
|
Config::get("cache_regions.region_resource_server_lifetime", 60),
|
|
function() {
|
|
return $this->resource_server()->first();
|
|
});
|
|
}
|
|
|
|
public function getApplicationType()
|
|
{
|
|
return $this->application_type;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
* @throws Exception
|
|
*/
|
|
public function getFriendlyApplicationType()
|
|
{
|
|
switch($this->application_type){
|
|
case IClient::ApplicationType_JS_Client:
|
|
return 'Client Side (JS)';
|
|
break;
|
|
case IClient::ApplicationType_Service:
|
|
return 'Service Account';
|
|
break;
|
|
case IClient::ApplicationType_Web_App:
|
|
return 'Web Server Application';
|
|
break;
|
|
case IClient::ApplicationType_Native:
|
|
return 'Native Application';
|
|
break;
|
|
}
|
|
throw new Exception('Invalid Application Type');
|
|
}
|
|
|
|
public function getClientAllowedOrigins()
|
|
{
|
|
return explode(',', $this->allowed_origins);
|
|
}
|
|
|
|
/**
|
|
* the origin is the triple {protocol, host, port}
|
|
* @param $origin
|
|
* @return bool
|
|
*/
|
|
public function isOriginAllowed($origin)
|
|
{
|
|
if(!filter_var($origin, FILTER_VALIDATE_URL)) return false;
|
|
$parts = @parse_url($origin);
|
|
if ($parts == false) {
|
|
return false;
|
|
}
|
|
if($parts['scheme']!=='https')
|
|
return false;
|
|
$origin_without_port = $parts['scheme'].'://'.$parts['host'];
|
|
|
|
if(str_contains($this->allowed_origins,$origin_without_port )) return true;
|
|
if(isset($parts['port'])){
|
|
$origin_with_port = $parts['scheme'].'://'.$parts['host'].':'.$parts['port'];
|
|
return str_contains($this->allowed_origins, $origin_with_port );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function getWebsite()
|
|
{
|
|
$res = $this->website;
|
|
if(empty($res)) $res = '#';
|
|
return $res;
|
|
}
|
|
|
|
/**
|
|
* @return DateTime
|
|
*/
|
|
public function getClientSecretExpiration()
|
|
{
|
|
$exp_date = $this->client_secret_expires_at;
|
|
if(is_null($exp_date)) return null;
|
|
|
|
if($exp_date instanceof DateTime)
|
|
return $exp_date;
|
|
return new DateTime($exp_date);
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function isClientSecretExpired()
|
|
{
|
|
$now = new DateTime();
|
|
$exp_date = $this->getClientSecretExpiration();
|
|
|
|
if(is_null($exp_date)) return false;
|
|
return $exp_date < $now;
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getContacts()
|
|
{
|
|
return explode(',',$this->contacts);
|
|
}
|
|
|
|
/**
|
|
* @return int
|
|
*/
|
|
public function getDefaultMaxAge()
|
|
{
|
|
return (int)$this->default_max_age;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function requireAuthTimeClaim()
|
|
{
|
|
return $this->require_auth_time;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getLogoUri()
|
|
{
|
|
return $this->logo_uri;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getPolicyUri()
|
|
{
|
|
$res = $this->policy_uri;
|
|
if(empty($res)) $res = '#';
|
|
return $res;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getTermOfServiceUri()
|
|
{
|
|
$res = $this->tos_uri;
|
|
if(empty($res)) $res = '#';
|
|
return $res;
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getPostLogoutUris()
|
|
{
|
|
return explode(',', $this->post_logout_redirect_uris);
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getLogoutUri()
|
|
{
|
|
return $this->logout_uri;
|
|
}
|
|
|
|
/**
|
|
* @return JWTResponseInfo
|
|
*/
|
|
public function getIdTokenResponseInfo()
|
|
{
|
|
return new JWTResponseInfo
|
|
(
|
|
DigitalSignatures_MACs_Registry::getInstance()->get($this->id_token_signed_response_alg),
|
|
KeyManagementAlgorithms_Registry::getInstance()->get($this->id_token_encrypted_response_alg),
|
|
ContentEncryptionAlgorithms_Registry::getInstance()->get($this->id_token_encrypted_response_enc)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return JWTResponseInfo
|
|
*/
|
|
public function getUserInfoResponseInfo()
|
|
{
|
|
return new JWTResponseInfo
|
|
(
|
|
DigitalSignatures_MACs_Registry::getInstance()->get($this->userinfo_signed_response_alg),
|
|
KeyManagementAlgorithms_Registry::getInstance()->get($this->userinfo_encrypted_response_alg),
|
|
ContentEncryptionAlgorithms_Registry::getInstance()->get($this->userinfo_encrypted_response_enc)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return TokenEndpointAuthInfo
|
|
*/
|
|
public function getTokenEndpointAuthInfo()
|
|
{
|
|
return new TokenEndpointAuthInfo(
|
|
$this->token_endpoint_auth_method,
|
|
DigitalSignatures_MACs_Registry::getInstance()->isSupported($this->token_endpoint_auth_signing_alg) ?
|
|
DigitalSignatures_MACs_Registry::getInstance()->get($this->token_endpoint_auth_signing_alg) :
|
|
null
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getSubjectType()
|
|
{
|
|
return $this->subject_type;
|
|
}
|
|
|
|
/**
|
|
* @return IClientPublicKey[]
|
|
*/
|
|
public function getPublicKeys()
|
|
{
|
|
return $this->public_keys()->get();
|
|
}
|
|
|
|
/**
|
|
* @return IClientPublicKey[]
|
|
*/
|
|
public function getPublicKeysByUse($use)
|
|
{
|
|
return $this->public_keys()->where('usage','=',$use)->all();
|
|
}
|
|
|
|
/**
|
|
* @param string $kid
|
|
* @return IClientPublicKey
|
|
*/
|
|
public function getPublicKeyByIdentifier($kid)
|
|
{
|
|
return $this->public_keys()->where('kid','=',$kid)->first();
|
|
}
|
|
|
|
/**
|
|
* @param IClientPublicKey $public_key
|
|
* @return $this
|
|
*/
|
|
public function addPublicKey(IClientPublicKey $public_key)
|
|
{
|
|
$this->public_keys()->save($public_key);
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getJWKSUri()
|
|
{
|
|
return $this->jwks_uri;
|
|
}
|
|
|
|
/**
|
|
* @param string $use
|
|
* @param string $alg
|
|
* @return IClientPublicKey
|
|
*/
|
|
public function getCurrentPublicKeyByUse($use, $alg)
|
|
{
|
|
$now = new \DateTime();
|
|
|
|
return $this->public_keys()
|
|
->where('usage','=',$use)
|
|
->where('alg','=',$alg)
|
|
->where('active','=', true)
|
|
->where('valid_from','<=',$now)
|
|
->where('valid_to','>=',$now)
|
|
->first();
|
|
}
|
|
|
|
/**
|
|
* @param string $post_logout_uri
|
|
* @return bool
|
|
*/
|
|
public function isPostLogoutUriAllowed($post_logout_uri)
|
|
{
|
|
if(!filter_var($post_logout_uri, FILTER_VALIDATE_URL)) return false;
|
|
|
|
$parts = @parse_url($post_logout_uri);
|
|
|
|
if ($parts == false) {
|
|
return false;
|
|
}
|
|
if($parts['scheme']!=='https')
|
|
return false;
|
|
|
|
$logout_without_port = $parts['scheme'].'://'.$parts['host'];
|
|
|
|
if(str_contains($this->post_logout_redirect_uris, $logout_without_port )) return true;
|
|
|
|
if(isset($parts['port']))
|
|
{
|
|
$logout_with_port = $parts['scheme'].'://'.$parts['host'].':'.$parts['port'];
|
|
return str_contains($this->post_logout_redirect_uris, $logout_with_port );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @param $user
|
|
* @return $this
|
|
*/
|
|
public function addAdminUser($user)
|
|
{
|
|
$this->admin_users()->attach($user->id);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param $user
|
|
* @return $this
|
|
*/
|
|
public function removeAdminUser($user)
|
|
{
|
|
$this->admin_users()->detach($user->id);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @return $this
|
|
*/
|
|
public function removeAllAdminUsers(){
|
|
$this->admin_users()->detach();
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param $user
|
|
* @return bool
|
|
*/
|
|
public function candEdit($user)
|
|
{
|
|
$is_admin = $this->admin_users()->where('id', '=', $user->id)->count() > 0;
|
|
$is_owner = intval($this->user_id) === intval($user->id);
|
|
return $is_owner || $is_admin;
|
|
}
|
|
|
|
/**
|
|
* @param $user
|
|
* @return bool
|
|
*/
|
|
public function canDelete($user)
|
|
{
|
|
return $this->isOwner($user);
|
|
}
|
|
|
|
public function isOwner($user)
|
|
{
|
|
return intval($this->user_id) === intval($user->id);
|
|
}
|
|
|
|
/**
|
|
* @param $user
|
|
* @return $this
|
|
*/
|
|
public function setOwner($user)
|
|
{
|
|
$this->user()->associate($user);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param IApiScope $scope
|
|
* @return $this
|
|
*/
|
|
public function addScope(IApiScope $scope)
|
|
{
|
|
$this->scopes()->attach($scope->id);
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @param $editing_user
|
|
* @return $this
|
|
*/
|
|
public function setEditedBy($editing_user){
|
|
$this->edited_by()->associate($editing_user);
|
|
return $this;
|
|
}
|
|
|
|
public function getEditedByNice()
|
|
{
|
|
$user = $this->edited_by()->first();
|
|
return is_null($user)? 'N/A':$user->getEmail();
|
|
}
|
|
|
|
public function getOwnerNice()
|
|
{
|
|
$user = $this->user()->first();
|
|
return is_null($user)? 'N/A':$user->getEmail();
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function useRefreshToken()
|
|
{
|
|
return (bool)$this->use_refresh_token;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function useRotateRefreshTokenPolicy()
|
|
{
|
|
return (bool)$this->rotate_refresh_token;
|
|
}
|
|
|
|
/**
|
|
* @return AccessToken[]
|
|
*/
|
|
public function getValidAccessTokens()
|
|
{
|
|
return $this->access_tokens()->whereRaw(" DATE_ADD(created_at, INTERVAL lifetime second) >= UTC_TIMESTAMP() ")->get();
|
|
}
|
|
} |