How do you pass a SecureString from a PSCredential to a NetworkCredential? - httpwebrequest

NOTE: I cannot use PowerShell V3.0 here otherwise I'd be using Invoke-WebRequest and living a happy life.
I have a PowerShell V2.0 script that needs to POST data to a HTTP-Basic authenticated resource. For the purposes of the script I don't want or need to know the user's password, I just want to convert from a PSCredentials object (as returned from PromptForCredential) to a NetworkCredential for use with HttpWebRequest.
$uri = "https://example.com/some/resource/"
# Get our user's credentials...
$defaultUsername = "Some Username"
$caption = "Authentication required"
$message = "A username and password is required for ${uri}"
#$target = $uri #<<--NOTE: This prepends $uri+"\" to the username.
#$target = "" #<<--NOTE: This prepends "\" to the username.
$target = $null #<<--NOTE: This still prepends "\" to the username.
$psCredential = $Host.UI.PromptForCredential($caption, $message, $defaultUsername, $target)
# Construct a CredentialCache for HttpWebRequest...
# NOTE: We need to delete the "domain part" of the username from the PSCrential.Username, otherwise we get "Something\Username"
$username = ($psCredential.Username).Split('\')[1]
$networkCredential = New-Object System.Net.NetworkCredential($username, [System.Security.SecureString]$psCredential.Password)
$credentialCache = New-Object System.Net.CredentialCache
$credentialCache.Add( (New-Object Uri($uri)), "Basic", $networkCredential)
#...
$request = New-Object System.Net.HttpWebRequest($uri)
$request.Credentials = $credentialCache
#...
[System.Net.HttpWebResponse]$response = [System.Net.HttpWebResponse]$request.GetResponse()
This of course fails with the exception:
Exception calling "GetResponse" with "0" argument(s):
"The remote server returned an error: (401) Unauthorized."
Allegedly we have a NetworkCredential(String userName, SecureString password) constructor, but the user's credentials arrive the server as username:System.Security.SecureString.
Is there some little detail I'm missing? Do I need to decrypt the SecureString and pass that to the NetworkCredential(String userName, String password) constructor instead?

I've found the problem... the NetworkCredential(String userName, SecureString password) constructor is only available starting from .NET Framework 4.0. Of course PowerShell 2.0 is running in .NET 2.0.
While there are ways and means of making PowerShell 2.0 run inside .NET 4.0 I'm not a liberty to alter the runtime environment's configuration.
Instead I've gone down the "Unsecure String" path. Based on the article "How to properly convert SecureString to String" I've created this PowerShell function:
function Convert-To-Unsecure-String {
Param(
[Parameter(HelpMessage="The SecureString object to make a very unsecure String")]
[ValidateNotNull()]
[System.Security.SecureString]
$securePassword
)
$unmanagedString = [System.IntPtr]::Zero
try {
$unmanagedString = [Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($securePassword);
return [Runtime.InteropServices.Marshal]::PtrToStringUni($unmanagedString);
}
finally {
[Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($unmanagedString);
}
}
And replace the original example's NetworkCredential constructor with:
$networkCredential = New-Object System.Net.NetworkCredential($username, (Convert-To-Unsecure-String($psCredential.Password)) )
Now I'm getting the correct base64 encoded "username:password" string at the server.

I ran into the same issue. The fix for mine was very simple. Do not include the Domain name in the user name. I was trying to connect to JIRA to run a JQL.
Don't do this
$userName = Me#Mydomain; Or $userName=MyDomain/Me
But do that
$userName = Me

Related

Unable to connect to SQL server via PowerShell using secure password

I'm trying to connect to SQL server via PowerShell using the below (I'm new to this). I always get the error "login failed" when I use secure password (from Get-Credential or password stored in file). But if I pass the password as plaintext instead of secure string, it connects successfully. Could any one please suggest a method to pass secure password, ideally stored in an external file.
The code I ran and the error is below:
$cred = Get-Credential
$pwd = $cred.Password
$uid = $cred.UserName
$SQLServer = "."
$SQLDBName = "TestDB"
#Initialize connection string
$connString = "Data Source=$SQLServer;Database=$SQLDBName;User ID=$uid;Password=$pwd"
#Create a SQL connection object
$conn = New-Object System.Data.SqlClient.SqlConnection $connString
#Attempt to open the connection
$conn.Open()
if($conn.State -eq "Open")
{
# We have a successful connection here
# Notify of successful connection
Write-Host "Test connection successful"
$conn.Close()
}
Exception calling "Open" with "0" argument(s): "Login failed for user 'TestUser'."
At line:18 char:1
+ $conn.Open()
+ ~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : SqlException
Further details of error from SQL server:
Login failed for user 'TestUser'. Reason: Password did not match that for the login provided. [CLIENT: <local machine>]
Change this
$pwd = $cred.Password
to this
$pwd = $cred.GetNetworkCredential().Password
However, I would advise against storing a plain text password in memory like this. Your method requires it to--at best--be passed as a parameter in plain text, so you need a better method.
Try using this sqlserver module which supports the -Credential parameter in the Invoke-Sqlcmd function.
I was able to pass secure string as password by adding Integrated Security = True; parameter in connection string.
Thank you.

Powershell New-WebServiceProxy - how to call WCF service

I'm trying to call a WCF service from PowerShell.
This is what I have so far based on a few misc example I found on the web:
# Create the WebSvcURL variable and pass the WSDL URL
$WebSvcURL= “http://localhost/DEMO/SetPassKey/SetPassKey_Logic_SetPassKeyWebService_SetPassKeyWCF.svc?wsdl“
#Create the Web Service Proxy Object
#$serviceProxy = New-WebServiceProxy -Uri $WebSvcURL -Namespace "http://Sample.SetPassKey.Logic" -Class Program -UseDefaultCredential
$serviceProxy = New-WebServiceProxy -Uri $WebSvcURL -UseDefaultCredential
# Create Request Object
$namespace = $serviceProxy.getType().namespace
write-host "Namespace=$namespace"
$req = New-Object ($namespace + "/SetPassKeyOrchWebRequest")
$resp = New-Object ($namespace + "/SetPassKeyOrchWebResponse")
$req.NewPassKey = "TEST" # <--- PUT YOUR NEW PASSWORD HERE
$resp = $serviceProxy.SetPassKey($req)
$resp
I'm confused on a few things:
If and when I need to include the -Namespace and -Class parameters on the. Since it looks at the WSDL, I don't understand why it needs the -Namespace and -Class.
How to construct the request/response objects
Should the URL be the actual URL of the web service or should it include the ?wsdl suffix
UPDATE:
I found this blog which states:
The -Namespace parameter is optional and when not
specified then it gets a random value from the cmdlet.
I was thinking it was an XML Namespace, but it's a .NET framework namespace.
# Create the WebSvcURL variable and pass the WSDL URL
$WebSvcURL= “http://localhost/DEMO/SetPassKey/SetPassKey_Logic_SetPassKeyWebService_SetPassKeyWCF.svc?wsdl“
#Create the Web Service Proxy Object
$serviceProxy = New-WebServiceProxy -Uri $WebSvcURL -UseDefaultCredential -Namespace "MyNamespace" -Class Program
# Create Request Object
$req = New-Object ("MyNamespace.SetPassKeyOrchWebRequest")
$resp = New-Object ("MyNamespace.SetPassKeyOrchWebResponse")
$req.NewPassKey = "TEST" # <--- PUT YOUR NEW PASSWORD HERE
$resp = $serviceProxy.SetPassKey($req)
$resp
Now I get this error:
Exception calling "SetPassKey" with "1" argument(s): "The underlying connection was closed: An unexpected error occurred on a
receive."
The webservice can be called by a C# console program, so I know it works. Just need to call it from PowerShell.
I tried some things, and finally got it to work.
I did change my binding from WCF-WSHttp to WCF-BasicHttp.
I also followed this post, which said they only got it working by using the auto-generated namespace.
One of my issues originally was using "/" instead of "." as the separator between the namespace and the web request/response class names.
I'm still confused by the -namespace and -class parameters, as the post above said it only got the process working by using the autogenerated namespace (which happens when you omit the -namespace parm).
My code ended up something like this:
# Create the WebSvcURL variable and pass the WSDL URL
$WebSvcURL= “http://localhost/DEMO/SetPassKey/SetPassKey_Logic_SetPassKeyWebService_SetPassKeyWCF.svc?wsdl“
#Create the Web Service Proxy Object
$serviceProxy = New-WebServiceProxy -Uri $WebSvcURL -UseDefaultCredential
$autoGenNamespace = $serviceProxy.getType().namespace
write-host "Namespace=$namespace"
$req = New-Object ($autoGenNamespace + ".SetPassKeyOrchWebRequest")
$resp = New-Object ($autoGenNamespace + ".SetPassKeyOrchWebResponse")
$req.NewPassKey = "TEST" # <--- PUT YOUR NEW PASSWORD HERE
$req.NewPassKey = "TEST" # <--- PUT YOUR NEW PASSWORD HERE
Write-Host "Request:"
Write-Host ($req | Format-Table | Out-String)
Write-Host "About to call WebService"
$resp = $serviceProxy.SetPassKey($req)
Write-Host "Response:"
Write-Host ($resp | Format-Table | Out-String)
NOTE: Just putting the variable name without the write-host statement was causing things to come out in a different sequence. The FormatTable was still truncating some fields, so I will list each return string in my $resp separately.

Authentication error with webclient in powershell

I'm relatively new to Powershell so really not sure where to go with this issue now. I am trying to download a file from a subversion repository and am getting the (401) Unauthorized" error. I am able to log into the site and download the file using IE using the exact Same credentials on the same machine.
$source = "http://repository/folder/File.exe"
$destination = "E:\Temp\File.exe"
$wc = New-Object System.Net.WebClient
$user="user"
$pwd=convertto-securestring -string "password" -AsPlainText -force
$creds=New-Object System.Management.Automation.PSCredential -ArgumentList $user, $pwd
$wc.Credentials = New-Object System.Net.NetworkCredential ($user, $Creds.GetNetworkCredential().Password,"DOMAIN")
$download=$wc.DownloadFile($source, "$destination")
Exception calling "DownloadFile" with "2" argument(s): "The remote server returned an error: (401) Unauthorized."
Any ideas if this is cross platform issue? And how to get around this?
Thanks
Are you using basic auth on your iis/apache? If so try this:
$source = "http://repository/folder/File.exe"
$destination = "E:\Temp\File.exe"
$wc = new-object System.Net.WebClient
$credCache = new-object System.Net.CredentialCache
$creds = new-object System.Net.NetworkCredential($user,$pwd)
$credCache.Add($source, "Basic", $creds)
$wc.Credentials = $credCache
$wc.DownloadFile($source, $destination)

Google login in PHP backend and JS frontend

Front end is 100% JS. User click on sign in button and an authResult['code'] is received and send via ajax to localhost/api/user/login which has the following content:
$code = $data['code'];
require_once 'Google/Client.php';
$client = new Google_Client();
$client->setClientId('xxxxxx');
$client->setClientSecret('xxxxx');
$client->setRedirectUri('http://localhost:8080');
$client->setScopes('email'); //Why do I need this? I already set scope in JS.
$client->authenticate($code); //It fails here. with no error. just 400 bad request.
$token = json_decode($client->getAccessToken());
$reqUrl = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' .
$token->access_token;
$req = new Google_HttpRequest($reqUrl);
$tokenInfo = json_decode(
$client::getIo()->authenticatedRequest($req)->getResponseBody());
//Check errors.
//Save user personal info in database
//Set login sessions
Why do I need to set scopes if I already set them in javascript?
Why is it failing when authenticate function is called? Im getting no erros.
Why do I need a setRedirectUri() when it is on the backend?
You don't need to set scopes in this case.
(see answer 3, but also): Check your client ID matches the one used in the Javascript, and that the client secret is exactly as in the console (no trailing/leading spaces).
Changing your redirecturi to 'postmessage' - this is the string used when the code was generated via the Javascript process.
You can also try manually constructing the URL and calling it with curl to make sure everything is as you expect: https://developers.google.com/accounts/docs/OAuth2WebServer#handlingtheresponse

Apache Directory studio ldap bind from php

I am new to Apache Directory Studio and ldap. I am running a ldap server from Apache Directory studio. I have a user in ldap and i am trying to bind to the uid from a php script.Not sure where i am going wrong.
I am using username as "uid=admin,ou=user"
password as "secret"
I also tried username as "uid=arone_a,ou=users,dc=example,dc=com"
and password as "password"
Password attribute was set manually and arone_a is the user uid.
I am trying to write a php script which can pull all users in the ldap server.
Thanks in advance.
My PHP script is:
$ldaphost = "localhost";
$ldapport = 10389;
$ldaprdn='uid=admin,ou=system';
$ldappass='secret';
$ldapconn = ldap_connect($ldaphost, $ldapport)
or die("Could not connect to $ldaphost");
if($ldapconn)
{
$ldapbind=ldap_bind($ldapconn,$ldaprdn,$ldappass);
if($ldapbind)
{
echo "success";
}
else
{
echo "not success";
}
}
Connection goes through but bind is not going through.
I was having a similar issue and the problem was that I added to the userPassword attribute an additional param specifying the language, resulting in userPassword;lang-ca-ES (the wizard shows a form to add it).
That provoked that using Apache Directory Studio the "Verify" was working good, but it failed in the "Bind" check (you can do both in the password editor, double clicking the userPassword attribute.
I finally left userPassword without additional attributes and it binded perfectly :)
Just add the ldap set option, it worked for me
<?php
$ldaphost = "localhost";
$ldapport = 10389;
$ldaprdn='uid=admin,ou=system';
$ldappass='secret';
$ldapconn = ldap_connect($ldaphost, $ldapport)
or die("Could not connect to $ldaphost");
if($ldapconn) {
ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
$ldapbind=ldap_bind($ldapconn,$ldaprdn,$ldappass);
if($ldapbind) {
echo "success";
} else {
echo "not success";
}
}
?>
The simple BIND request requires the DN, not the RDN. Should your BIND DN be something like uid=admin,ou=system,dc=example,dc=com?
see also
LDAP: Authentication best practices
LDAP: Programming practices