~fkooman/vpn-user-portal

8311affa961bbe7ce4a5da6bfd9188046051149d — François Kooman 3 months ago 7f0af2c
add support for tcp_only post parameter, fix error responses
M src/ClientConfig.php => src/ClientConfig.php +19 -8
@@ 10,6 10,7 @@
namespace LC\Portal;

use LC\Common\ProfileConfig;
use LC\Portal\Exception\ClientConfigException;

class ClientConfig
{


@@ 18,11 19,12 @@ class ClientConfig
    const STRATEGY_ALL = 2;

    /**
     * @param int $remoteStrategy
     * @param int  $remoteStrategy
     * @param bool $tcpOnly
     *
     * @return string
     */
    public static function get(ProfileConfig $profileConfig, array $serverInfo, array $clientCertificate, $remoteStrategy)
    public static function get(ProfileConfig $profileConfig, array $serverInfo, array $clientCertificate, $remoteStrategy, $tcpOnly)
    {
        // make a list of ports/proto to add to the configuration file
        $hostName = $profileConfig->hostName();


@@ 32,7 34,13 @@ class ClientConfig
            $vpnProtoPorts = $profileConfig->exposedVpnProtoPorts();
        }

        $remoteProtoPortList = self::remotePortProtoList($vpnProtoPorts, $remoteStrategy);
        $remoteProtoPortList = self::remotePortProtoList($vpnProtoPorts, $remoteStrategy, $tcpOnly);
        // make sure we have remotes
        // we assume that *without* tcpOnly we always have remotes,
        // otherwise it is a server configuration bug
        if ($tcpOnly && 0 === \count($remoteProtoPortList)) {
            throw new ClientConfigException('no TCP remotes available');
        }

        $clientConfig = [
            '# OpenVPN Client Configuration',


@@ 112,18 120,19 @@ class ClientConfig
            $clientConfig[] = sprintf('remote %s %d %s', $hostName, (int) substr($remoteProtoPort, 4), substr($remoteProtoPort, 0, 3));
        }

        return implode(PHP_EOL, $clientConfig);
        return implode(\PHP_EOL, $clientConfig);
    }

    /**
     * Pick a "normal" UDP and TCP port. Pick a "special" UDP and TCP
     * port.
     *
     * @param int $remoteStrategy
     * @param int  $remoteStrategy
     * @param bool $tcpOnly
     *
     * @return array<string>
     */
    public static function remotePortProtoList(array $vpnProtoPorts, $remoteStrategy)
    public static function remotePortProtoList(array $vpnProtoPorts, $remoteStrategy, $tcpOnly)
    {
        $specialUdpPorts = [];
        $specialTcpPorts = [];


@@ 151,9 160,11 @@ class ClientConfig
        }

        $clientPortList = [];
        self::getItem($clientPortList, $normalUdpPorts, $remoteStrategy);
        if (!$tcpOnly) {
            self::getItem($clientPortList, $normalUdpPorts, $remoteStrategy);
            self::getItem($clientPortList, $specialUdpPorts, $remoteStrategy);
        }
        self::getItem($clientPortList, $normalTcpPorts, $remoteStrategy);
        self::getItem($clientPortList, $specialUdpPorts, $remoteStrategy);
        self::getItem($clientPortList, $specialTcpPorts, $remoteStrategy);

        return $clientPortList;

A src/Exception/ClientConfigException.php => src/Exception/ClientConfigException.php +16 -0
@@ 0,0 1,16 @@
<?php

/*
 * eduVPN - End-user friendly VPN.
 *
 * Copyright: 2016-2019, The Commons Conservancy eduVPN Programme
 * SPDX-License-Identifier: AGPL-3.0+
 */

namespace LC\Portal\Exception;

use Exception;

class ClientConfigException extends Exception
{
}

M src/VpnApiModule.php => src/VpnApiModule.php +12 -5
@@ 24,6 24,7 @@ use LC\Common\Http\Service;
use LC\Common\Http\ServiceModuleInterface;
use LC\Common\HttpClient\ServerClient;
use LC\Common\ProfileConfig;
use LC\Portal\Exception\ClientConfigException;
use LC\Portal\OAuth\VpnAccessTokenInfo;

class VpnApiModule implements ServiceModuleInterface


@@ 130,7 131,8 @@ class VpnApiModule implements ServiceModuleInterface
                            return new ApiErrorResponse('profile_config', 'profile not available or no permission');
                        }

                        $vpnConfig = $this->getConfigOnly($requestedProfileId, $remoteStrategy);
                        $tcpOnly = 'on' === $request->optionalPostParameter('tcp_only');
                        $vpnConfig = $this->getConfigOnly($requestedProfileId, $remoteStrategy, $tcpOnly);
                        $clientCertificate = $this->getCertificate($accessTokenInfo);
                        $vpnConfig .= "\n<cert>\n".$clientCertificate['certificate']."\n</cert>\n<key>\n".$clientCertificate['private_key']."\n</key>";
                        $response = new Response(200, 'application/x-openvpn-profile');


@@ 139,7 141,9 @@ class VpnApiModule implements ServiceModuleInterface

                        return $response;
                    } catch (InputValidationException $e) {
                        return new ApiErrorResponse('profile_config', $e->getMessage());
                        return new JsonResponse(['error' => $e->getMessage()], 400);
                    } catch (ClientConfigException $e) {
                        return new JsonResponse(['error' => $e->getMessage()], 406);
                    }
                }
            );


@@ 306,7 310,9 @@ class VpnApiModule implements ServiceModuleInterface
                        return new ApiErrorResponse('profile_config', 'profile not available or no permission');
                    }

                    $vpnConfig = $this->getConfigOnly($requestedProfileId, $remoteStrategy);
                    // APIv2 has no support for "tcpOnly" OpenVPN profiles, so
                    // always false...
                    $vpnConfig = $this->getConfigOnly($requestedProfileId, $remoteStrategy, false);
                    $response = new Response(200, 'application/x-openvpn-profile');
                    $response->setBody(str_replace("\n", "\r\n", $vpnConfig));



@@ 364,10 370,11 @@ class VpnApiModule implements ServiceModuleInterface
    /**
     * @param string $profileId
     * @param int    $remoteStrategy
     * @param bool   $tcpOnly
     *
     * @return string
     */
    private function getConfigOnly($profileId, $remoteStrategy)
    private function getConfigOnly($profileId, $remoteStrategy, $tcpOnly)
    {
        // obtain information about this profile to be able to construct
        // a client configuration file


@@ 377,7 384,7 @@ class VpnApiModule implements ServiceModuleInterface
        // get the CA & tls-auth
        $serverInfo = $this->serverClient->getRequireArray('server_info', ['profile_id' => $profileId]);

        return ClientConfig::get($profileConfig, $serverInfo, [], $remoteStrategy);
        return ClientConfig::get($profileConfig, $serverInfo, [], $remoteStrategy, $tcpOnly);
    }

    /**

M src/VpnPortalModule.php => src/VpnPortalModule.php +2 -1
@@ 378,7 378,8 @@ class VpnPortalModule implements ServiceModuleInterface
        // get the CA & tls-auth
        $serverInfo = $this->serverClient->getRequireArray('server_info', ['profile_id' => $profileId]);

        $clientConfig = ClientConfig::get($profileConfig, $serverInfo, $clientCertificate, ClientConfig::STRATEGY_RANDOM);
        // last parameter is "tcpOnly". From the portal we always return both UDP & TCP remotes
        $clientConfig = ClientConfig::get($profileConfig, $serverInfo, $clientCertificate, ClientConfig::STRATEGY_RANDOM, false);

        // convert the OpenVPN file to "Windows" format, no platform cares, but
        // in Notepad on Windows it looks not so great everything on one line