Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for custom internet proxy credentials with env vars #1061

Merged
merged 1 commit into from
Sep 16, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Paket.sln
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "commands", "commands", "{44
docs\content\commands\update.md = docs\content\commands\update.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paket.Bootstrapper.Tests", "tests\Paket.Bootstrapper.Tests\Paket.Bootstrapper.Tests.csproj", "{7C622582-E281-4EAB-AADA-B5893BB89B45}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -95,6 +97,10 @@ Global
{7BAB0AE2-089F-4761-B138-A717AA2F86C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7BAB0AE2-089F-4761-B138-A717AA2F86C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BAB0AE2-089F-4761-B138-A717AA2F86C5}.Release|Any CPU.Build.0 = Release|Any CPU
{7C622582-E281-4EAB-AADA-B5893BB89B45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7C622582-E281-4EAB-AADA-B5893BB89B45}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7C622582-E281-4EAB-AADA-B5893BB89B45}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7C622582-E281-4EAB-AADA-B5893BB89B45}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -104,5 +110,6 @@ Global
{8E6D5255-776D-4B61-85F9-73C37AA1FB9A} = {A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1}
{E789C72A-5CFD-436B-8EF1-61AA2852A89F} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4}
{4425A246-BD18-4622-86B5-0154F19165E4} = {8E6D5255-776D-4B61-85F9-73C37AA1FB9A}
{7C622582-E281-4EAB-AADA-B5893BB89B45} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ source https://nuget.org/api/v2
nuget Newtonsoft.Json
nuget Argu
nuget FSharp.Core
nuget NUnit

github fsharp/FAKE src/app/FakeLib/Globbing/Globbing.fs
github fsprojects/Chessie src/Chessie/ErrorHandling.fs
Expand Down
1 change: 1 addition & 0 deletions paket.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ NUGET
Argu (1.0.0)
FSharp.Core (4.0.0.1)
Newtonsoft.Json (7.0.1)
NUnit (2.6.4)
GITHUB
remote: fsharp/FAKE
specs:
Expand Down
10 changes: 7 additions & 3 deletions src/Paket.Bootstrapper/BootstrapperHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,17 @@ private static void WriteConsole(string message, ConsoleColor consoleColor)

internal static IWebProxy GetDefaultWebProxyFor(String url)
{
IWebProxy result = WebRequest.GetSystemWebProxy();
Uri uri = new Uri(url);
Uri address = result.GetProxy(uri);

IWebProxy result;
if (EnvProxy.TryGetProxyFor(uri, out result) && result.GetProxy(uri) != uri)
return result;

result = WebRequest.GetSystemWebProxy();
Uri address = result.GetProxy(uri);
if (address == uri)
return null;

return new WebProxy(address)
{
Credentials = CredentialCache.DefaultCredentials,
Expand Down
70 changes: 70 additions & 0 deletions src/Paket.Bootstrapper/EnvProxy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Net;

namespace Paket.Bootstrapper
{
internal class EnvProxy
{
private static readonly EnvProxy instance = new EnvProxy();
private readonly Dictionary<string, IWebProxy> proxies = new Dictionary<string, IWebProxy>(StringComparer.OrdinalIgnoreCase);

private static string GetEnvVarValue(string name)
{
// under mono, env vars are case sensitive
return Environment.GetEnvironmentVariable(name.ToUpperInvariant())
?? Environment.GetEnvironmentVariable(name.ToLowerInvariant());
}

private string[] GetBypassList()
{
var noproxy = GetEnvVarValue("NO_PROXY");
if (string.IsNullOrEmpty(noproxy))
return new string[0];
return noproxy.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}

private bool TryGetCredentials(Uri uri, out NetworkCredential credentials)
{
var userPass = uri.UserInfo.Split(new[] { ':' }, 2);
if (userPass.Length == 2 && userPass[0].Length > 0)
{
credentials = new NetworkCredential(Uri.UnescapeDataString(userPass[0]), Uri.UnescapeDataString(userPass[1]));
return true;
}
credentials = null;
return false;
}

private void AddProxy(string scheme, string[] bypassList)
{
var envVarName = string.Format("{0}_PROXY", scheme.ToUpperInvariant());
var envVarValue = GetEnvVarValue(envVarName);
if (envVarValue == null)
return;
Uri envUri;
if (Uri.TryCreate(envVarValue, UriKind.Absolute, out envUri))
{
var proxy = new WebProxy(new Uri(string.Format("{0}://{1}:{2}", scheme, envUri.Host, envUri.Port)));
NetworkCredential credentials;
if (TryGetCredentials(envUri, out credentials))
proxy.Credentials = credentials;
proxy.BypassProxyOnLocal = true;
proxy.BypassList = bypassList;
proxies.Add(scheme, proxy);
}
}

protected EnvProxy()
{
var bypassList = GetBypassList();
AddProxy("http", bypassList);
AddProxy("https", bypassList);
}

public static bool TryGetProxyFor(Uri uri, out IWebProxy proxy)
{
return instance.proxies.TryGetValue(uri.Scheme, out proxy);
}
}
}
1 change: 1 addition & 0 deletions src/Paket.Bootstrapper/Paket.Bootstrapper.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<ItemGroup>
<Compile Include="BootstrapperHelper.cs" />
<Compile Include="DownloadArguments.cs" />
<Compile Include="EnvProxy.cs" />
<Compile Include="GitHubDownloadStrategy.cs" />
<Compile Include="IDownloadStrategy.cs" />
<Compile Include="NugetDownloadStrategy.cs" />
Expand Down
3 changes: 3 additions & 0 deletions src/Paket.Bootstrapper/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Paket.Bootstrapper.Tests")]

namespace Paket.Bootstrapper
{
Expand Down
53 changes: 45 additions & 8 deletions src/Paket.Core/Utils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,53 @@ let inline normalizeXml(doc:XmlDocument) =
xmlTextWriter.Flush()
stringWriter.GetStringBuilder().ToString()

let envProxies () =
let getEnvValue (name:string) =
let v = Environment.GetEnvironmentVariable(name.ToUpperInvariant())
// under mono, env vars are case sensitive
if v = null then Environment.GetEnvironmentVariable(name.ToLowerInvariant()) else v
let bypassList =
let noproxy = getEnvValue "NO_PROXY"
if String.IsNullOrEmpty(noproxy) then [||] else
noproxy.Split([| ',' |], StringSplitOptions.RemoveEmptyEntries)
let getCredentials (uri:Uri) =
let userPass = uri.UserInfo.Split([| ':' |], 2)
if userPass.Length <> 2 || userPass.[0].Length = 0 then None else
let credentials = new NetworkCredential(Uri.UnescapeDataString userPass.[0], Uri.UnescapeDataString userPass.[1])
Some credentials
let getProxy (scheme:string) =
let envVarName = sprintf "%s_PROXY" (scheme.ToUpperInvariant())
let envVarValue = getEnvValue envVarName
if envVarValue = null then None else
match Uri.TryCreate(envVarValue, UriKind.Absolute) with
| true, envUri ->
let proxy = new WebProxy(new Uri(sprintf "%s://%s:%d" scheme envUri.Host envUri.Port))
proxy.Credentials <- Option.toObj <| getCredentials envUri
proxy.BypassProxyOnLocal <- true
proxy.BypassList <- bypassList
Some proxy
| _ -> None
let addProxy (map:Map<string, WebProxy>) scheme =
match getProxy scheme with
| Some p -> Map.add scheme p map
| _ -> map
List.fold addProxy Map.empty [ "http"; "https" ]

let getDefaultProxyFor url =
let result = WebRequest.GetSystemWebProxy()
let uri = new Uri(url)
let address = result.GetProxy(uri)

if address = uri then null else
let proxy = new WebProxy(address)
proxy.Credentials <- CredentialCache.DefaultCredentials
proxy.BypassProxyOnLocal <- true
proxy
let getDefault () =
let result = WebRequest.GetSystemWebProxy()
let address = result.GetProxy(uri)

if address = uri then null else
let proxy = new WebProxy(address)
proxy.Credentials <- CredentialCache.DefaultCredentials
proxy.BypassProxyOnLocal <- true
proxy
match envProxies().TryFind uri.Scheme with
| Some p ->
if p.GetProxy(uri) <> uri then p else getDefault()
| None -> getDefault()

let inline createWebClient(url,auth:Auth option) =
let client = new WebClient()
Expand Down
146 changes: 146 additions & 0 deletions tests/Paket.Bootstrapper.Tests/EnvWebProxyShould.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
using NUnit.Framework;
using System;
using System.Net;
using System.Reflection;

namespace Paket.Bootstrapper.Tests
{
[TestFixture]
class EnvWebProxyShould
{
private sealed class FakeEnvProxy : EnvProxy
{
public FakeEnvProxy()
{
var instanceField = typeof(EnvProxy).GetField("instance", BindingFlags.Static | BindingFlags.NonPublic);
instanceField.SetValue(null, this);
}
new public bool TryGetProxyFor(Uri uri, out IWebProxy proxy)
{
return EnvProxy.TryGetProxyFor(uri, out proxy);
}
}
private sealed class DisposableEnvVar : IDisposable
{
private readonly string name;
private readonly string oldValue;
public DisposableEnvVar(string name, string value = null)
{
this.name = name;
oldValue = Environment.GetEnvironmentVariable(name);
Environment.SetEnvironmentVariable(name, value);
}
public void Dispose()
{
Environment.SetEnvironmentVariable(name, oldValue);
}
}

[Test] public void GetNoProxyIfNoneDefined()
{
using (new DisposableEnvVar("http_proxy"))
using (new DisposableEnvVar("https_proxy"))
{
var envProxy = new FakeEnvProxy();
IWebProxy proxy;
Assert.IsFalse(envProxy.TryGetProxyFor(new Uri("http://github.com"), out proxy));
Assert.IsFalse(envProxy.TryGetProxyFor(new Uri("https://github.com"), out proxy));
}
}

[Test] public void GetHttpProxyWithNoPortNoCredentials()
{
using (new DisposableEnvVar("http_proxy", "http://proxy.local"))
using (new DisposableEnvVar("no_proxy"))
{
var envProxy = new FakeEnvProxy();
IWebProxy proxy;
Assert.IsTrue(envProxy.TryGetProxyFor(new Uri("http://github.com"), out proxy));
var webProxy = proxy as WebProxy;
Assert.IsNotNull(webProxy);
Assert.That(webProxy.Address, Is.EqualTo(new Uri("http://proxy.local")));
Assert.IsTrue(webProxy.BypassProxyOnLocal);
Assert.That(webProxy.BypassList.Length, Is.EqualTo(0));
Assert.IsNull(webProxy.Credentials);
}
}

[Test] public void GetHttpProxyWithPortNoCredentials()
{
using (new DisposableEnvVar("http_proxy", "http://proxy.local:8080"))
using (new DisposableEnvVar("no_proxy"))
{
var envProxy = new FakeEnvProxy();
IWebProxy proxy;
Assert.IsTrue(envProxy.TryGetProxyFor(new Uri("http://github.com"), out proxy));
var webProxy = proxy as WebProxy;
Assert.IsNotNull(webProxy);
Assert.That(webProxy.Address, Is.EqualTo(new Uri("http://proxy.local:8080")));
Assert.IsTrue(webProxy.BypassProxyOnLocal);
Assert.That(webProxy.BypassList.Length, Is.EqualTo(0));
Assert.IsNull(webProxy.Credentials);
}
}

[Test] public void GetHttpProxyWithPortAndCredentials()
{
const string password = "p@ssw0rd:";
using (new DisposableEnvVar("http_proxy", string.Format("http://user:{0}@proxy.local:8080", Uri.EscapeDataString(password))))
using (new DisposableEnvVar("no_proxy"))
{
var envProxy = new FakeEnvProxy();
IWebProxy proxy;
Assert.IsTrue(envProxy.TryGetProxyFor(new Uri("http://github.com"), out proxy));
var webProxy = proxy as WebProxy;
Assert.IsNotNull(webProxy);
Assert.That(webProxy.Address, Is.EqualTo(new Uri("http://proxy.local:8080")));
Assert.IsTrue(webProxy.BypassProxyOnLocal);
Assert.That(webProxy.BypassList.Length, Is.EqualTo(0));
var credentials = webProxy.Credentials as NetworkCredential;
Assert.IsNotNull(credentials);
Assert.That(credentials.UserName, Is.EqualTo("user"));
Assert.That(credentials.Password, Is.EqualTo(password));
}
}

[Test] public void GetHttpsProxyWithPortAndCredentials()
{
const string password = "p@ssw0rd:";
using (new DisposableEnvVar("https_proxy", string.Format("https://user:{0}@proxy.local:8080", Uri.EscapeDataString(password))))
using (new DisposableEnvVar("no_proxy"))
{
var envProxy = new FakeEnvProxy();
IWebProxy proxy;
Assert.IsTrue(envProxy.TryGetProxyFor(new Uri("https://github.com"), out proxy));
var webProxy = proxy as WebProxy;
Assert.IsNotNull(webProxy);
Assert.That(webProxy.Address, Is.EqualTo(new Uri("https://proxy.local:8080")));
Assert.IsTrue(webProxy.BypassProxyOnLocal);
Assert.That(webProxy.BypassList.Length, Is.EqualTo(0));
var credentials = webProxy.Credentials as NetworkCredential;
Assert.IsNotNull(credentials);
Assert.That(credentials.UserName, Is.EqualTo("user"));
Assert.That(credentials.Password, Is.EqualTo(password));
}
}

[Test] public void GetHttpProxyWithBypassList()
{
using (new DisposableEnvVar("http_proxy", string.Format("http://proxy.local:8080")))
using (new DisposableEnvVar("no_proxy", ".local,127.0.0.1"))
{
var envProxy = new FakeEnvProxy();
IWebProxy proxy;
Assert.IsTrue(envProxy.TryGetProxyFor(new Uri("http://github.com"), out proxy));
var webProxy = proxy as WebProxy;
Assert.IsNotNull(webProxy);
Assert.That(webProxy.Address, Is.EqualTo(new Uri("http://proxy.local:8080")));
Assert.IsTrue(webProxy.BypassProxyOnLocal);
Assert.That(webProxy.BypassList.Length, Is.EqualTo(2));
Assert.That(webProxy.BypassList[0], Is.EqualTo(".local"));
Assert.That(webProxy.BypassList[1], Is.EqualTo("127.0.0.1"));
Assert.IsNull(webProxy.Credentials);
}
}
}
}
Loading