<?php /**
 * @file
 * Contains \Drupal\oauth_server_sso\Controller\DefaultController.
 */

namespace Drupal\oauth_server_sso\Controller;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Form\FormBuilder;
use Drupal\Core\Url;
use Drupal\user\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Drupal\oauth_server_sso\DBQueries;
use Drupal\oauth_server_sso\handler;
use Drupal\Component\Utility\Html;
use Drupal\oauth_server_sso\Utilities;

class oauth_server_ssoController extends ControllerBase {

    protected $formBuilder;
    public function __construct(FormBuilder $formBuilder) {
        $this->formBuilder = $formBuilder;
    }

    public static function create(ContainerInterface $container) {
        return new static(
            $container->get("form_builder")
        );
    }

  public static function oauth_server_sso_authorize(){
    $config = \Drupal::config('oauth_server_sso.settings');
    if($config->get('enable_oauth_server') === false){
      handler::show_error_message('OAuth Server module is not enabled','Please navigate to the <b>Settings</b> tab and enable the <b>Master Switch</b>');
    }

    $request = \Drupal::request();
    $client_id = Html::escape($request->get('client_id') ?? '');
    handler::oauth_server_sso_validate_clientId($client_id, true);
    $redirect_uri = Html::escape($request->get('redirect_uri') ?? '');
    handler::oauth_server_sso_validate_redirectUrl($redirect_uri, true);
    if(\Drupal::currentUser()->isAuthenticated()){
      $user = \Drupal::currentUser();
      if (!$user->hasRole('administrator'))
       handler::show_error_message('Single Sign On not Allowed','This module is exclusively for use by Super Users/Administrators and is for demonstration purposes only. The Single Sign On feature for the end users is available in the <a target="_blank" href="https://plugins.miniorange.com/drupal-oauth-server"> premium </a> version of the module.');

      $does_exist = DBQueries::get_user_id($user);
      if($does_exist == FALSE){
        DBQueries::insert_user_in_table($user);
      }
      $scope = Html::escape($request->get('scope') ?? '');
      $state = Html::escape($request->get('state') ?? '');
      $nonce = Html::escape($request->get('nonce') ?? '');
      \Drupal::configFactory()->getEditable('oauth_server_sso.settings')->set('nonce',$nonce)->save();
      handler::oauth_server_sso_validate_scope($scope,$user);
      $code = handler::generateRandom(25);
      DBQueries ::insert_code_from_user_id($code, $user);
      
      $client_redirect_url = \Drupal::config('oauth_server_sso.settings')->get('oauth_server_sso_redirect_url'); 
      $url = Url::fromUri($client_redirect_url, ["query" => ["code"=>$code, "state"=>$state]])->toString();

      \Drupal::logger('oauth_server_sso')->info('<pre><code>'.print_r('auth code sent '.$code, TRUE) .'</code></pre>');
      $code_time = time();
      DBQueries ::insert_code_expiry_from_user_id($code_time,$user);
      $params = [
        'user' => $user->id(),
        'scope' => $scope,
        'url' => $url,
        'state' => $state,
        'redirect_url' => $client_redirect_url,
      ];

      return new RedirectResponse(Url::fromRoute('oauth_server_sso.consent_screen', $params)->toString());
    }else{
     $redirecting_url = Url::fromRoute('oauth_server_sso.authorize', $request->query->all())->toString();
     return new RedirectResponse(Url::fromRoute('user.login', ['destination' => $redirecting_url])->toString());
    }
    return new Response();
  }

  public function oauth_server_sso_access_token(){

    $config = \Drupal::config('oauth_server_sso.settings');
    if($config->get('enable_oauth_server') === false){
      return handler::returnSymfonyResponseObject(json_encode(array("statusCode" => "ERROR","statusMessage" => "OAuth Server module is not enabled")),Response::HTTP_FORBIDDEN);
    }

    $request = \Drupal::request();
    $request_code = Html::escape($request->get('code') ?? '');
    if(empty($request_code)){
      return handler::returnSymfonyResponseObject(json_encode(array("statusCode" => "InvalidAuthCode","statusMessage" => "Authorization code not received")),Response::HTTP_BAD_REQUEST);
    }

    \Drupal::logger('oauth_server_sso')->info('<pre><code>auth code received '.print_r($request_code, TRUE).'</code></pre>');
    $user_details = DBQueries ::get_code_from_user_id($request_code);
    if(!isset($user_details['user_id_val'])){
      return handler::returnSymfonyResponseObject(json_encode(array("statusCode" => "InvalidAuthCode","statusMessage" => "Invalid authorization code received")),Response::HTTP_BAD_REQUEST);
    }

    $user =User::load($user_details['user_id_val']);
    if (!$user->hasRole('administrator')){
       return handler::returnSymfonyResponseObject(json_encode(array("statusCode" => 'ERROR',"statusMessage" => 'This module is exclusively for use by Super Users/Administrators and is for demonstration purposes only. The Premium version of the module offers the Single Sign-On feature for end users')),Response::HTTP_FORBIDDEN);
    }

    $auth_code_validation = handler::validateAuthCode($user_details['auth_code_expiry_time'],$user);
    if ($auth_code_validation instanceof Response) {
      return $auth_code_validation;
    }

    $redirect_uri = Html::escape($request->get('redirect_uri') ?? '');
    $redirect_uri_validation = handler::oauth_server_sso_validate_redirectUrl($redirect_uri);
    if ($redirect_uri_validation instanceof Response) {
      return $redirect_uri_validation;
    }

    $client_id = Html::escape($request->get('client_id') ?? $request->server->get('PHP_AUTH_USER') ?? '');
    $client_id_validation = handler::oauth_server_sso_validate_clientId($client_id);
    if ($client_id_validation instanceof Response) {
      return $client_id_validation;
    }

    $client_secret = Html::escape($request->get('client_secret') ?? $request->server->get('PHP_AUTH_PW') ?? '');
    $client_secret_validation = handler::oauth_server_sso_validate_clientSecret($client_secret);
    if ($client_secret_validation instanceof Response) {
      return $client_secret_validation;
    }

    $grant_type = Html::escape($request->get('grant_type') ?? '');
    $grant_type_validation = handler::oauth_server_sso_validate_grant($grant_type);
    if ($grant_type_validation instanceof Response) {
      return $grant_type_validation;
    }

    $access_token = handler::generateRandom(127);
    DBQueries ::insert_access_token_with_user_id($user_details['user_id_val'], $access_token);
    $server_response= array();
    $scope=$user_details['scope'];
    $scope_values = explode(" ", $scope);
    if(in_array('openid', $scope_values) && $config->get('enable_openid') === true){
      $id_token=self::generateIDToken($client_id,$user_details['user_id_val']);
      $server_response = array('id_token' => $id_token,'access_token' => $access_token, 'expires_in' => 3600, 'token_type' => 'Bearer', 'scope' => $scope);
    }else{
      $server_response = array('access_token' => $access_token, 'expires_in' => 3600, 'token_type' => 'Bearer', 'scope' => $scope);
    }
    $req_time = time();
    DBQueries::insert_access_token_expiry_time($user_details['user_id_val'],$req_time);
    return handler::returnSymfonyResponseObject(json_encode($server_response),Response::HTTP_OK);

  }

  public function generateIDToken($client_id,$user_id){
    global $base_url;
    $user_obj = User::load($user_id);
    $uuid=  $user_obj->uuid();
    $email = $user_obj->getEmail();
    $config = \Drupal::config('oauth_server_sso.settings');
    $name = $user_obj->getAccountName();
    $header_arr = [
        'alg' => 'HS256',
        'typ' => 'JWT'
    ];
    $payload_arr = array(
        'iss'        => $base_url.'/mo',
        'sub'        => $uuid,
        'nonce'      => $config->get('nonce'),
        'aud'        => $client_id,
        'iat'        => time(),
        'exp'        => time()+3600,
        'username'   => $name,
        'email'      => $email,
    );
    $base64UrlHeader  = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode( json_encode( $header_arr ) ));
    $base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode( json_encode( $payload_arr ) ));
    $data = $base64UrlHeader.'.'.$base64UrlPayload;
    $client_secret = \Drupal::config('oauth_server_sso.settings')->get('oauth_server_sso_client_secret');
    //create signature
    $signature = hash_hmac('sha256', $data, $client_secret);
    $signature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));
    $id_token = $data.'.'.$signature;
    return $id_token;
  }

  public function oauth_server_sso_user_info(){

    $config = \Drupal::config('oauth_server_sso.settings');
    if($config->get('enable_oauth_server') === false){
     return handler::returnSymfonyResponseObject(json_encode(array("statusCode" => "ERROR","statusMessage" => "OAuth Server module is not enabled")),Response::HTTP_FORBIDDEN);
    }

    $request = \Drupal::request();
    $header_value = $request->headers->get('Authorization');
    $access_token_received= (!is_null($header_value))&&(strpos($header_value, 'Bearer ') === 0)?substr($header_value, 7):$request->get('access_token');
    if(empty($access_token_received)){
      return handler::returnSymfonyResponseObject(json_encode(array("statusCode" => "InvalidAuthenticationToken","statusMessage" => "Access token is empty")),Response::HTTP_UNAUTHORIZED);
    }

    \Drupal::logger('oauth_server_sso')->info('<pre><code>access token received '.print_r($access_token_received, TRUE).'</code></pre>');
    $user_details= DBQueries::get_user_id_from_access_token($access_token_received);
    if(!isset($user_details['user_id_val'])){
      return handler::returnSymfonyResponseObject(json_encode(array("statusCode" => "InvalidAuthenticationToken","statusMessage" => "Invalid access token received")),Response::HTTP_UNAUTHORIZED);
    }

    $user =User::load($user_details['user_id_val']);
    if (!$user->hasRole('administrator')) {
      return handler::returnSymfonyResponseObject(json_encode(array("statusCode" => 'ERROR',"statusMessage" => 'This module is exclusively for use by Super Users/Administrators and is for demonstration purposes only. The Premium version of the module offers the Single Sign-On feature for end users')),Response::HTTP_FORBIDDEN);
    }

    $req_time =$user_details['access_token_request_time'];
    $validation_result = handler::validateAccessToken($req_time);
    if ($validation_result instanceof Response) {
      return $validation_result;
    }

    $config = \Drupal::config('oauth_server_sso.settings');
    $scope = $user_details['scope'];
    $scope_values = explode(" ", $scope);
    $server_response = array();
    if(in_array('profile', $scope_values)) {
      $server_response= array(
        !empty($config->get('uid_attr_name'))?$config->get('uid_attr_name'):'id'=>$user->id(),
        !empty($config->get('uuid_attr_name'))?$config->get('uuid_attr_name'):'sub'=>$user->uuid(),
        !empty($config->get('name_attr_name'))?$config->get('name_attr_name'):'username'=>$user->getAccountName()
      );
    }
    if(in_array('email', $scope_values)) {
      $server_response = array_merge($server_response,array(
        !empty($config->get('mail_attr_name'))?$config->get('mail_attr_name'):'email'=>$user-> getEmail(),
      ));
    }
    $server_response = array_merge($server_response, ['sub' => $user->uuid()]);
    return handler::returnSymfonyResponseObject(json_encode($server_response),Response::HTTP_OK);
  }

  public function downloadPostmanCollection(){
    $base_url= \Drupal::request()->getSchemeAndHttpHost().\Drupal::request()->getBasePath();
    $request = \Drupal::request();
    $configurations = [];
    $configurations['info'] = [
      "_postman_id" => "2821a659-7b56-48be-9e72-ff95a971ee7f",
			"name" => "miniOrange Drupal OAuth Server",
			"schema" => "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
    ];
    $configurations['item'] = [
        "name" => "Authorization Code Grant",
    ];
    $configurations['item']['request'] = [
      'auth' => [
        'type'   => 'oauth2',
        'oauth2' => Utilities::getOAuthServerConfigurations()
      ],
      "method" => "GET",
      "header" => [],
      "url" => [
        "raw" => $base_url.'/mo/oauth2/userinfo',
        "protocol" => $request->getScheme(),
        "host" => [
          $request->getHost()
        ],
        "path" => [str_replace($request->getSchemeAndHttpHost(), '', $base_url),"mo","oauth2", "userinfo"],
        "query" => []
      ]
    ];
    $configurations['item']['response'] = [];
    header("Content-Disposition: attachment; filename = miniOrange_Drupal_OAuth_Server_Postman_Collection.json");
    echo(json_encode($configurations, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
    exit;
  }

  public function oauthServerDiscoveryEndpointInfo() {
    $response_type_supported = ["code"];
    $grant_type_supported = ["authorization_code"];
    $id_token_signing_alg_values_supported = ["HS256"];
    $scope_supported = ["openid", "email", "profile"];
    $claim_supported = ["iss", "sub", "aud", "iat", "exp", "nonce", "username", "email"];
    $auth_method_supported = ["client_secret_post", "client_secret_basic"];
    $subject_types_supported = ["public"];
    $configurations = [
        'issuer' => Url::fromUri('internal:/mo', ['absolute' => TRUE])->toString(),
        'authorization_endpoint' => Url::fromUri('internal:/mo/oauth2/authorize', ['absolute' => TRUE])->toString(),
        'token_endpoint' => Url::fromUri('internal:/mo/oauth2/token', ['absolute' => TRUE])->toString(),
        'userinfo_endpoint' => Url::fromUri('internal:/mo/oauth2/userinfo', ['absolute' => TRUE])->toString(),
        'jwks_uri' => Url::fromUri('internal:/mo/keys', ['absolute' => TRUE])->toString(),
        'response_types_supported' => $response_type_supported,
        'grant_types_supported' => $grant_type_supported,
        'id_token_signing_alg_values_supported' => $id_token_signing_alg_values_supported,
        'scopes_supported' => $scope_supported,
        'claims_supported' => $claim_supported,
        'token_endpoint_auth_methods_supported'=> $auth_method_supported,
        'subject_types_supported' => $subject_types_supported
    ];

    $jsonConfigurations = json_encode($configurations, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
    return handler::returnSymfonyResponseObject($jsonConfigurations, Response::HTTP_OK);
}

  public function openAddNewClientForm() {
    $response = new AjaxResponse();
    $provider_info['add_new_client_info'] = [
        '#type' => 'item',
        '#markup' => '<p>' . $this->t('The free version of this module allows configuration of only one client application. To configure multiple applications, please upgrade to the <a href=":licensing">Premium</a> version.', [
            ':licensing' => Url::fromRoute('oauth_server_sso.licensing')->toString(),
        ]) . '</p>',
    ];
    $ajax_form = new OpenModalDialogCommand('Add New OAuth/OpenID Client', $provider_info, ['width' => '40%']);
    $response->addCommand($ajax_form);
    return $response;
  }

  public function openAddMoreCallbackUrlsForm() {
    $response = new AjaxResponse();
    $provider_info['add_new_client_info'] = [
        '#type' => 'item',
        '#markup' => '<p>' . $this->t('The free version of the module allows only one Callback/Redirect URL. To add multiple callback URLs, please upgrade to the ').'<a href=":licensing">'.t('PREMIUM').'</a>'.t(' version.', [
            ':licensing' => Url::fromRoute('oauth_server_sso.licensing')->toString()
        ]). '</p>',
    ];
    $ajax_form = new OpenModalDialogCommand('Add Callback URL', $provider_info, ['width' => '40%']);
    $response->addCommand($ajax_form);
    return $response;
  }

}
