├── icon.png
├── NtlmProxy.Tests
├── packages.config
├── NtlmProxy.Tests.csproj
└── NtlmProxyTests.cs
├── .gitignore
├── PACKAGING.md
├── LICENSE
├── NtlmProxy
├── MikeRogers.NtlmProxy.nuspec
├── Properties
│ └── AssemblyInfo.cs
├── SimpleHttpServerOptions.cs
├── NtlmProxy.csproj
├── SimpleHttpServer.cs
└── NtlmProxy.cs
├── NtlmProxy.sln
└── README.md
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mike-rogers/NtlmProxy/HEAD/icon.png
--------------------------------------------------------------------------------
/NtlmProxy.Tests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.sln.docstates
8 |
9 | # Build results
10 | [Dd]ebug/
11 | [Rr]elease/
12 | x64/
13 | build/
14 | [Bb]in/
15 | [Oo]bj/
16 |
17 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
18 | !packages/*/build/
19 | NuGet.exe
20 |
21 | *_i.c
22 | *_p.c
23 | *.ilk
24 | *.meta
25 | *.obj
26 | *.pch
27 | *.pdb
28 | *.pgc
29 | *.pgd
30 | *.rsp
31 | *.sbr
32 | *.tlb
33 | *.tli
34 | *.tlh
35 | *.tmp
36 | *.tmp_proj
37 | *.log
38 | #*.vspscc
39 | #*.vssscc
40 | .builds
41 | *.pidb
42 | *.log
43 | *.scc
44 |
45 | # ReSharper is a .NET coding add-in
46 | _ReSharper*/
47 | *.[Rr]e[Ss]harper
48 | *.DotSettings
49 |
50 | # GhostDoc
51 | *.GhostDoc.xml
52 |
53 | # NuGet Packages Directory
54 | packages/
55 |
56 | # YES, I USE EMACS
57 | *~
58 |
59 | ## Windows detritus
60 |
61 | # Windows image file caches
62 | Thumbs.db
63 | ehthumbs.db
64 |
65 | # Folder config file
66 | Desktop.ini
67 |
68 | # Recycle Bin used on file shares
69 | $RECYCLE.BIN/
70 |
71 | ## Mac detritus
72 | .DS_Store
73 |
--------------------------------------------------------------------------------
/PACKAGING.md:
--------------------------------------------------------------------------------
1 | # How to package (because I can never remember)
2 |
3 | ## Update files
4 |
5 | Make sure to update the assembly version, the release notes and version number in `NtlmProxy/MikeRogers.NtlmProxy.nuspec`, and the version number in the README file's example NuGet XML.
6 |
7 | Update the build version number in Appveyor.
8 |
9 | ## Do the NuGet dance
10 |
11 | See [here](http://docs.nuget.org/docs/creating-packages/creating-and-publishing-a-package) for details.
12 |
13 | You'll probably want to install the Chocolatey `NuGet.CommandLine` package.
14 |
15 | ## The command that pays
16 |
17 | ... after compiling in Release mode, is this:
18 |
19 | (cd NtlmProxy && NuGet Pack NtlmProxy.csproj -Prop Configuration=Release)
20 |
21 | ## Package it up
22 |
23 | Make a ZIP file with:
24 |
25 | * `LICENSE`
26 | * `README.md`
27 | * `MikeRogers.NtlmProxy.dll`
28 |
29 | Name it `MikeRogers.NtlmProxy-${version}.zip`.
30 |
31 | ## Version it
32 |
33 | Create a tag with the version number and push it to GitHub.
34 |
35 | ## Release it
36 |
37 | Upload the ZIP file and the NuGet file to GitHub.
38 |
39 | ```bat
40 | > nuget setApiKey ${myKey}
41 |
42 | > nuget push ${packageName}
43 | ```
44 |
45 | ## Party hat
46 |
47 | Put on a party hat.
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, R. Michael Rogers
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | Redistributions in binary form must reproduce the above copyright notice, this
11 | list of conditions and the following disclaimer in the documentation and/or
12 | other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/NtlmProxy/MikeRogers.NtlmProxy.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | MikeRogers.NtlmProxy
5 | 1.2.1.0
6 | NtlmProxy Library
7 | Mike Rogers
8 | Mike Rogers
9 | https://raw.githubusercontent.com/mike-rogers/NtlmProxy/master/LICENSE
10 | https://github.com/mike-rogers/NtlmProxy
11 | https://raw.githubusercontent.com/mike-rogers/NtlmProxy/master/icon.png
12 | false
13 | Provides a proxy that adds NTLM data for the current user, for use on systems requiring NTLM
14 |
15 | 1.2.1 - Addressed bug (issue #3), allowing dynamic configuration of proxy port
16 | number
17 | 1.2 --- Added support for proxying options, including
18 | * Optional proxying of HTTP headers
19 | * Support for specific Angular.JS Content-Type
20 | 1.1 --- Added support for POST, PUT, DELETE verbs
21 | 1.0 --- Provides a proxy that adds NTLM data for the current user into a web request.
22 |
23 | Copyright 2015
24 | Selenium PhantomJS headless proxy ntlm
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/NtlmProxy.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.21005.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NtlmProxy", "NtlmProxy\NtlmProxy.csproj", "{79DF1E9F-06F9-4392-AB0D-3CAC3191EB5F}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NtlmProxy.Tests", "NtlmProxy.Tests\NtlmProxy.Tests.csproj", "{8D8540E1-C7B6-437A-AB55-95211669A2CC}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {79DF1E9F-06F9-4392-AB0D-3CAC3191EB5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {79DF1E9F-06F9-4392-AB0D-3CAC3191EB5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {79DF1E9F-06F9-4392-AB0D-3CAC3191EB5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {79DF1E9F-06F9-4392-AB0D-3CAC3191EB5F}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {8D8540E1-C7B6-437A-AB55-95211669A2CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {8D8540E1-C7B6-437A-AB55-95211669A2CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {8D8540E1-C7B6-437A-AB55-95211669A2CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {8D8540E1-C7B6-437A-AB55-95211669A2CC}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/NtlmProxy/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("NtlmProxy Library")]
8 | [assembly: AssemblyDescription("Provides a proxy that adds NTLM data for the current user, for use on systems requiring NTLM")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("Mike Rogers")]
11 | [assembly: AssemblyProduct("NtlmProxyLibrary")]
12 | [assembly: AssemblyCopyright("Copyright ©2015, All Rights Reserved")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Setting ComVisible to false makes the types in this assembly not visible
17 | // to COM components. If you need to access a type in this assembly from
18 | // COM, set the ComVisible attribute to true on that type.
19 | [assembly: ComVisible(false)]
20 |
21 | // The following GUID is for the ID of the typelib if this project is exposed to COM
22 | [assembly: Guid("970844e3-a3ea-4527-9fc4-2eef2e87475c")]
23 |
24 | // Version information for an assembly consists of the following four values:
25 | //
26 | // Major Version
27 | // Minor Version
28 | // Build Number
29 | // Revision
30 | //
31 | // You can specify all the values or you can default the Build and Revision Numbers
32 | // by using the '*' as shown below:
33 | // [assembly: AssemblyVersion("1.0.*")]
34 | [assembly: AssemblyVersion("1.2.1.0")]
35 | [assembly: AssemblyFileVersion("1.2.1.0")]
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NTLM Proxy
2 |
3 | Current status: [](https://ci.appveyor.com/project/mike-rogers/ntlmproxy)
4 |
5 | So my team is using [Selenium](http://docs.seleniumhq.org/) [WebDriver](http://docs.seleniumhq.org/projects/webdriver/) to test our web application, and it takes balls forever to run on our test browser. Our test browser is [Firefox](http://www.mozilla.org/en-US/firefox/fx/), which, with a name like that, you'd think would be... I dunno, zippy.
6 |
7 | Anyway.
8 |
9 | I wanted to use [PhantomJS](http://phantomjs.org/) to run headlessly, which would increase the speed of the test runs. Sounds good, right? Too bad we're using Windows Authentication. PhantomJS (currently, 20140205) doesn't like NTLM, and neither do I. The only way to pass Windows credentials is to use some sort of proxy.
10 |
11 | ## Some sort of proxy
12 |
13 | That's why I wrote this little library.
14 |
15 | With a simple `using` directive it will create a tiny web proxy and naively forward any requests sent to it on to a target URL, with NTLM headers for the current user.
16 |
17 | It's pretty simple to use:
18 |
19 | ```csharp
20 | using (var proxy = new NtlmProxy(new Uri("http://localhost:8081/")))
21 | {
22 | // Make your requests here to http://localhost:3999/{0}, proxy.Port
23 | }
24 | ```
25 |
26 | You can pass in your own custom options:
27 |
28 | ```csharp
29 | var options = new SimpleHttpServerOptions
30 | {
31 | Port = 3999,
32 | AuthenticationScheme = AuthenticationSchemes.None
33 | };
34 |
35 | using (var proxy = new NtlmProxy(new Uri("http://localhost:8081/"), options))
36 | {
37 | // Make your requests here to http://localhost:3999/whatever
38 | }
39 | ```
40 |
41 | To see a comprehensive list of options, see the comments for [SimpleHttpServerOptions.cs](https://github.com/mike-rogers/NtlmProxy/blob/master/NtlmProxy/SimpleHttpServerOptions.cs).
42 |
43 | ## How do I install it?
44 |
45 | You can download the zipped DLL in the Releases section above, or you can use NuGet:
46 |
47 | ```xml
48 |
49 | ```
50 |
51 | ## Why do you keep saying 'naively'?
52 |
53 | I'm no NTLM expert, but I think after the first authorization challenge the client should be holding onto the credentials and including the appropriate NTLM headers. I'm pretty sure this proxy server won't do that.
54 |
55 | ## It's still pretty cool
56 |
57 | I know, right?
58 |
59 | ## License?
60 |
61 | As always, we rock the BSD.
62 |
--------------------------------------------------------------------------------
/NtlmProxy/SimpleHttpServerOptions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Net;
3 |
4 | namespace MikeRogers.NtlmProxy
5 | {
6 | ///
7 | /// Class SimpleHttpServerOptions. This class cannot be inherited.
8 | ///
9 | public sealed class SimpleHttpServerOptions
10 | {
11 | ///
12 | /// Gets or sets a value indicating whether this instance has an Angular content-type header.
13 | ///
14 | /// true if this instance has angular content type; otherwise, false.
15 | public bool HasAngularContentType { get; set; }
16 |
17 | ///
18 | /// Gets or sets the request headers.
19 | ///
20 | /// The request headers.
21 | public Dictionary RequestHeaders { get; set; }
22 |
23 | ///
24 | /// Gets the port.
25 | ///
26 | /// The port.
27 | public int Port { get; set; }
28 |
29 | ///
30 | /// Gets or sets the authentication scheme.
31 | ///
32 | /// The authentication scheme.
33 | public AuthenticationSchemes AuthenticationScheme { get; set; }
34 |
35 | ///
36 | /// Should the proxy duplicate individual request headers?
37 | ///
38 | public bool AreHeadersDuplicated { get; set; }
39 |
40 | ///
41 | /// List of headers to not repeat when AreHeadersDuplicated is true
42 | ///
43 | public List ExcludedHeaders { get; private set; }
44 |
45 | ///
46 | /// Initializes a new instance of the class.
47 | ///
48 | public SimpleHttpServerOptions()
49 | {
50 | RequestHeaders = new Dictionary();
51 | AuthenticationScheme = AuthenticationSchemes.Anonymous;
52 | ExcludedHeaders = new List { "Host", "Accept-Encoding" };
53 | }
54 |
55 | ///
56 | /// The default options
57 | ///
58 | public static SimpleHttpServerOptions GetDefaultOptions()
59 | {
60 | return new SimpleHttpServerOptions
61 | {
62 | Port = 0,
63 | AuthenticationScheme = AuthenticationSchemes.Anonymous,
64 | HasAngularContentType = false,
65 | AreHeadersDuplicated = false,
66 | RequestHeaders = new Dictionary()
67 | };
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/NtlmProxy/NtlmProxy.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {79DF1E9F-06F9-4392-AB0D-3CAC3191EB5F}
8 | Library
9 | Properties
10 | MikeRogers.NtlmProxy
11 | MikeRogers.NtlmProxy
12 | v4.5
13 | 512
14 |
15 |
16 | true
17 | full
18 | false
19 | bin\Debug\
20 | DEBUG;TRACE
21 | prompt
22 | 4
23 |
24 |
25 | pdbonly
26 | true
27 | bin\Release\
28 | TRACE
29 | prompt
30 | 4
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
60 |
--------------------------------------------------------------------------------
/NtlmProxy.Tests/NtlmProxy.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {8D8540E1-C7B6-437A-AB55-95211669A2CC}
8 | Library
9 | Properties
10 | MikeRogers.NtlmProxy.Tests
11 | MikeRogers.NtlmProxy.Tests
12 | v4.5
13 | 512
14 |
15 |
16 | AnyCPU
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | AnyCPU
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 |
36 |
37 |
38 |
39 | ..\packages\Moq.4.2.1402.2112\lib\net40\Moq.dll
40 |
41 |
42 | ..\packages\NUnit.2.6.3\lib\nunit.framework.dll
43 |
44 |
45 | ..\packages\RestSharp.104.4.0\lib\net4\RestSharp.dll
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | {79df1e9f-06f9-4392-ab0d-3cac3191eb5f}
60 | NtlmProxy
61 |
62 |
63 |
64 |
65 |
66 |
67 |
74 |
--------------------------------------------------------------------------------
/NtlmProxy/SimpleHttpServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Net.Http;
6 | using System.Net.Sockets;
7 | using System.Threading.Tasks;
8 |
9 | namespace MikeRogers.NtlmProxy
10 | {
11 | ///
12 | /// A simple HTTP server that can be set to a random unused port
13 | /// and expect a given type of authentication.
14 | ///
15 | public sealed class SimpleHttpServer : IDisposable
16 | {
17 | #region Fields
18 |
19 | ///
20 | /// The instance for creating a small proxy server
21 | ///
22 | private readonly HttpListener _listener;
23 |
24 | ///
25 | /// The function that handles incoming proxied requests
26 | ///
27 | private readonly Func> _requestHandler;
28 |
29 | #endregion
30 |
31 |
32 | #region Properties
33 |
34 | ///
35 | /// Gets or sets the port.
36 | ///
37 | /// The port.
38 | public int Port { get; private set; }
39 |
40 | #endregion Properties
41 |
42 |
43 | #region Constructors
44 |
45 | ///
46 | /// Initializes a new instance of the class.
47 | ///
48 | /// The function that handles incoming proxied requests.
49 | /// Configuration options for the server.
50 | public SimpleHttpServer(
51 | Func> handler,
52 | SimpleHttpServerOptions options = null)
53 | {
54 | options = options ?? SimpleHttpServerOptions.GetDefaultOptions();
55 |
56 | if (options.RequestHeaders == null)
57 | {
58 | throw new ArgumentNullException("options");
59 | }
60 |
61 | Port = options.Port == 0 ? GetEmptyPort() : options.Port;
62 | _requestHandler = handler;
63 |
64 | _listener = new HttpListener
65 | {
66 | AuthenticationSchemes = options.AuthenticationScheme,
67 | IgnoreWriteExceptions = true
68 | };
69 |
70 | _listener.Prefixes.Add(string.Format("http://localhost:{0}/", Port.ToString(CultureInfo.InvariantCulture)));
71 |
72 | _listener.Start();
73 | StartListenLoop();
74 | }
75 |
76 | #endregion
77 |
78 |
79 | #region Public Methods
80 |
81 | ///
82 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
83 | ///
84 | public void Dispose()
85 | {
86 | _listener.Stop();
87 | }
88 |
89 | #endregion
90 |
91 |
92 | #region Private Methods
93 |
94 | ///
95 | /// Gets the empty port.
96 | ///
97 | /// An unused port number
98 | private static int GetEmptyPort()
99 | {
100 | // from http://stackoverflow.com/a/3978040/996184
101 | var listener = new TcpListener(IPAddress.Any, 0);
102 | listener.Start();
103 | var port = ((IPEndPoint)listener.LocalEndpoint).Port;
104 | listener.Stop();
105 | return port;
106 | }
107 |
108 | ///
109 | /// Starts the listen loop.
110 | ///
111 | private async void StartListenLoop()
112 | {
113 | while (true)
114 | {
115 | var context = await _listener.GetContextAsync();
116 | var response = await _requestHandler(context);
117 |
118 | using (var stream = context.Response.OutputStream)
119 | {
120 | var bytes = await response.Content.ReadAsByteArrayAsync();
121 |
122 | // Copy response attributes back to the proxy
123 | context.Response.ContentLength64 = bytes.Length;
124 | context.Response.StatusCode = (int)response.StatusCode;
125 | context.Response.StatusDescription = response.StatusCode.ToString();
126 | if (response.Content.Headers.ContentType != null)
127 | {
128 | context.Response.ContentType = response.Content.Headers.ContentType.ToString();
129 | }
130 |
131 | stream.Write(bytes, 0, bytes.Count());
132 | stream.Close();
133 | }
134 | }
135 | }
136 |
137 | #endregion
138 |
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/NtlmProxy/NtlmProxy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Net.Http;
6 | using System.Text.RegularExpressions;
7 | using System.Threading.Tasks;
8 |
9 | namespace MikeRogers.NtlmProxy
10 | {
11 | ///
12 | /// Class NtlmProxy. This class cannot be inherited.
13 | ///
14 | public sealed class NtlmProxy : IDisposable
15 | {
16 | #region Fields
17 |
18 | ///
19 | /// The hostname to which incoming requests will be proxied
20 | ///
21 | private readonly Uri _hostname;
22 |
23 | ///
24 | /// The simple HTTP server that accepts proxied requests
25 | ///
26 | private readonly SimpleHttpServer _server;
27 |
28 | ///
29 | /// The options for the server
30 | ///
31 | private readonly SimpleHttpServerOptions _options;
32 |
33 | #endregion
34 |
35 |
36 | #region Properties
37 |
38 | ///
39 | /// Gets the proxy server port.
40 | ///
41 | public int Port
42 | {
43 | get { return _server.Port; }
44 | }
45 |
46 | #endregion
47 |
48 |
49 | #region Constructors
50 |
51 | ///
52 | /// Initializes a new instance of the class.
53 | ///
54 | /// The proxied hostname.
55 | /// Configuration options for the server.
56 | public NtlmProxy(Uri proxiedHostname, SimpleHttpServerOptions serverOptions = null)
57 | {
58 | _options = serverOptions ?? SimpleHttpServerOptions.GetDefaultOptions();
59 | _server = new SimpleHttpServer(ProcessRequest, serverOptions);
60 | _hostname = proxiedHostname;
61 | }
62 |
63 | #endregion
64 |
65 |
66 | #region Public Methods
67 |
68 | ///
69 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
70 | ///
71 | public void Dispose()
72 | {
73 | _server.Dispose();
74 | }
75 |
76 | #endregion
77 |
78 |
79 | #region Private Methods
80 |
81 | ///
82 | /// Processes the request.
83 | ///
84 | /// The context.
85 | /// A task thread that resolves into an HTTP response.
86 | private async Task ProcessRequest(HttpListenerContext context)
87 | {
88 | var credential = CredentialCache.DefaultNetworkCredentials;
89 | var myCache = new CredentialCache
90 | {
91 | {_hostname, "NTLM", credential}
92 | };
93 |
94 | var handler = new HttpClientHandler
95 | {
96 | AllowAutoRedirect = true,
97 | Credentials = myCache
98 | };
99 |
100 | using (var client = new HttpClient(handler))
101 | {
102 | var httpMethod = new HttpMethod(context.Request.HttpMethod);
103 | var target = new Uri(_hostname, context.Request.Url.PathAndQuery);
104 | var content = new StreamReader(context.Request.InputStream, context.Request.ContentEncoding).ReadToEnd();
105 |
106 | // New implementation suggested by https://github.com/blabla4
107 | var request = new HttpRequestMessage(httpMethod, target);
108 | if (content != String.Empty)
109 | {
110 | var contentType = context.Request.ContentType;
111 |
112 | if (_options.HasAngularContentType && contentType != null)
113 | {
114 | // Thank you to https://github.com/svantreeck
115 | contentType = Regex.Replace(contentType, ";charset=(.)*", string.Empty);
116 | }
117 |
118 | request.Content = new StringContent(content, context.Request.ContentEncoding, contentType);
119 | }
120 |
121 | // Add headers ('thank you' to https://github.com/svantreeck)
122 | _options.RequestHeaders.ToList().ForEach(x => request.Headers.Add(x.Key, x.Value));
123 |
124 | if (_options.AreHeadersDuplicated)
125 | {
126 | foreach (var key in context.Request.Headers.AllKeys)
127 | {
128 | if (!_options.ExcludedHeaders.Contains(key))
129 | {
130 | request.Headers.TryAddWithoutValidation(key, context.Request.Headers[key]);
131 | }
132 | }
133 | }
134 |
135 | return await client.SendAsync(request);
136 | }
137 | }
138 |
139 | #endregion
140 | }
141 | }
--------------------------------------------------------------------------------
/NtlmProxy.Tests/NtlmProxyTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Net;
5 | using System.Net.Http;
6 | using NUnit.Framework;
7 | using RestSharp;
8 | using System.Text;
9 |
10 | namespace MikeRogers.NtlmProxy.Tests
11 | {
12 | ///
13 | /// These are the worst tests I have ever written.
14 | ///
15 | [TestFixture]
16 | public sealed class NtlmProxyTests
17 | {
18 | ///
19 | /// Sample text string to be served by the fake server.
20 | ///
21 | private const string ExpectedResultText = "I enjoy cows.";
22 |
23 | ///
24 | /// The types of HTTP methods supported.
25 | ///
26 | private static readonly object[] HttpMethods =
27 | {
28 | Method.GET,
29 | Method.POST,
30 | Method.DELETE,
31 | Method.PUT
32 | };
33 |
34 | ///
35 | /// Passes if the response from server is returned correctly,
36 | /// and if the credentials make it all the way from the web request through
37 | /// proxy to server.
38 | ///
39 | /// The HTTP method to be tested.
40 | [Test, TestCaseSource("HttpMethods")]
41 | public void ShouldProxyCredentialsOnRequest(Method method)
42 | {
43 | var serverOptions = SimpleHttpServerOptions.GetDefaultOptions();
44 | var proxyOptions = SimpleHttpServerOptions.GetDefaultOptions();
45 | serverOptions.AuthenticationScheme = AuthenticationSchemes.Ntlm;
46 | proxyOptions.AuthenticationScheme = AuthenticationSchemes.Ntlm;
47 |
48 | var serverAssertion = new Action(context =>
49 | {
50 | var currentCredentials = System.Security.Principal.WindowsIdentity.GetCurrent();
51 | if (currentCredentials == null)
52 | {
53 | Assert.Fail("Unable to determine current Windows user credentials");
54 | }
55 | else
56 | {
57 | Assert.That(context.User.Identity.Name, Is.EqualTo(currentCredentials.Name));
58 | }
59 | });
60 |
61 | var clientAssertion = new Action(proxy =>
62 | {
63 | var proxyUrl = string.Format("http://localhost:{0}/", proxy.Port);
64 |
65 | var client = new RestClient(proxyUrl)
66 | {
67 | Authenticator = new NtlmAuthenticator()
68 | };
69 |
70 | var request = new RestRequest("/", method);
71 |
72 | var response = client.Execute(request);
73 |
74 | Assert.That(response.Content, Is.EqualTo(ExpectedResultText));
75 | });
76 |
77 | ExecuteTestInContext(serverOptions, proxyOptions, serverAssertion, clientAssertion);
78 | }
79 |
80 | ///
81 | /// https://github.com/svantreeck reported in https://github.com/mike-rogers/NtlmProxy/pull/2 that
82 | /// Angular.js has a weird behavior with certain ContentType strings. The option under test
83 | /// removes the 'charset' string from the ContentType.
84 | ///
85 | [Test]
86 | public void ShouldRemoveCharsetForAngularOption()
87 | {
88 | var options = SimpleHttpServerOptions.GetDefaultOptions();
89 | options.HasAngularContentType = true;
90 |
91 | var serverAssertion = new Action(context =>
92 | {
93 | var contentType = context.Request.ContentType;
94 | Assert.That(contentType == null || !contentType.Contains("charset="));
95 | });
96 |
97 | var clientAssertion = new Action(proxy =>
98 | {
99 | var proxyUrl = string.Format("http://localhost:{0}/", proxy.Port);
100 |
101 | var client = new RestClient(proxyUrl);
102 |
103 | var request = new RestRequest("/", Method.GET);
104 |
105 | client.Execute(request);
106 | });
107 |
108 | ExecuteTestInContext(options, SimpleHttpServerOptions.GetDefaultOptions(), serverAssertion, clientAssertion);
109 | }
110 |
111 | ///
112 | /// Should be able to inject headers into proxied requests.
113 | ///
114 | [Test]
115 | public void ShouldInjectHeadersIntoProxiedRequest()
116 | {
117 | var serverOptions = SimpleHttpServerOptions.GetDefaultOptions();
118 | var proxyOptions = SimpleHttpServerOptions.GetDefaultOptions();
119 | proxyOptions.RequestHeaders = new Dictionary
120 | {
121 | {"HeaderOne", "Donkey"},
122 | {"HeaderTwo", "Pony"}
123 | };
124 |
125 | var serverAssertion = new Action(context =>
126 | {
127 | Assert.That(context.Request.Headers["HeaderOne"], Is.EqualTo("Donkey"));
128 | Assert.That(context.Request.Headers["HeaderTwo"], Is.EqualTo("Pony"));
129 | });
130 |
131 | var clientAssertion = new Action(proxy =>
132 | {
133 | var proxyUrl = string.Format("http://localhost:{0}/", proxy.Port);
134 |
135 | var client = new RestClient(proxyUrl);
136 |
137 | var request = new RestRequest("/", Method.GET);
138 |
139 | client.Execute(request);
140 | });
141 |
142 | ExecuteTestInContext(serverOptions, proxyOptions, serverAssertion, clientAssertion);
143 | }
144 |
145 | ///
146 | /// The responses from the server should be added to the response object
147 | /// passed back to the executing context.
148 | ///
149 | [Test]
150 | public void ShouldDuplicateHeadersInProxiedRequest()
151 | {
152 | var proxyOptions = new SimpleHttpServerOptions { AreHeadersDuplicated = true };
153 |
154 | var serverAssertion = new Action(context =>
155 | {
156 | Assert.That(context.Request.Headers["HeaderOne"], Is.EqualTo("Donkey"));
157 | Assert.That(context.Request.Headers["HeaderTwo"], Is.EqualTo("Pony"));
158 | });
159 |
160 | var clientAssertion = new Action(proxy =>
161 | {
162 | var proxyUrl = string.Format("http://localhost:{0}/", proxy.Port);
163 |
164 | var client = new RestClient(proxyUrl);
165 |
166 | var request = new RestRequest("/", Method.GET);
167 | request.AddHeader("HeaderOne", "Donkey");
168 | request.AddHeader("HeaderTwo", "Pony");
169 |
170 | client.Execute(request);
171 | });
172 |
173 | ExecuteTestInContext(SimpleHttpServerOptions.GetDefaultOptions(), proxyOptions, serverAssertion, clientAssertion);
174 | }
175 |
176 | ///
177 | /// The responses from the server should be added to the response object
178 | /// passed back to the executing context.
179 | ///
180 | [Test]
181 | public void ShouldIncludeProxiedResponseDataBackToClient()
182 | {
183 | var options = SimpleHttpServerOptions.GetDefaultOptions();
184 |
185 | var clientAssertion = new Action(proxy =>
186 | {
187 | var proxyUrl = string.Format("http://localhost:{0}/", proxy.Port);
188 |
189 | var client = new RestClient(proxyUrl);
190 |
191 | var request = new RestRequest("/", Method.GET);
192 |
193 | var response = client.Execute(request);
194 |
195 | Assert.That(response.ContentLength, Is.EqualTo(13));
196 | Assert.That(response.ContentType, Is.Empty);
197 | Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
198 | Assert.That(response.StatusDescription, Is.EqualTo("OK"));
199 | });
200 |
201 | ExecuteTestInContext(options, SimpleHttpServerOptions.GetDefaultOptions(), null, clientAssertion);
202 | }
203 |
204 | ///
205 | /// Should proxy simple GET request per read me instructions.
206 | ///
207 | [Test]
208 | public void ShouldProxySimpleGetRequestOnCustomPort()
209 | {
210 | var proxyOptions = new SimpleHttpServerOptions
211 | {
212 | Port = 3999
213 | };
214 |
215 | var serverOptions = SimpleHttpServerOptions.GetDefaultOptions();
216 |
217 | var clientAssertion = new Action(proxy =>
218 | {
219 | var proxyUrl = string.Format("http://localhost:{0}/", proxy.Port);
220 |
221 | var client = new RestClient(proxyUrl);
222 |
223 | var request = new RestRequest("/", Method.GET);
224 |
225 | var response = client.Execute(request);
226 |
227 | Assert.That(response.ContentLength, Is.EqualTo(13));
228 | Assert.That(response.ContentType, Is.Empty);
229 | Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
230 | Assert.That(response.StatusDescription, Is.EqualTo("OK"));
231 | });
232 |
233 | ExecuteTestInContext(serverOptions, proxyOptions, null, clientAssertion);
234 | }
235 |
236 | ///
237 | /// This method creates a stub server (server).
238 | /// It will then create the proxy (proxy).
239 | /// Finally it issues a web request to the proxy.
240 | ///
241 | /// The options for the running HTTP server.
242 | /// The options for the running proxy server.
243 | /// The configuration/assertion code to run in the context of the server.
244 | /// The configuration/assertion code to run in the context of the client.
245 | private static void ExecuteTestInContext(
246 | SimpleHttpServerOptions serverOptions,
247 | SimpleHttpServerOptions proxyOptions,
248 | Action serverAssertion,
249 | Action clientAssertion)
250 | {
251 | // Unfortunately I'm not familiar enough with await/async to correct the following:
252 | #pragma warning disable 1998
253 | using (var server = new SimpleHttpServer(async context =>
254 | #pragma warning restore 1998
255 | {
256 | var response = new HttpResponseMessage
257 | {
258 | Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes(ExpectedResultText)))
259 | };
260 |
261 | if (serverAssertion != null)
262 | {
263 | serverAssertion(context);
264 | }
265 |
266 | return response;
267 | }, serverOptions))
268 | {
269 | var serverUri = new Uri(string.Format("http://localhost:{0}/", server.Port));
270 | using (var proxy = new NtlmProxy(serverUri, proxyOptions))
271 | {
272 | clientAssertion(proxy);
273 | }
274 | }
275 | }
276 | }
277 | }
278 |
--------------------------------------------------------------------------------