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 ntlm authentication #2658

Merged
merged 1 commit into from
Aug 25, 2017
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
4 changes: 2 additions & 2 deletions docs/content/commands/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
paket config add-credentials <source URL>
```

Paket will then ask you for the username and password that will be used for the
specified `<source URL>`.
Paket will then ask you for the username, password, and authentication type that
will be used for the specified `<source URL>`.

The credentials you enter here will then be used for `source`s in the
[`paket.dependencies` file](nuget-dependencies.html) that match `<source URL>`
Expand Down
8 changes: 6 additions & 2 deletions docs/content/nuget-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ source http://myserver/nuget/api/v2 // Custom feed.
It's also possible to provide login information for private NuGet feeds:

```paket
source http://example.com/nuget/api/v2 username: "user name" password: "the password"
source http://example.com/nuget/api/v2 username: "user name" password: "the password" authtype: "basic"
```

If you don't want to check your username and password into source control, you
can use environment variables instead:

```paket
source http://myserver/nuget/api/v2 username: "%PRIVATE_FEED_USER%" password: "%PRIVATE_FEED_PASS%"
source http://myserver/nuget/api/v2 username: "%PRIVATE_FEED_USER%" password: "%PRIVATE_FEED_PASS%" authtype: "ntlm"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need either username/password OR ntlm, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or do you need to enter your windows account and password? It should be able to use the current windows account.

Copy link
Contributor Author

@samhanes samhanes Aug 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the default credentials aren't used - just a different authentication scheme. See my reply to your other comment. :)

```

`%PRIVATE_FEED_USER%` and `%PRIVATE_FEED_PASS%` will be expanded with the
Expand All @@ -52,6 +52,10 @@ variables.

The [`paket.lock` file](lock-file.html) will also reflect these settings.

`authtype` is an optional parameter to specify the authentication scheme. Allowed
values are `basic` and `ntlm`. If no authentication type is specified, basic
authentication will be used.

**Note:** If [`paket.dependencies` file](dependencies-file.html) exists while
running the [`convert-from-nuget` command](paket-convert-from-nuget.html), the
`PRIVATE_FEED_USER` and `PRIVATE_FEED_PASS` will *not* be expanded. Please see
Expand Down
19 changes: 16 additions & 3 deletions src/Paket.Core/Common/Utils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,17 @@ let internal memoizeAsync f =
fun (x: 'a) -> // task.Result serialization to sync after done.
cache.GetOrAdd(x, fun x -> f(x) |> Async.StartAsTask) |> Async.AwaitTask

type AuthType = | Basic | NTLM

type Auth =
| Credentials of Username : string * Password : string
| Credentials of Username : string * Password : string * Type : AuthType
| Token of string

let internal parseAuthTypeString (str:string) =
match str.Trim().ToLowerInvariant() with
| "ntlm" -> AuthType.NTLM
| _ -> AuthType.Basic

let TimeSpanToReadableString(span:TimeSpan) =
let pluralize x = if x = 1 then String.Empty else "s"
let notZero x y = if x > 0 then y else String.Empty
Expand Down Expand Up @@ -582,7 +589,7 @@ let createHttpClient (url,auth:Auth option) =
let client = new HttpClient(handler)
match auth with
| None -> handler.UseDefaultCredentials <- true
| Some(Credentials(username, password)) ->
| Some(Credentials(username, password, AuthType.Basic)) ->
// htttp://stackoverflow.com/questions/16044313/webclient-httpwebrequest-with-basic-authentication-returns-404-not-found-for-v/26016919#26016919
//this works ONLY if the server returns 401 first
//client DOES NOT send credentials on first request
Expand All @@ -593,6 +600,9 @@ let createHttpClient (url,auth:Auth option) =
let credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(username + ":" + password))
client.DefaultRequestHeaders.Authorization <-
new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentials)
| Some(Credentials(username, password, AuthType.NTLM)) ->
let cred = System.Net.NetworkCredential(username,password)
handler.Credentials <- cred.GetCredential(new Uri(url), "NTLM")
| Some(Token token) ->
client.DefaultRequestHeaders.Authorization <-
new System.Net.Http.Headers.AuthenticationHeaderValue("token", token)
Expand All @@ -616,7 +626,7 @@ let createWebClient (url,auth:Auth option) =
let githubToken = Environment.GetEnvironmentVariable "PAKET_GITHUB_API_TOKEN"

match auth with
| Some (Credentials(username, password)) ->
| Some (Credentials(username, password, AuthType.Basic)) ->
// htttp://stackoverflow.com/questions/16044313/webclient-httpwebrequest-with-basic-authentication-returns-404-not-found-for-v/26016919#26016919
//this works ONLY if the server returns 401 first
//client DOES NOT send credentials on first request
Expand All @@ -627,6 +637,9 @@ let createWebClient (url,auth:Auth option) =
let credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(username + ":" + password))
client.Headers.[HttpRequestHeader.Authorization] <- sprintf "Basic %s" credentials
client.Credentials <- new NetworkCredential(username,password)
| Some (Credentials(username, password, AuthType.NTLM)) ->
let cred = NetworkCredential(username,password)
client.Credentials <- cred.GetCredential(new Uri(url), "NTLM")
| Some (Token token) ->
client.Headers.[HttpRequestHeader.Authorization] <- sprintf "token %s" token
| None when not (isNull githubToken) ->
Expand Down
13 changes: 8 additions & 5 deletions src/Paket.Core/Dependencies/NuGet.fs
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ let GetVersions force alternativeProjectRoot root (sources, packageName:PackageN
if (not force) && errorFileExists then return [] else
match nugetSource with
| NuGetV2 source ->
let auth = source.Authentication |> Option.map toBasicAuth
let auth = source.Authentication |> Option.map toCredentials
if String.containsIgnoreCase "artifactory" source.Url then
return [getVersionsCached "ODataNewestFirst" NuGetV2.tryGetAllVersionsFromNugetODataFindByIdNewestFirst (nugetSource, auth, source.Url, packageName) ]
else
Expand All @@ -449,7 +449,7 @@ let GetVersions force alternativeProjectRoot root (sources, packageName:PackageN
return v2Feeds
| NuGetV3 source ->
let! versionsAPI = PackageSources.getNuGetV3Resource source AllVersionsAPI
let auth = source.Authentication |> Option.map toBasicAuth
let auth = source.Authentication |> Option.map toCredentials
return [ getVersionsCached "V3" tryNuGetV3 (nugetSource, auth, versionsAPI, packageName) ]
| LocalNuGet(path,Some _) ->
return [ NuGetLocal.getAllVersionsFromLocalPath (true, path, packageName, alternativeProjectRoot, root) ]
Expand Down Expand Up @@ -695,9 +695,9 @@ let DownloadPackage(alternativeProjectRoot, root, (source : PackageSource), cach
#endif

if authenticated then
match source.Auth |> Option.map toBasicAuth with
match source.Auth |> Option.map toCredentials with
| None | Some(Token _) -> request.UseDefaultCredentials <- true
| Some(Credentials(username, password)) ->
| Some(Credentials(username, password, AuthType.Basic)) ->
// htttp://stackoverflow.com/questions/16044313/webclient-httpwebrequest-with-basic-authentication-returns-404-not-found-for-v/26016919#26016919
//this works ONLY if the server returns 401 first
//client DOES NOT send credentials on first request
Expand All @@ -707,6 +707,9 @@ let DownloadPackage(alternativeProjectRoot, root, (source : PackageSource), cach
//so use THIS instead to send credentials RIGHT AWAY
let credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(username + ":" + password))
request.Headers.[HttpRequestHeader.Authorization] <- String.Format("Basic {0}", credentials)
| Some(Credentials(username, password, AuthType.NTLM)) ->
let cred = NetworkCredential(username,password)
request.Credentials <- cred.GetCredential(downloadUri, "NTLM")
else
request.UseDefaultCredentials <- true

Expand Down Expand Up @@ -743,7 +746,7 @@ let DownloadPackage(alternativeProjectRoot, root, (source : PackageSource), cach
| :? System.Net.WebException as exn when
attempt < 5 &&
exn.Status = WebExceptionStatus.ProtocolError &&
(match source.Auth |> Option.map toBasicAuth with
(match source.Auth |> Option.map toCredentials with
| Some(Credentials(_)) -> true
| _ -> false)
-> do! download false (attempt + 1)
Expand Down
2 changes: 1 addition & 1 deletion src/Paket.Core/Dependencies/NuGetV2.fs
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ let FindPackages(auth, nugetURL, packageNamePrefix, maxResults) =
let url = sprintf "%s/Packages()?$filter=IsLatestVersion and IsAbsoluteLatestVersion and substringof('%s',tolower(Id))" nugetURL ((packageNamePrefix:string).ToLowerInvariant())
async {
try
let! raw = getFromUrl(auth |> Option.map toBasicAuth,url,acceptXml)
let! raw = getFromUrl(auth |> Option.map toCredentials,url,acceptXml)
let doc = XmlDocument()
doc.LoadXml raw
return
Expand Down
6 changes: 3 additions & 3 deletions src/Paket.Core/Dependencies/NuGetV3.fs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ let private getPackages(auth, nugetURL, packageNamePrefix, maxResults) = async {
match apiRes with
| Some url ->
let query = sprintf "%s?q=%s&take=%d" url packageNamePrefix maxResults
let! response = safeGetFromUrl(auth |> Option.map toBasicAuth,query,acceptJson)
let! response = safeGetFromUrl(auth |> Option.map toCredentials,query,acceptJson)
match SafeWebResult.asResult response with
| Result.Ok text -> return Result.Ok (extractPackages text)
| Result.Error err -> return Result.Error err
Expand Down Expand Up @@ -208,7 +208,7 @@ let getRegistration (source : NugetV3Source) (packageName:PackageName) (version:
async {
let! registrationUrl = PackageSources.getNuGetV3Resource source Registration
let url = sprintf "%s%s/%s.json" registrationUrl (packageName.ToString().ToLower()) (version.Normalize())
let! rawData = safeGetFromUrl (source.Authentication |> Option.map toBasicAuth, url, acceptJson)
let! rawData = safeGetFromUrl (source.Authentication |> Option.map toCredentials, url, acceptJson)
return
match rawData with
| NotFound -> None //raise <| System.Exception(sprintf "could not get registration data (404) from '%s'" url)
Expand All @@ -235,7 +235,7 @@ let getPackageDetails (source:NugetV3Source) (packageName:PackageName) (version:
match registrationData with
| None -> return EmptyResult
| Some registrationData ->
let! catalogData = getCatalog registrationData.CatalogEntry (source.Authentication |> Option.map toBasicAuth)
let! catalogData = getCatalog registrationData.CatalogEntry (source.Authentication |> Option.map toCredentials)

let dependencies =
if catalogData.DependencyGroups = null then
Expand Down
2 changes: 1 addition & 1 deletion src/Paket.Core/Dependencies/RemoteUpload.fs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ let Push maxTrials url apiKey clientVersion packageFileName =
try
let authOpt = ConfigFile.GetAuthentication(url)
match authOpt with
| Some (Auth.Credentials (u,_)) ->
| Some (Auth.Credentials (u,_,_)) ->
tracefnVerbose "Authorizing using credentials for user %s" u
| Some (Auth.Token _) ->
tracefnVerbose "Authorizing using token"
Expand Down
18 changes: 9 additions & 9 deletions src/Paket.Core/PackageManagement/NugetConvert.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,19 @@ type CredsMigrationMode =

static member ToAuthentication mode sourceName auth =
match mode, auth with
| Encrypt, Credentials(username,password) ->
ConfigAuthentication(username, password)
| Plaintext, Credentials(username,password) ->
PlainTextAuthentication(username, password)
| Selective, Credentials(username,password) ->
| Encrypt, Credentials(username, password, authType) ->
ConfigAuthentication(username, password, authType)
| Plaintext, Credentials(username, password, authType) ->
PlainTextAuthentication(username, password, authType)
| Selective, Credentials(username, password, authType) ->
let question =
sprintf "Credentials for source '%s': " sourceName +
"[encrypt and save in config (Yes) " +
sprintf "| save as plaintext in %s (No)]" Constants.DependenciesFileName

match Utils.askYesNo question with
| true -> ConfigAuthentication(username, password)
| false -> PlainTextAuthentication(username, password)
| true -> ConfigAuthentication(username, password, authType)
| false -> PlainTextAuthentication(username, password, authType)
| _ -> failwith "invalid auth"

/// Represents type of NuGet packages.config file
Expand Down Expand Up @@ -96,8 +96,8 @@ type NugetConfig =
let encryptedPass = authNode |> tryGetValue "Password"

match userName, encryptedPass, clearTextPass with
| Some userName, Some encryptedPass, _ -> Some(Credentials(userName, ConfigFile.DecryptNuget encryptedPass))
| Some userName, _, Some clearTextPass -> Some(Credentials(userName,clearTextPass))
| Some userName, Some encryptedPass, _ -> Some(Credentials(userName, ConfigFile.DecryptNuget encryptedPass, AuthType.Basic))
| Some userName, _, Some clearTextPass -> Some(Credentials(userName,clearTextPass, AuthType.Basic))
| _ -> None

configNode
Expand Down
4 changes: 2 additions & 2 deletions src/Paket.Core/PublicAPI.fs
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,10 @@ type Dependencies(dependenciesFileName: string) =
projectName, installAfter))

/// Adds credentials for a Nuget feed
member this.AddCredentials(source: string, username: string, password : string) : unit =
member this.AddCredentials(source: string, username: string, password : string, authType : string) : unit =
RunInLockedAccessMode(
this.RootPath,
fun () -> ConfigFile.askAndAddAuth source username password |> returnOrFail )
fun () -> ConfigFile.askAndAddAuth source username password authType |> returnOrFail )

/// Adds a token for a source
member this.AddToken(source : string, token : string) : unit =
Expand Down
29 changes: 21 additions & 8 deletions src/Paket.Core/Versioning/ConfigFile.fs
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,21 @@ let private readPassword (message : string) : string =
Console.Write "\b \b"
password.Substring(0, (password.Length - 1))
else ""
else Console.Write "\r"
else Console.WriteLine()
password

let getAuthFromNode (node : XmlNode) =
match node.Name.ToLowerInvariant() with
| "credential" ->
let username = node.Attributes.["username"].Value
let password = node.Attributes.["password"].Value
let authType =
match node.Attributes.["authType"] with
| null -> AuthType.Basic
| n -> n.Value |> Utils.parseAuthTypeString

let salt = node.Attributes.["salt"].Value
Credentials (username, Decrypt salt password)
Credentials (username, Decrypt salt password, authType)
| "token" -> Token node.Attributes.["value"].Value
| _ -> failwith "unknown node"

Expand All @@ -113,10 +118,11 @@ let private createSourceNode (credentialsNode : XmlNode) source nodeName =
credentialsNode.AppendChild node |> ignore
node

let private setCredentials (username : string) (password : string) (node : XmlElement) =
let private setCredentials (username : string) (password : string) (authType : string) (node : XmlElement) =
let salt, encrypedPassword = Encrypt password
node.SetAttribute ("username", username)
node.SetAttribute ("password", encrypedPassword)
node.SetAttribute ("authType", authType)
node.SetAttribute ("salt", salt)
node

Expand Down Expand Up @@ -167,19 +173,19 @@ let GetAuthenticationForUrl =
let GetAuthentication (source : string) =
GetAuthenticationForUrl(source,source)

let AddCredentials (source, username, password) =
let AddCredentials (source, username, password, authType) =
trial {
let! credentialsNode = getConfigNode "credentials"
let newCredentials =
match getSourceNodes credentialsNode source "credential" |> List.tryHead with
| None -> createSourceNode credentialsNode source "credential" |> Some
| Some existingNode ->
match getAuthFromNode existingNode with
| Credentials (_, existingPassword) ->
| Credentials (_, existingPassword, _) ->
if existingPassword <> password then existingNode |> Some
else None
| _ -> None
|> Option.map (setCredentials username password)
|> Option.map (setCredentials username password authType)
match newCredentials with
| Some credentials -> do! saveConfigNode credentials
| None -> ()
Expand All @@ -203,7 +209,7 @@ let AddToken (source, token) =
| None -> ()
}

let askAndAddAuth (source : string) (username : string) (password : string) =
let askAndAddAuth (source : string) (username : string) (password : string) (authType : string) =
let username =
if username = "" then
Console.Write "Username: "
Expand All @@ -216,4 +222,11 @@ let askAndAddAuth (source : string) (username : string) (password : string) =
readPassword "Password: "
else
password
AddCredentials (source.TrimEnd [|'/'|], username, password)
let authType =
if authType = "" then
Console.Write "Authentication type (basic|ntlm, default = basic): "
let input = Console.ReadLine().Trim()
if input = "" then "basic" else input
else
authType
AddCredentials (source.TrimEnd [|'/'|], username, password, authType)
Loading