AIRAVATA-2342 Retrieves user's realm roles using Keycloak API
Project: http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/repo Commit: http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/commit/c92cc2b9 Tree: http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/tree/c92cc2b9 Diff: http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/diff/c92cc2b9 Branch: refs/heads/develop Commit: c92cc2b9b8d583889c8875e429850c94ee2c348d Parents: 8b483be Author: Marcus Christie <[email protected]> Authored: Thu Mar 23 09:59:45 2017 -0400 Committer: Marcus Christie <[email protected]> Committed: Thu Mar 23 09:59:45 2017 -0400 ---------------------------------------------------------------------- app/controllers/AccountController.php | 1 + app/filters.php | 33 ++++---- app/libraries/Keycloak/API/RoleMapper.php | 83 ++++++++++++++++++++ app/libraries/Keycloak/Keycloak.php | 23 +++++- .../Keycloak/KeycloakServiceProvider.php | 62 ++++++++------- 5 files changed, 154 insertions(+), 48 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/blob/c92cc2b9/app/controllers/AccountController.php ---------------------------------------------------------------------- diff --git a/app/controllers/AccountController.php b/app/controllers/AccountController.php index e98db86..1f69fcf 100644 --- a/app/controllers/AccountController.php +++ b/app/controllers/AccountController.php @@ -187,6 +187,7 @@ class AccountController extends BaseController $expirationTime = time() + $response->expires_in - 5; //5 seconds safe margin $userProfile = Keycloak::getUserProfileFromOAuthToken($accessToken); + Log::debug("userProfile", array($userProfile)); $username = $userProfile['username']; $userRoles = $userProfile['roles']; http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/blob/c92cc2b9/app/filters.php ---------------------------------------------------------------------- diff --git a/app/filters.php b/app/filters.php index 4dc9f38..a7d492a 100755 --- a/app/filters.php +++ b/app/filters.php @@ -20,22 +20,23 @@ App::before(function ($request) { if(Session::has('authz-token')){ $currentTime = time(); if($currentTime > Session::get('oauth-expiration-time')){ - $response = WSIS::getRefreshedOAutheToken(Session::get('oauth-refresh-code')); - if(isset($response->access_token)){ - $accessToken = $response->access_token; - $refreshToken = $response->refresh_token; - $expirationTime = time() + $response->expires_in - 300; - $authzToken = Session::get('authz-token'); - $authzToken->accessToken = $accessToken; - $authzToken->claimsMap['gatewayID'] = Config::get('pga_config.airavata')['gateway-id']; - $authzToken->claimsMap['userName'] = Session::get('username'); - Session::put('authz-token',$authzToken); - Session::put('oauth-refresh-code',$refreshToken); - Session::put('oauth-expiration-time',$expirationTime); - }else{ - Session::flush(); - return Redirect::to('home'); - } + // TODO: implement for Keycloak + // $response = WSIS::getRefreshedOAutheToken(Session::get('oauth-refresh-code')); + // if(isset($response->access_token)){ + // $accessToken = $response->access_token; + // $refreshToken = $response->refresh_token; + // $expirationTime = time() + $response->expires_in - 300; + // $authzToken = Session::get('authz-token'); + // $authzToken->accessToken = $accessToken; + // $authzToken->claimsMap['gatewayID'] = Config::get('pga_config.airavata')['gateway-id']; + // $authzToken->claimsMap['userName'] = Session::get('username'); + // Session::put('authz-token',$authzToken); + // Session::put('oauth-refresh-code',$refreshToken); + // Session::put('oauth-expiration-time',$expirationTime); + // }else{ + // Session::flush(); + // return Redirect::to('home'); + // } } } }); http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/blob/c92cc2b9/app/libraries/Keycloak/API/RoleMapper.php ---------------------------------------------------------------------- diff --git a/app/libraries/Keycloak/API/RoleMapper.php b/app/libraries/Keycloak/API/RoleMapper.php new file mode 100644 index 0000000..2e6d170 --- /dev/null +++ b/app/libraries/Keycloak/API/RoleMapper.php @@ -0,0 +1,83 @@ +<?php +namespace Keycloak\API; + +use Log; + +/** + * RoleMapper class + * + * This class provide an easy to use interface for + * the Keycloak RoleMapper REST API. + */ +class RoleMapper { + + private $base_endpoint_url; + private $admin_username; + private $admin_password; + private $verify_peer; + + public function __construct($base_endpoint_url, $admin_username, $admin_password, $verify_peer) { + $this->base_endpoint_url = $base_endpoint_url; + $this->admin_username = $admin_username; + $this->admin_password = $admin_password; + $this->verify_peer = $verify_peer; + } + + /** + * Get realm-level role mappings for a user + * GET /admin/realms/{realm}/users/{id}/role-mappings/realm + * + * Returns Array of RoleRepresentations + */ + public function getRealmRoleMappingsForUser($realm, $user_id){ + + // curl -H "Authorization: bearer $access_token" https://149.165.156.62:8443/auth/admin/realms/airavata/users/2c9ad2c6-0212-4aef-a5fb-9df862578934/role-mappings/realm + + // get access token for admin API + $access_token = $this->getAPIAccessToken(); + $r = curl_init($this->base_endpoint_url . '/admin/realms/' . rawurlencode($realm) . '/users/' . rawurlencode($user_id) . '/role-mappings/realm'); + curl_setopt($r, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($r, CURLOPT_ENCODING, 1); + curl_setopt($r, CURLOPT_SSL_VERIFYPEER, $this->verify_peer); + curl_setopt($r, CURLOPT_HTTPHEADER, array( + "Authorization: Bearer " . $access_token + )); + + $response = curl_exec($r); + if ($response == false) { + die("curl_exec() failed. Error: " . curl_error($r)); + } + $result = json_decode($response); + // Log::debug("getRealmRoleMappingsForUser result", array($result)); + return $result; + } + + // TODO: factor this out into base class? + private function getAPIAccessToken() { + + // http://www.keycloak.org/docs/2.5/server_development/topics/admin-rest-api.html + // curl -d client_id=admin-cli -d username=username \ + // -d "password=password" -d grant_type=password https://149.165.156.62:8443/auth/realms/master/protocol/openid-connect/token + + $r = curl_init($this->base_endpoint_url . '/realms/master/protocol/openid-connect/token'); + curl_setopt($r, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($r, CURLOPT_ENCODING, 1); + curl_setopt($r, CURLOPT_SSL_VERIFYPEER, $this->verify_peer); + + // Assemble POST parameters for the request. + $post_fields = "client_id=admin-cli&username=" . urlencode($this->admin_username) . "&password=" . urlencode($this->admin_password) . "&grant_type=password"; + + // Obtain and return the access token from the response. + curl_setopt($r, CURLOPT_POST, true); + curl_setopt($r, CURLOPT_POSTFIELDS, $post_fields); + + $response = curl_exec($r); + if ($response == false) { + die("curl_exec() failed. Error: " . curl_error($r)); + } + + $result = json_decode($response); + // Log::debug("API Access Token result", array($result)); + return $result->access_token; + } +} http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/blob/c92cc2b9/app/libraries/Keycloak/Keycloak.php ---------------------------------------------------------------------- diff --git a/app/libraries/Keycloak/Keycloak.php b/app/libraries/Keycloak/Keycloak.php index c1c6f33..93ac99d 100644 --- a/app/libraries/Keycloak/Keycloak.php +++ b/app/libraries/Keycloak/Keycloak.php @@ -2,29 +2,37 @@ namespace Keycloak; +use Keycloak\API\RoleMapper; + use Log; use Illuminate\Routing\UrlGenerator; use Illuminate\Support\Facades\Config; class Keycloak { + private $realm; private $openid_connect_discovery_url; private $client_id; private $client_secret; private $callback_url; private $verify_peer; + private $role_mapper; + /** * Constructor * */ - public function __construct($openid_connect_discovery_url, $client_id, $client_secret, $callback_url, $verify_peer) { + public function __construct($realm, $openid_connect_discovery_url, $client_id, $client_secret, $callback_url, $verify_peer, $base_endpoint_url, $admin_username, $admin_password) { + $this->realm = $realm; $this->openid_connect_discovery_url = $openid_connect_discovery_url; $this->client_id = $client_id; $this->client_secret = $client_secret; $this->callback_url = $callback_url; $this->verify_peer = $verify_peer; + + $this->role_mapper = new RoleMapper($base_endpoint_url, $admin_username, $admin_password, $verify_peer); } public function getOAuthRequestCodeUrl(){ @@ -100,8 +108,14 @@ class Keycloak { $firstname = $userinfo->given_name; $lastname = $userinfo->family_name; $email = $userinfo->email; - // TODO: get roles from Keycloak API - return array('username'=>$username, 'firstname'=>$firstname, 'lastname'=>$lastname, 'email'=>$email, 'roles'=>array()); + + // get roles from Keycloak API + $role_mappings = $this->role_mapper->getRealmRoleMappingsForUser($this->realm, $userinfo->sub); + $roles = []; + foreach ($role_mappings as $role_mapping) { + $roles[] = $role_mapping->name; + } + return array('username'=>$username, 'firstname'=>$firstname, 'lastname'=>$lastname, 'email'=>$email, 'roles'=>$roles); } private function getOpenIDConnectDiscoveryConfiguration() { @@ -114,6 +128,9 @@ class Keycloak { curl_setopt($r, CURLOPT_SSL_VERIFYPEER, $this->verify_peer); $result = curl_exec($r); + if ($result == false) { + die("curl_exec() failed. Error: " . curl_error($r)); + } $json = json_decode($result); http://git-wip-us.apache.org/repos/asf/airavata-php-gateway/blob/c92cc2b9/app/libraries/Keycloak/KeycloakServiceProvider.php ---------------------------------------------------------------------- diff --git a/app/libraries/Keycloak/KeycloakServiceProvider.php b/app/libraries/Keycloak/KeycloakServiceProvider.php index b7e033c..b8bfcbb 100644 --- a/app/libraries/Keycloak/KeycloakServiceProvider.php +++ b/app/libraries/Keycloak/KeycloakServiceProvider.php @@ -5,40 +5,44 @@ use Illuminate\Support\Facades\Config; class KeycloakServiceProvider extends ServiceProvider { - /** - * Indicates if loading of the provider is deferred. - * - * @var bool - */ - protected $defer = false; + /** + * Indicates if loading of the provider is deferred. + * + * @var bool + */ + protected $defer = false; /** - * Bootstrap the application events. - * - * @return void - */ + * Bootstrap the application events. + * + * @return void + */ public function boot() { $this->package('keycloak/keycloak'); } - /** - * Register the service provider. - * - * @return void - */ - public function register() - { + /** + * Register the service provider. + * + * @return void + */ + public function register() + { //registering service provider $this->app['keycloak'] = $this->app->share(function($app) { $identityServerConfig = Config::get('pga_config.wsis'); return new Keycloak( + $identityServerConfig['tenant-domain'], $identityServerConfig['openid-connect-discovery-url'], $identityServerConfig['oauth-client-key'], $identityServerConfig['oauth-client-secret'], $identityServerConfig['oauth-callback-url'], - $identityServerConfig['verify-peer'] + $identityServerConfig['verify-peer'], + $identityServerConfig['service-url'], + $identityServerConfig['admin-username'], + $identityServerConfig['admin-password'] ); }); @@ -48,16 +52,16 @@ class KeycloakServiceProvider extends ServiceProvider { $loader = \Illuminate\Foundation\AliasLoader::getInstance(); $loader->alias('Keycloak', 'Keycloak\Facades\Keycloak'); }); - } - - /** - * Get the services provided by the provider. - * - * @return array - */ - public function provides() - { - return array('wsis'); - } + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('keycloak'); + } }
