Migrating a SOAP client from to .NET5 that uses WSE and Microsoft.Web.Services2 - wcf

I am migrating a .NetFramework application that access a DMS system over SOAP
The working implementation involves some generated code where it was necessary to change the base class to Microsoft.Web.Services2.WebServiceClientProtocol in order for the security headers to be correctly built.
public partial class MyService: Microsoft.Web.Services2.WebServicesClientProtocol
The following code successfully calls the WsSearchDmsDocument
var token = new UsernameToken(DmsUsername, DmsPassword, PasswordOption.SendHashed);
var client = new MyService() {Url = ReinsUrl};
SoapContext requestContext = client.RequestSoapContext;
requestContext.Security.Timestamp.TtlInSeconds = 60;
requestContext.Security.Tokens.Add(token);
var myRequest = new Request();
var response = client.WsSearchDmsDocument(request);
Which sends the username/password security header looking like and returns the expected response
<wsse:UsernameToken wsu:Id='UsernameToken-238be95be3bf445fb8534666a7a8693c'>
<wsse:Username>***login***</wsse:Username>
<wsse:Password Type='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-usernametoken-profile-1.0#PasswordDigest'>***Base64 (SHA-1 (nonce + created + password) )***</wsse:Password>
<wsse:Nonce EncodingType='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soapmessage-security-1.0#Base64Binary'>***Base64 nonce***</wsse:Nonce>
<wsu:Created>2019-09-06T12:09:15.604Z</wsu:Created>
</wsse:UsernameToken>
In .Net5
I modified the MyService class in the following way
internal partial class MyService: System.ServiceModel.ClientBase<MyReinsServices>, MyReinsServices
Then I try to call the service
var token = new UsernameToken(DmsUsername, DmsPassword, PasswordOption.SendHashed);
MyService client = new MyService(ReinsServicesClient.EndpointConfiguration.ReinsServicesSoap11, ReinsUrl);
UserNamePasswordClientCredential credential = client.ClientCredentials.UserName;
credential.UserName = DmsUsername;
credential.Password = DmsPassword;
var myRequest = new Request();
var response = client.WsSearchDmsDocument(request);
But this fails with
com.sun.xml.wss.XWSSecurityException:
Message does not conform to configured policy [ AuthenticationTokenPolicy(S) ]:
No Security Header found;
nested exception is com.sun.xml.wss.XWSSecurityException:
com.sun.xml.wss.XWSSecurityException:
Message does not conform to configured policy [ AuthenticationTokenPolicy(S) ]:
No Security Header found
This is a similar error to what I was getting in the .Net Framework version before I used Microsoft.Web.Services2.WebServicesClientProtocol
I think I am very close to a solution but no matter what client.ClientCredentials I take it does not satisfy the security header needed by this particular SOAP service
EDIT
I am able to use SoapUI to call this service. I have to set in WS-Security setting a username and password with PasswordDigest and adding this as a Basic Auth to the outgoing WSS. I then copy the resulting SOAP Envelope into Soap.txt and try to send this via .NET5 in the following code
string soap = File.ReadAllText("soup.txt");
XmlDocument document = new XmlDocument();
document.LoadXml(soap); //loading soap message as string
XmlNamespaceManager manager = new XmlNamespaceManager(document.NameTable);
manager.AddNamespace("reins", "http://scor.com/dms-reins-webservices/schemas/2.0/reins");
manager.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
manager.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
manager.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
//Build the token
RNGCryptoServiceProvider Generator = new RNGCryptoServiceProvider();
var _nonce = new byte[16];
Generator.GetBytes(_nonce);
string nonce = Convert.ToBase64String(_nonce);
var created = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ssZ");
string payLoad = nonce + created + DmsPassword;
byte[] payLoadBytes = System.Text.Encoding.UTF8.GetBytes(payLoad);
SHA1 sha = new SHA1CryptoServiceProvider();
var tokenBytes = sha.ComputeHash(payLoadBytes);
string token = Convert.ToBase64String(tokenBytes);
document.SelectSingleNode("//soapenv:Envelope/soapenv:Header/wsse:Security/wsse:UsernameToken/wsu:Created", manager).InnerText = created;
document.SelectSingleNode("//soapenv:Envelope/soapenv:Header/wsse:Security/wsse:UsernameToken/wsse:Password", manager).InnerText = token;
document.SelectSingleNode("//soapenv:Envelope/soapenv:Header/wsse:Security/wsse:UsernameToken/wsse:Nonce", manager).InnerText = nonce;
var httpClient = new HttpClient();
httpClient.DefaultRequestVersion = HttpVersion.Version11;
HttpResponseMessage response;
var soapMessage = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri(url),
Content = new StringContent(s, Encoding.UTF8, MediaTypeNames.Text.Xml),
};
soapMessage.Headers.Add("Accept", "text/xml");
soapMessage.Headers.Add("Accept-Encoding", "gzip,deflate");
soapMessage.Headers.Add("SOAPAction", "");
soapMessage.Headers.Add("Connection", "Keep-Alive");
soapMessage.Headers.Add("User-Agent", "Apache-HttpClient/4.5.5 (Java/12.0.1)");
response = httpClient.Send(soapMessage);
When I make the request via SoapUi I get a StatusCode 200 and the data I want. When I use the above code that should perform the exact same operation I get an StatusCode 500 Internal Server error.
Any suggestions how to solve either the first part of this or to get this hack to work using .NET5 or .NET6 would be welcome

Related

I'm getting an error when using RestSharp DigestAuthenticator

So I have a .net core API that's trying to use RestSharp(which I'm fairly new to) to call another API. This other API apparently requires Digest based authentication to access, so I went ahead and tried using the DigestAuthenticator class provided by RestSharp. However, the result was an error saying Header not found : Digest Realm. Image of error below.
RestSharp DigestAuthenticator Error
So, I'm assuming that I would need to add a header for digest auth in my request. But, how would I go about doing that?
Below is what I've done so far,
RestClient client = new RestClient();
RestRequest request = new RestRequest();
client.BaseUrl = new System.Uri("http://ip_address:port/otherApi");
client.Authenticator = new DigestAuthenticator("myusername", "mypassword");
request.Method = Method.POST;
//not sure how to add header for digest auth
//request.AddHeader("")
request.AddParameter("application/xml", xmlString, ParameterType.RequestBody);
client.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
IRestResponse response = client.Execute(request);
return Ok(response.Content);

asp.net core webapp - OAUTH2 AUTHORIZATION - 401 Error

i want to use the itwin api and i created a little asp.net core 3.1 webapp.
The Authorization endpoint: https://ims.bentley.com/connect/authorize works fine and i get a code.
Now i want to use the code to get the access token with this backend method:
string url = "https://ims.bentley.com/connect/token";
string clientID = "webapp-XXXXXXXXXXXXXXXXXXXXXX";
string secretID = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
//string svcCredentials = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(clientID + ":" + secretID));
//string svcCredentials = Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(clientID + ":" + secretID));
string svcCredentials = Convert.ToBase64String(Encoding.UTF8.GetBytes(clientID + ":" + secretID));
var nvc = new List<KeyValuePair<string, string>>();
nvc.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
nvc.Add(new KeyValuePair<string, string>("code", code));
nvc.Add(new KeyValuePair<string, string>("redirect_uri", "http://localhost:44343/signin-callback"));
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", svcCredentials);
HttpResponseMessage response = client.PostAsync(url, new FormUrlEncodedContent(nvc)).Result;
var token = response.Content.ReadAsStringAsync().Result;
}`
Everthing works fine but i get an 401 Error. Use i the wrong BASE64 encoding?
thank you for help.
I suggest trying the following:
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", svcCredentials);
Based on the documentation for AuthenticationHeaderValue the first parameter is the scheme and we use Bearer.
You need to url-encode client_id and client_secret before using them for Basic authentication, as described in documentation
The client must authenticate using the HTTP Basic method and provide the url-encoded clientId and the clientSecret
This is also stated in OAuth specification
The client identifier is encoded using the
"application/x-www-form-urlencoded" encoding algorithm per
Appendix B, and the encoded value is used as the username; the client
password is encoded using the same algorithm and used as the
password.

NTLM Auth with RestSharp

I am attempting to create some tests using RestSharp for a project I am working on.
This project uses Single Sign-on NTLM Authentication.
I am attemping to use a NTLMAuthenticator but my getUser request is always failing. I am not positive what URL to put in for the CredentialCache, the project or the SSO Id Provider.
SharedRequests shared = new SharedRequests();
var credential = new CredentialCache
{
{
new Uri("project or ID Provider URL or something else?"),
"NTLM",
new NetworkCredential("doamin\Username", "Password")
}
};
RestClient client = new RestClient();
client.BaseUrl=new Uri("projectURL");
client.Authenticator = new NtlmAuthenticator(credential);
client.PreAuthenticate = true;
RestRequest request = shared.GetCurrentUser();
IRestResponse response = client.Execute(request);
my response always gets a 500 error which is what is expected when no auth cookies are present.

Sharepoint 2013 REST api from desktop application - Authentication

I am trying to consume SharePoint 2013 REST services from a Desktop application ( cross-platform, cross-os ). Application is basically a HTML page in application view.
Is there a simple way I can authenticate my calls using HTTP methods ?
Yes, you can get authenticated and receive a digest via a REST call.
string url = "http://Your.SP.Site";
HttpClient client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true });
client.BaseAddress = new System.Uri(url);
string cmd = "_api/contextinfo";
client.DefaultRequestHeaders.Add("Accept", "application/json;odata=verbose");
client.DefaultRequestHeaders.Add("ContentType", "application/json");
client.DefaultRequestHeaders.Add("ContentLength", "0");
StringContent httpContent = new StringContent("");
var response = client.PostAsync(cmd, httpContent).Result;
if (response.IsSuccessStatusCode)
{
string content = response.Content.ReadAsStringAsync().Result;
JsonObject val = JsonValue.Parse(content).GetObject();
JsonObject d = val.GetNamedObject("d");
JsonObject wi = d.GetNamedObject("GetContextWebInformation");
retVal = wi.GetNamedString("FormDigestValue");
}
The above example shows how to retrieve the digest in C# with the HttpClient. This string needs to be passed as a header to all of the other rest calls you make to carry forward the authentication. You can create a credential by passing in a username and password if needed.
I have more examples here:
https://arcandotnet.wordpress.com/2015/04/01/sharepoint-2013-rest-services-using-c-and-the-httpclient-for-windows-store-apps/
You can do these calls in JavaScript as well and Microsoft has a lot of documentation on that. There is also .NET library, Microsoft.SharePoint.Client.DLL (CSOM) that simplifies this type of coding but you must have the library installed on the client.

Sitecore context & HttpWebRequest

Is it possible to pass Sitecore Credentials via an HttpWebRequest? The code below works great, except for the fact that the asmx being called executes as the anonymous user. I'd like to be able to pass the sitecore current user credentials to the page I'm calling.
CookieContainer cookieJar = new CookieContainer();
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("ttp://localhost/file.asmx");
req.Headers.Add("SOAPAction", "\"h_ttp://tempuri.org/Register\"");
req.ContentType = "text/xml;charset=\"utf-8\"";
req.Accept = "text/xml";
req.Method = "POST";
req.ContentLength = 0;
req.CookieContainer = cookieJar;
WebResponse response = req.GetResponse();
Stream responseStream = response.GetResponseStream();
StreamReader respStrm = new StreamReader(response.GetResponseStream(), System.Text.Encoding.ASCII);
string responseITem = respStrm.ReadToEnd();
HttpContext.Current.Response.Write(responseITem);
HttpContext.Current.Response.End();
The Sitecore user credential informations are stored in a cookie. So you could add client cookies to your http request:
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
request.CookieContainer = new CookieContainer();
HttpCookieCollection userCookies = Request.Cookies;
for (int userCookieCount = 0; userCookieCount < userCookies.Count; userCookieCount++)
{
HttpCookie httpCookie = userCookies.Get(userCookieCount);
Cookie cookie = new Cookie();
/* We have to add the target host because the cookie does not contain the domain information.
In this case, this behaviour is not a security issue, because the target is our own platform.
Further informations: http://stackoverflow.com/a/460990
*/
cookie.Domain = request.RequestUri.Host;
cookie.Expires = httpCookie.Expires;
cookie.Name = httpCookie.Name;
cookie.Path = httpCookie.Path;
cookie.Secure = httpCookie.Secure;
cookie.Value = httpCookie.Value;
request.CookieContainer.Add(cookie);
}
You could also check our Sitecore Error Manager module. There we also create http requests with sending the client cookies (see lines 149-170):
https://github.com/unic/SitecoreErrorManager/blob/master/Modules.ErrorManager/Controls/BaseError.cs
You need to add the current user credentials to the request so you can retrieve them in your asmx webservice and use the credentials to log the user so the context is set.
// add the credentials to the Post method
var credentials = "yourCredentials";
req.ContentLength = credentials.Length;
using (var dataStream = req.GetRequestStream())
{
dataStream.Write(credentials, 0, credentials.Length);
}
In your asmx webservice you can login with the userName only or the combination of the userName and Password which are retrieved from the request.
Sitecore.Security.Authentication.AuthenticationManager.Login(userName);
EDIT: there is a security risk here when sending credentials as plain text, use at least HTTPS to make it more secure.