eveseat/eseye

View on GitHub
bin/index.php

Summary

Maintainability
C
7 hrs
Test Coverage
<?php

/*
 * This file is part of SeAT
 *
 * Copyright (C) 2015 to 2022 Leon Jacobs
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

// If you are reading this, prepare the eye bleach! This is absolutely
// some of the shittest PHP you will _ever_ read. It is mostly because
// we want to have everyting in a single file, making it easy to run
// using the tokenegenerator command. Still, its terrible, and I know.

session_start();

// Helpers

/**
 * Redirect a request to the start of this script.
 */
function redirect_to_new()
{

    header('Location: ' . $_SERVER['PHP_SELF'] . '?action=new');
    exit;
}

/**
 * @return string
 */
function get_sso_callback_url()
{

    if (! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
        $protocol = 'https://';
    else
        $protocol = 'http://';

    return $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'] . '?action=eveonlinecallback';
}

// UI Parts
/**
 * @return string
 */
function get_header()
{

    return <<<'EOF'
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->

    <title>New ESI Refresh Token</title>

    <!-- Bootstrap core CSS -->
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

    <!-- Custom styles for this template -->
    <style type='text/css'>.header,body{padding-bottom:20px}.header,.jumbotron{border-bottom:1px solid #e5e5e5}body{padding-top:20px}.footer,.header,.marketing{padding-right:15px;padding-left:15px}.header h3{margin-top:0;margin-bottom:0;line-height:40px}.footer{padding-top:19px;color:#777;border-top:1px solid #e5e5e5}@media (min-width:768px){.container{max-width:730px}}.container-narrow>hr{margin:30px 0}.jumbotron{text-align:center}.jumbotron .btn{padding:14px 24px;font-size:21px}.marketing{margin:40px 0}.marketing p+h4{margin-top:28px}@media screen and (min-width:768px){.footer,.header,.marketing{padding-right:0;padding-left:0}.header{margin-bottom:30px}.jumbotron{border-bottom:0}}</style>

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>

  <body>

    <div class="container">
      <div class="header clearfix">
        <h3 class="text-muted">ESI Refresh Token Generator</h3>
      </div>
EOF;

}

/**
 * @return string
 */
function get_footer()
{

    return <<<'EOF'
    </div> <!-- /container -->
  </body>
</html>
EOF;

}

// Page contents

/**
 * Fresh, new login page.
 */
function new_login()
{

    $action = $_SERVER['PHP_SELF'] . '?action=submitsecrets';
    $callback = get_sso_callback_url();

    echo get_header();
    echo <<<EOF
      <div class="jumbotron">
        <p>
          Create a new Application on the
          <a href="https://developers.eveonline.com/applications/create" target="_blank">EVE Online Developers Site</a>.
          Use the resultant <b>Client ID</b> and <b>Secret Key</b> in the form below.
        </p>
        <p>
        The callback url to use in the application form is: <pre>$callback</pre>
        </p>
      </div>

      <div class="row marketing">

        <form action="$action" method="post" class="form-horizontal">
        <fieldset>

        <!-- Text input-->
        <div class="form-group">
          <label class="col-md-4 control-label" for="clientid">Client ID</label>
          <div class="col-md-4">
          <input id="clientid" name="clientid" type="text" placeholder="Client ID" class="form-control input-md">
          <span class="help-block">ClientID From the EVE Online Developers Site</span>
          </div>
        </div>

        <!-- Password input-->
        <div class="form-group">
          <label class="col-md-4 control-label" for="secret">Secret</label>
          <div class="col-md-4">
            <input id="secret" name="secret" type="password" placeholder="Secret" class="form-control input-md">
            <span class="help-block">Secret From the EVE Online Developers Site</span>
          </div>
        </div>
        
        <!-- Select Multiple -->
        <div class="form-group">
          <label class="col-md-4 control-label" for="scopes">Scopes</label>
          <div class="col-md-4">
            <select id="scopes" name="scopes[]" class="form-control" multiple="multiple">

            <!-- in the tools directory, run: -->
            <!-- php get_endpoints_and_scopes.php | grep "|" | cut -d"|" -f 3 | sort | uniq | grep -v public | awk '{ print "<option value=\"" $1 "\">" $1 "</option>"}' -->
            <!-- done :D -->

<option value="esi-alliances.read_contacts.v1">esi-alliances.read_contacts.v1</option>
<option value="esi-assets.read_assets.v1">esi-assets.read_assets.v1</option>
<option value="esi-assets.read_corporation_assets.v1">esi-assets.read_corporation_assets.v1</option>
<option value="esi-bookmarks.read_character_bookmarks.v1">esi-bookmarks.read_character_bookmarks.v1</option>
<option value="esi-bookmarks.read_corporation_bookmarks.v1">esi-bookmarks.read_corporation_bookmarks.v1</option>
<option value="esi-calendar.read_calendar_events.v1">esi-calendar.read_calendar_events.v1</option>
<option value="esi-calendar.respond_calendar_events.v1">esi-calendar.respond_calendar_events.v1</option>
<option value="esi-characters.read_agents_research.v1">esi-characters.read_agents_research.v1</option>
<option value="esi-characters.read_blueprints.v1">esi-characters.read_blueprints.v1</option>
<option value="esi-characters.read_chat_channels.v1">esi-characters.read_chat_channels.v1</option>
<option value="esi-characters.read_contacts.v1">esi-characters.read_contacts.v1</option>
<option value="esi-characters.read_corporation_roles.v1">esi-characters.read_corporation_roles.v1</option>
<option value="esi-characters.read_fatigue.v1">esi-characters.read_fatigue.v1</option>
<option value="esi-characters.read_fw_stats.v1">esi-characters.read_fw_stats.v1</option>
<option value="esi-characters.read_loyalty.v1">esi-characters.read_loyalty.v1</option>
<option value="esi-characters.read_medals.v1">esi-characters.read_medals.v1</option>
<option value="esi-characters.read_notifications.v1">esi-characters.read_notifications.v1</option>
<option value="esi-characters.read_opportunities.v1">esi-characters.read_opportunities.v1</option>
<option value="esi-characters.read_standings.v1">esi-characters.read_standings.v1</option>
<option value="esi-characters.read_titles.v1">esi-characters.read_titles.v1</option>
<option value="esi-characters.write_contacts.v1">esi-characters.write_contacts.v1</option>
<option value="esi-characterstats.read.v1">esi-characterstats.read.v1</option>
<option value="esi-clones.read_clones.v1">esi-clones.read_clones.v1</option>
<option value="esi-clones.read_implants.v1">esi-clones.read_implants.v1</option>
<option value="esi-contracts.read_character_contracts.v1">esi-contracts.read_character_contracts.v1</option>
<option value="esi-contracts.read_corporation_contracts.v1">esi-contracts.read_corporation_contracts.v1</option>
<option value="esi-corporations.read_blueprints.v1">esi-corporations.read_blueprints.v1</option>
<option value="esi-corporations.read_contacts.v1">esi-corporations.read_contacts.v1</option>
<option value="esi-corporations.read_container_logs.v1">esi-corporations.read_container_logs.v1</option>
<option value="esi-corporations.read_corporation_membership.v1">esi-corporations.read_corporation_membership.v1</option>
<option value="esi-corporations.read_divisions.v1">esi-corporations.read_divisions.v1</option>
<option value="esi-corporations.read_facilities.v1">esi-corporations.read_facilities.v1</option>
<option value="esi-corporations.read_fw_stats.v1">esi-corporations.read_fw_stats.v1</option>
<option value="esi-corporations.read_medals.v1">esi-corporations.read_medals.v1</option>
<option value="esi-corporations.read_standings.v1">esi-corporations.read_standings.v1</option>
<option value="esi-corporations.read_starbases.v1">esi-corporations.read_starbases.v1</option>
<option value="esi-corporations.read_structures.v1">esi-corporations.read_structures.v1</option>
<option value="esi-corporations.read_titles.v1">esi-corporations.read_titles.v1</option>
<option value="esi-corporations.track_members.v1">esi-corporations.track_members.v1</option>
<option value="esi-corporations.write_structures.v1">esi-corporations.write_structures.v1</option>
<option value="esi-fittings.read_fittings.v1">esi-fittings.read_fittings.v1</option>
<option value="esi-fittings.write_fittings.v1">esi-fittings.write_fittings.v1</option>
<option value="esi-fleets.read_fleet.v1">esi-fleets.read_fleet.v1</option>
<option value="esi-fleets.write_fleet.v1">esi-fleets.write_fleet.v1</option>
<option value="esi-industry.read_character_jobs.v1">esi-industry.read_character_jobs.v1</option>
<option value="esi-industry.read_character_mining.v1">esi-industry.read_character_mining.v1</option>
<option value="esi-industry.read_corporation_jobs.v1">esi-industry.read_corporation_jobs.v1</option>
<option value="esi-industry.read_corporation_mining.v1">esi-industry.read_corporation_mining.v1</option>
<option value="esi-killmails.read_corporation_killmails.v1">esi-killmails.read_corporation_killmails.v1</option>
<option value="esi-killmails.read_killmails.v1">esi-killmails.read_killmails.v1</option>
<option value="esi-location.read_location.v1">esi-location.read_location.v1</option>
<option value="esi-location.read_online.v1">esi-location.read_online.v1</option>
<option value="esi-location.read_ship_type.v1">esi-location.read_ship_type.v1</option>
<option value="esi-mail.organize_mail.v1">esi-mail.organize_mail.v1</option>
<option value="esi-mail.read_mail.v1">esi-mail.read_mail.v1</option>
<option value="esi-mail.send_mail.v1">esi-mail.send_mail.v1</option>
<option value="esi-markets.read_character_orders.v1">esi-markets.read_character_orders.v1</option>
<option value="esi-markets.read_corporation_orders.v1">esi-markets.read_corporation_orders.v1</option>
<option value="esi-markets.structure_markets.v1">esi-markets.structure_markets.v1</option>
<option value="esi-planets.manage_planets.v1">esi-planets.manage_planets.v1</option>
<option value="esi-planets.read_customs_offices.v1">esi-planets.read_customs_offices.v1</option>
<option value="esi-search.search_structures.v1">esi-search.search_structures.v1</option>
<option value="esi-skills.read_skillqueue.v1">esi-skills.read_skillqueue.v1</option>
<option value="esi-skills.read_skills.v1">esi-skills.read_skills.v1</option>
<option value="esi-ui.open_window.v1">esi-ui.open_window.v1</option>
<option value="esi-ui.write_waypoint.v1">esi-ui.write_waypoint.v1</option>
<option value="esi-universe.read_structures.v1">esi-universe.read_structures.v1</option>
<option value="esi-wallet.read_character_wallet.v1">esi-wallet.read_character_wallet.v1</option>
<option value="esi-wallet.read_corporation_wallets.v1">esi-wallet.read_corporation_wallets.v1</option>

            </select>
          </div>
        </div>

        <!-- Button -->
        <div class="form-group">
          <label class="col-md-4 control-label" for="login"></label>
          <div class="col-md-4">
            <button id="login" name="login" class="btn btn-primary">Generate Login</button>
          </div>
        </div>

        </fieldset>
        </form>

      </div>
EOF;
    echo get_footer();

}

/**
 * @param $url
 */
function print_sso_url($url)
{

    echo get_header();
    echo <<<EOF
      <div class="jumbotron">
        <p>
          Click the button below to login with your EVE Online account.<br>
          <a href="$url">
            <img src="https://images.contentful.com/idjq7aai9ylm/18BxKSXCymyqY4QKo8KwKe/c2bdded6118472dd587c8107f24104d7/EVE_SSO_Login_Buttons_Small_White.png?w=195&h=30" />
          </a>
        </p>
        <p>
          The generated URL is:
          <pre>$url</pre>
        </p>
      </div>
EOF;
    echo get_footer();

}

/**
 * @param $access_token
 * @param $refresh_token
 */
function print_tokens($access_token, $refresh_token)
{

    $start_again_url = $_SERVER['PHP_SELF'] . '?action=new';

    echo get_header();
    echo <<<EOF
      <div class="jumbotron">
        <p>
          Your current access token is: <pre>$access_token</pre><br>
          Valid for ~20 minutes.
        </p>
        <p>
          Your refresh token is: <pre>$refresh_token</pre><br>
          Valid until you delete the app from your account
          <a href="https://community.eveonline.com/support/third-party-applications/">here</a>.
        </p>
        <a class="btn btn-lg btn-success" href="$start_again_url" role="button">Start Again</a>
      </div>
EOF;
    echo get_footer();
}

// Ensure we have an action!
if (! isset($_GET['action']))
    redirect_to_new();

// Worlds most caveman router!

// Decide where to go based on the value of 'action'
switch ($_GET['action']) {

    // Display the form to create a new login.
    case 'new':
        $_SESSION['test'] = 'bob';
        new_login();
        break;

    case 'submitsecrets':
        // Ensure we got some values
        if (! isset($_REQUEST['clientid']) ||
            ! isset($_REQUEST['secret']) ||
            ! isset($_REQUEST['scopes'])
        ) {

            echo 'All fields are mandatory!<br>' . PHP_EOL;
            echo '<a href="' . $_SERVER['PHP_SELF'] . '?action=new">Start again</a>';

            exit;
        }

        $_SESSION['clientid'] = $_REQUEST['clientid'];
        $_SESSION['secret'] = $_REQUEST['secret'];
        $_SESSION['state'] = uniqid();

        // Generate the url with the requested scopes
        $url = 'https://login.eveonline.com/v2/oauth/authorize/?response_type=code&redirect_uri=' .
            urlencode(get_sso_callback_url()) . '&client_id=' .
            $_SESSION['clientid'] . '&scope=' . implode(' ', $_REQUEST['scopes']) . ' &state=' . $_SESSION['state'];

        // Print the HTML with the login button.
        print_sso_url($url);
        break;

    case 'eveonlinecallback':
        // Verify the state.
        if ($_REQUEST['state'] != $_SESSION['state']) {

            echo 'Invalid State! You will have to start again!<br>';
            echo '<a href="' . $_SERVER['PHP_SELF'] . '?action=new">Start again</a>';
            exit;
        }

        // Clear the state value.
        $_SESSION['state'] = null;

        // Prep the authentication header.
        $headers = [
            'Authorization: Basic ' . base64_encode($_SESSION['clientid'] . ':' . $_SESSION['secret']),
            'Content-Type: application/json',
        ];

        // Seems like CCP does not mind JSON in the body. Yay.
        $fields = json_encode([
            'grant_type' => 'authorization_code',
            'code'       => $_REQUEST['code'],
        ]);

        // Start a cURL session
        $ch = curl_init('https://login.eveonline.com/v2/oauth/token');
        curl_setopt_array($ch, [
            CURLOPT_URL             => 'https://login.eveonline.com/v2/oauth/token',
            CURLOPT_POST            => true,
            CURLOPT_POSTFIELDS      => $fields,
            CURLOPT_HTTPHEADER      => $headers,
            CURLOPT_RETURNTRANSFER  => true,
            CURLOPT_USERAGENT       => 'eseye/tokengenerator',
            CURLOPT_SSL_VERIFYPEER  => true,
            CURLOPT_SSL_CIPHER_LIST => 'TLSv1',
        ]);

        $result = curl_exec($ch);

        $data = json_decode($result);

        print_tokens($data->access_token, $data->refresh_token);
        break;

    // If we dont know what 'action' to perform, then redirect.
    default:
        redirect_to_new();
        break;
}