Demo OIDC client to an EGI MasterPortal using the RCauth Delegation Server

The demonstrator shows example portal integration code to do a full OpenID-connect handshake with a Master Portal plus the /getproxy request to obtain a (optionally VOMS) proxy certificate.
The different steps are:
  1. Do an /authorize request at the Master Portal
  2. Do a /token request using the received code
  3. Do a /getproxy request using the received access_token
Steps 1. and 2. are standard OIDC steps using the authorization flow, using the Master Portal as OIDC Authorization Server. Step 3. acts as a request to a protected resource using the access_token as bearer token. In step 1. the user is redirected from the Master Portal to the RCauth.eu Online CA for a second OIDC flow and ultimately doing a federated login at the home IdP. The consecutive services passed in step 1. are:
  1. this 'VO portal'
  2. EGI Master Portal
  3. RCauth online CA and its filtering WAYF
  4. home IdP or IdP proxy
Steps 2. and 3. are back-channel interactions between this 'VO portal' and the Master Portal. See here for a detailed description of the flow.

     

<?php
 
//
 // Small demonstration program showing how to obtain a proxy via /getproxy
 // endpoint on a MasterPortal. It is provided as is.
 //
 // Copyright (C) FOM-Nikhef 2016-
 // Licensed under the Apache License, Version 2.0 (the "License")
 //  http://www.apache.org/licenses/LICENSE-2.0
 //
 // Authors: Mischa Salle (msalle (AT) nikhef.nl)
 //
 
ini_set("display_errors",1);

 
// Wrapper for curl
 
function do_curl($url$fields, &$response, &$error)  {
     
//url-ify the data for the POST
     
$fields_string="";
     foreach(
$fields as $key=>$value) {
         
$fields_string .= $key.'='.$value.'&';
     }
     
rtrim($fields_string'&');

     
// open connection
     
$ch curl_init();

     
// set the url, number of POST vars, POST data
     
curl_setopt($ch,CURLOPT_URL$url);
     
curl_setopt($ch,CURLOPT_POSTcount($fields));
     
curl_setopt($ch,CURLOPT_POSTFIELDS$fields_string);

     
curl_setopt($ch,CURLOPT_HEADERfalse);
     
curl_setopt($ch,CURLOPT_RETURNTRANSFERtrue);
     
curl_setopt($ch,CURLOPT_FOLLOWLOCATIONfalse);

     
// force IPv4 resolution
//     curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);

     // next lines are optional, to give cURL debug output in file
     
$curl_log fopen("/tmp/curl_demo_stderr.log""a");
     
curl_setopt($ch,CURLOPT_STDERR$curl_log);
     
curl_setopt($ch,CURLOPT_VERBOSEtrue);

     
// execute post
     
$response curl_exec($ch);
     
$status_code "";
     
$error "";
     if (empty(
$response)) {
    
// probably connection error
    
$error curl_error($ch);
     } else {
        
$status_code curl_getinfo($ch,CURLINFO_HTTP_CODE);
        
$info curl_getinfo($ch);
     }

     
// close connection
     
curl_close($ch);
     return 
$status_code;
 }

 
// Parses curl status code and prints error when available
 
function parse_curl_status($type$status_code$response$error) {
     if (
$status_code >= 300 || !empty($error))     {
         print(
"<html>\n");
         print(
"Error obtaining $type:<BR>\n");
     if (
$error) {
         print(
"CURL error: ".sanitize($error)."<BR><BR>\n");
     } else {
         print(
"Status code: ".$status_code."<BR><BR>\n");
         
// We might get a error= and error_description= back
         
if (strpos($response'error_description=') !== false) {
         
$err=parse_ini_string($response);
         if (isset(
$err['error']))
             print(
"<b>".sanitize($err['error'])."</b>.\n<br>\n");
         if (isset(
$err['error_description']))
             print(
"Description: ".sanitize(urldecode($err['error_description']))."\n<br>\n");
         } else
         print(
"Response:<BR>\n".sanitize($response)."\n");
     }
         print(
"</html>\n");
         exit;
     }
 }

 
// Prints content of the (json parsed) token response
 
function print_token_response($values) {
     print(
"<h1>First cURL response (/token request):</h1>\n");
     
// Decoded response
     
print("<h2>Parsed response:</h2>\n<pre>\n");
     print(
sanitize(print_r($valuestrue)));
     print(
"</pre>\n");
     
// Decoded ID token
     
print("<h2>Parsed ID Token:</h2>\n<pre>\n");
     foreach (
explode("."$values['id_token']) as $block)    {
         
$subblock=json_decode(base64_decode($block));
         print(
sanitize(print_r($subblocktrue)));
     }
     print(
"</pre>\n");
 }

 
// Prints content of the getproxy response
 
function print_getproxy_response($response)    {
     
$proxy tempnam("/tmp""x509up_u");
     
file_put_contents($proxy$response);
     print(
"<h1>Second cURL response (/getproxy request):</h1>\n");
     print(
"<h2>Bare response:</h2>\n<pre>\n");
     print(
sanitize($response));
     print(
"</pre>");
     print(
"<h2>Proxy information:</h2>\n");
     print(
"<pre>\n");
     
passthru("voms-proxy-info -all -text -file ".$proxy);
     
unlink($proxy);
     print(
"</pre>\n");
     print(
"</html>");
 }

 
// Sanitize input
 
function sanitize($input)    {
     return 
htmlspecialchars($inputENT_QUOTES"UTF-8");
 }

 
//////////////////////////////////////////////////////////////////////// 
 // Start of code
 //////////////////////////////////////////////////////////////////////// 

 // Include client variables: $client_id and $client_secret
 // This also sets $base_url, the base address of the Master Portal
 // MAKE SURE TO KEEP OUT OF WEBROOT!!!
 
include("/etc/demo/demobasic_elevator.php");

 
// Set other OIDC parameters
 
$redirect_uri=urlencode("https://" $_SERVER["SERVER_NAME"] . $_SERVER["SCRIPT_NAME"]);

 
// Initialize or resume session: use session ID for state
 
session_start();
 if (empty(
$_SESSION['count'])) {
     
$_SESSION['count'] = 1;
 } else {
     
$_SESSION['count']++;
 }

 
// Check we didn't get back an error or we end up in an endless loop
 
if (isset($_GET['error'])) {
     print(
"<html>\n");
     if (isset(
$_GET['error']))
         print(
"<b>".sanitize($_GET['error'])."</b>.\n<br>\n");
     if (isset(
$_GET['error_description'])) {
         print(
"Description:<br>\n<pre>\n");
         print(
urldecode(sanitize($_GET['error_description'])));
     print(
"</pre>\n<br>\n");
     }
     print(
"</html>\n");
     exit;
 }

 
// Where are we: no code: then redirect browser to /authorize
 
if (!isset($_GET['code'])) {
     if (isset(
$_GET['voms']))
         
$_SESSION['voms']=true;
     else
         
$_SESSION['voms']=false;
     
// authorize request
     
$url $base_url."/authorize";
     
$fields = array(
//         'scope' => 'openid edu.uiuc.ncsa.myproxy.getcert',
//         'scope' => 'openid org.cilogon.userinfo edu.uiuc.ncsa.myproxy.getcert',
         
'scope' => 'openid email profile org.cilogon.userinfo edu.uiuc.ncsa.myproxy.getcert',
         
'response_type' => 'code',
         
'client_id' => $client_id,
         
'redirect_uri' => $redirect_uri,
         
'state' => hash('sha256'session_id())
     );
     
// Add specific IdP hint: this will bypass the WAYF
     
if (isset($_GET['idphint']))
         
$fields['idphint'] = urlencode('https://aai.egi.eu/auth/realms/egi');

     
//url-ify the data for the POST
     
$fields_string="";
     foreach(
$fields as $key=>$value) {
         
$fields_string .= $key.'='.$value.'&';
     }
     
rtrim($fields_string'&');

     
// Redirect
     
header("Location: ".$url."?".$fields_string);
 } else {
     
// first token request
     
$url $base_url."/token";
     
$fields = array(
         
'grant_type' => 'authorization_code',
         
'code' => urlencode($_GET['code']),
         
'redirect_uri' => $redirect_uri,
         
'client_id' => "$client_id",
         
'client_secret' => "$client_secret"
     
);
     
// Run curl and parse status
     
$status_code do_curl($url$fields$response$error);
     
     
// parse status code
     
parse_curl_status("token"$status_code$response$error);

     
// Decoded response
     
$values=json_decode($responsetrue);
     
// Get access token (and ID Token)
     
$access_token=$values['access_token'];
     if (!isset(
$access_token)) {
         print(
"<html>\nCannot find token in response\n");
     print(
"response=<pre>".sanitize($response)."</pre>\n");
     print(
"url=<pre>".$url."</pre>\n");
     print(
"status_code=<pre>".$status_code."</pre>\n");
     print(
"fields=<pre>");
     print(
sanitize(print_r($fieldstrue)));
     print(
"</pre></html>\n");
         exit;
     }
     
$id_token=$values['id_token'];

     
// Print all the output
     
print_token_response($values);

     
// Can optionally do a /userinfo callout here
     
     // getproxy request: either with or without VOMS extensions
     
$url $base_url."/getproxy";
     
$fields = array(
         
'client_id' => "$client_id",
         
'client_secret' => "$client_secret",
         
'access_token' => urlencode($access_token),
//       'proxylifetime' => 86399,
     
);
     
// Add voms request parameters when needed
     
if ($_SESSION['voms'])    {
         
$fields['voname'] = 'rcdemo.aarc-project.eu';
         
$fields['vomses'] = '"rcdemo.aarc-project.eu" "rcvoms.nikhef.nl" "15000" "/DC=org/DC=terena/DC=tcs/C=NL/ST=Noord-Holland/L=Amsterdam/O=Nikhef/CN=pers.nikhef.nl" "rcdemo.aarc-project.eu"';
     }

     
// Run curl and parse status
     
$status_code do_curl($url$fields$response$error);

     
// parse status code
     
parse_curl_status("proxy"$status_code$response$error);

     
// Print all the output
     
print_getproxy_response($response);
 }
?>