how to consume ws security header in soap web service c# - c#

I need some help.Firstly I wrote one soap web service with basic authentication.But I have to change with wssecurity in soap.How can I consume coming ws security header I have to read username and password and I have to compare my username and password in my webconfig file inside service. I write this code but I m not sure : I done this way.But how can I configure in webconfig.file
public class QuantityService : Microsoft.Web.Services3.WebServicesClientProtocol, IQuantityService
{
private OperationResult AuthCheck()
{
OperationResult retVal = new OperationResult()
{
ReturnCode = 0,
ReturnMessage = "OK"
};
string userName = ConfigurationManager.AppSettings["username"].ToString();
string password = ConfigurationManager.AppSettings["password"].ToString();
//UsernameToken token = new UsernameToken(userName,password,PasswordOption.SendPlainText);
QuantityService serviceProxy = new QuantityService();
SoapContext requestContext = serviceProxy.RequestSoapContext;
//requestContext.Security.Tokens.Add(token);
if (requestContext == null)
{
throw new ApplicationException("Non-SOAP request.");
}
foreach (SecurityToken tok in requestContext.Security.Tokens)
{
if (tok is UsernameToken)
{
if (userName == ((UsernameToken)tok).Username && password == ((UsernameToken)tok).Password)
{
retVal.ReturnCode = 0;
retVal.ReturnMessage = "OK";
}
else
{
retVal.ReturnCode = -2;
retVal.ReturnMessage = "Unauthorized.";
}
}
}
return retVal;
}

Related

Send MIME content to a particular user using Chilkat and Graph API access token

I am trying to send MIME content to a single user using Chilkat library. For sending mail I am using access token of Graph API client credentials. But getting authentication failure error in chilkat. Below is the sample code.
Calling from Main method:
string mime = System.IO.File.ReadAllText(#"\\Mac\Home\Downloads\12_01.eml");
GetFreshToken(tenantID, clientID, clientSecret);
SendMailUsingChilkat(tenantID,clientID, clientSecret,mime,"user.name#domain.com");
Methods used in:
bool GetFreshToken(string tenantId, string clientId, string clientSecret)
{
bool ret = false;
Chilkat.OAuth2 oauth2 = new Chilkat.OAuth2();
bool success;
oauth2.ListenPort = 3017;
oauth2.AuthorizationEndpoint = "https://login.microsoftonline.com/xxxxxx-xxxx-xxxx-xxxx-xxxx0f78bbd/oauth2/v2.0/authorize";
oauth2.TokenEndpoint = "https://login.microsoftonline.com/xxxxxx-xxxx-xxxx-xxxx-xxxx0f78bbd/oauth2/v2.0/token";
oauth2.ClientId = clientId;
oauth2.ClientSecret = clientSecret;
oauth2.CodeChallenge = false;
oauth2.Scope = "openid profile offline_access https://outlook.office365.com/SMTP.Send https://outlook.office365.com/POP.AccessAsUser.All https://outlook.office365.com/IMAP.AccessAsUser.All";
string url = oauth2.StartAuth();
if (oauth2.LastMethodSuccess != true)
{
MessageBox.Show(oauth2.LastErrorText);
return ret;
}
int numMsWaited = 0;
while ((numMsWaited < 30000) && (oauth2.AuthFlowState < 3))
{
oauth2.SleepMs(100);
numMsWaited = numMsWaited + 100;
}
if (oauth2.AuthFlowState < 3)
{
oauth2.Cancel();
MessageBox.Show("No response from the browser!");
return ret;
}
if (oauth2.AuthFlowState == 5)
{
MessageBox.Show("OAuth2 failed to complete.");
MessageBox.Show(oauth2.FailureInfo);
return ret;
}
if (oauth2.AuthFlowState == 4)
{
MessageBox.Show("OAuth2 authorization was denied.");
MessageBox.Show(oauth2.AccessTokenResponse);
return ret;
}
if (oauth2.AuthFlowState != 3)
{
MessageBox.Show("Unexpected AuthFlowState:" + Convert.ToString(oauth2.AuthFlowState));
return ret;
}
Chilkat.JsonObject json = new Chilkat.JsonObject();
json.Load(oauth2.AccessTokenResponse);
json.EmitCompact = false;
if (json.HasMember("expires_on") != true)
{
Chilkat.CkDateTime dtExpire = new Chilkat.CkDateTime();
dtExpire.SetFromCurrentSystemTime();
dtExpire.AddSeconds(json.IntOf("expires_in"));
json.AppendString("expires_on", dtExpire.GetAsUnixTimeStr(false));
}
json.Emit();
Chilkat.FileAccess fac = new Chilkat.FileAccess();
ret = fac.WriteEntireTextFile("microsoftGraph.json", json.Emit(), "utf-8", false);
return ret;
}
bool GetOrRefreshToken(string clientId, string clientSecret, out string accessToken)
{
bool ret = false;
accessToken = null;
Chilkat.JsonObject json = new Chilkat.JsonObject();
bool success = json.LoadFile("microsoftGraph.json");
if (success != true)
{
return false;
}
Chilkat.CkDateTime dtExpire = new Chilkat.CkDateTime();
dtExpire.SetFromUnixTime(false, json.IntOf("expires_on"));
if (dtExpire.ExpiresWithin(10, "minutes") != true)
{
Debug.WriteLine("No need to refresh, the access token won't expire within the next 10 minutes.");
accessToken = json.StringOf("access_token");
return true;
}
// OK, we need to refresh the access token by sending a POST like this:
Chilkat.OAuth2 oauth2 = new Chilkat.OAuth2();
oauth2.TokenEndpoint = "https://login.microsoftonline.com/xxxxxx-xxxx-xxxx-xxxx-xxxx0f78bbd/oauth2/v2.0/token";
oauth2.ClientId = clientId;
oauth2.ClientSecret = clientSecret;
oauth2.RefreshToken = json.StringOf("refresh_token");
success = oauth2.RefreshAccessToken();
if (success != true)
{
Debug.WriteLine(oauth2.LastErrorText);
return false;
}
// Update the JSON with the new tokens.
json.UpdateString("access_token", oauth2.AccessToken);
json.UpdateString("refresh_token", oauth2.RefreshToken);
json.EmitCompact = false;
Debug.WriteLine(json.Emit());
if (json.HasMember("expires_on") != true)
{
dtExpire.SetFromCurrentSystemTime();
dtExpire.AddSeconds(json.IntOf("expires_in"));
json.AppendString("expires_on", dtExpire.GetAsUnixTimeStr(false));
}
Chilkat.FileAccess fac = new Chilkat.FileAccess();
ret = fac.WriteEntireTextFile("microsoftGraph.json", json.Emit(), "utf-8", false);
accessToken = json.StringOf("access_token");
return ret;
}
async void SendMailUsingChilkat(string tenantId, string clientId, string clientSecret, string mime, string recipient)
{
string accessToken;
GetOrRefreshToken(clientId, clientSecret, out accessToken);
Chilkat.Email email = new Chilkat.Email();
email.LoadEml(mime);
Chilkat.MailMan mailman = new Chilkat.MailMan();
mailman.SmtpHost = "smtp.office365.com";
mailman.SmtpPort = 587;
mailman.StartTLS = true;
mailman.SmtpUsername = email.From;
mailman.OAuth2AccessToken = accessToken;
bool success = mailman.SendMime(email.From, recipient, mime);
if (success == true)
{
Debug.WriteLine("Mail Sent!");
return;
}
if (mailman.LastSmtpStatus != 535)
{
Debug.WriteLine(mailman.LastErrorText);
return;
}
}
I referred below links for implementation
https://www.example-code.com/csharp/office365_oauth2_access_token.asp
https://www.example-code.com/csharp/office365_refresh_access_token.asp
https://www.example-code.com/csharp/office365_smtp_send_email.asp
At the time I'm writing this response, Chilkat has not yet been able to get the client credentials flow working with Office365. We can get an access token, but it fails to authenticate. I believe additional magical incantations need to be performed in Azure, and I've yet to discover the secret sauce.
Especially with different identity and account types: https://learn.microsoft.com/en-us/security/zero-trust/develop/identity-supported-account-types
See related Microsoft docs:
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
https://learn.microsoft.com/en-us/answers/questions/1046512/smtp-for-o365-with-client-credentials-flow.html

How to change password policy by allowing Non-alphanumeric characters in the password?

I currently am using an IdentityServer4.
Authentication provider is AD.
I implemented the IResourceOwnerPasswordValidator interface, consequently its method ValidateAsync was implemented.
public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
var username = context.UserName;
var password = context.Password;
var validation = adAuthProvider.Validate(username, password);
if (validation.Valid)
{
username = Utilities.TrimDomain(username);
context.Result = new GrantValidationResult(username, "pwd");
}
else
{
if (validation.ValidationResponseType == ValidationResponseType.Unauthorized)
{
context.Result = new GrantValidationResult(username, "pwd");
}
else
{
context.Result.IsError = validation.ValidationResponseType == ValidationResponseType.Error;
}
}
context.Result.CustomResponse = new Dictionary<string, object>();
context.Result.CustomResponse.Add("ValidationResponse", validation);
return Task.CompletedTask;
}
An implementation of Validate method in AdAuthProvider class
public ValidationResponse Validate(string username, string password)
{
var validation = new ValidationResponse { Valid = false };
var ad = GetActiveDirectoryConfiguration(username);
using (var connection = ldapProvider.Connect(ad.Url, ad.Port))
{
username = username;
var userLoginWithDomainFromConfig = $"{ ad.DistinguishedName }#{ ad.Url }";
connection.Login(userLoginWithDomainFromConfig, ad.Password);
//try to login
connection.Login($"{username}#{ad.Url}", password);
validation.Valid = true;
}
return validation;
}
How to change password policy by allowing Non-alphanumeric characters in the password?

IFtpHomeDirectoryProvider did not rise

I've realized curom authentication for ftp site basing on https://blogs.msdn.microsoft.com/robert_mcmurray/2011/06/30/how-to-create-an-authentication-provider-for-ftp-7-5-using-blogengine-nets-xml-membership-files/, but IFtpHomeDirectoryProvider.GetUserHomeDirectoryData did not rise
public class FtpEngineNetAuthentication : BaseProvider,
IFtpAuthenticationProvider
, IFtpRoleProvider
, IFtpHomeDirectoryProvider
, IFtpPostprocessProvider
, IFtpLogProvider
{
private readonly string _logfile = Path.Combine(#"c:\test", "logs", "FtpExtension.log");
private string _dbConnectionString;
private string _ftpHomeDirectory;
protected override void Initialize(StringDictionary config)
{
// Retrieve the paths from the configuration dictionary.
_dbConnectionString = config["RextorConnectionString"];
_ftpHomeDirectory = config["ftpHomeDirectory"];
}
bool TryGetUser(string login, out User user)
{
user = null;
try
{
using (var conn = new SqlConnection(_dbConnectionString))
{
var command = new SqlCommand("SELECT UserName, Password, HashAlgorithm, PasswordSalt FROM Orchard_Users_UserPartRecord WHERE UserName = #login", conn);
command.Parameters.AddWithValue("#login", login);
conn.Open();
using (var reader = command.ExecuteReader())
{
if (reader.Read()) // Don't assume we have any rows.
{
user = new User()
{
Login = reader.GetString(0),
Password = reader.GetString(1),
HashAlgorithm = reader.GetString(2),
PasswordSalt = reader.GetString(3)
};
}
}
conn.Close();
}
}
catch (Exception)
{
return false;
}
return true;
}
// Define the GetUserHomeDirectoryData method.
string IFtpHomeDirectoryProvider.GetUserHomeDirectoryData(string sessionId, string siteName, string userName)
{
// Test if the path to the home directory is empty.
if (string.IsNullOrEmpty(_ftpHomeDirectory))
{
// Throw an exception if the path is missing or empty.
throw new ArgumentException(#"Missing ftpHomeDirectory value in configuration.");
}
LogMessage($"directory: {userName}");
var result = $#"{_ftpHomeDirectory}\{userName}";
LogMessage(result);
// Return the path to the home directory.
return result;
}
// Define the AuthenticateUser method.
bool IFtpAuthenticationProvider.AuthenticateUser(string sessionId, string siteName, string userName, string userPassword, out string canonicalUserName)
{
// Define the canonical user name.
canonicalUserName = userName.Replace("#", "").Replace(".", "");
// Validate that the user name and password are not empty.
if (String.IsNullOrEmpty(userName) || String.IsNullOrEmpty(userPassword))
{
// Return false (authentication failed) if either are empty.
return false;
}
try
{
// Create a user object.
User user = null;
// Test if the user name is in the dictionary of users.
if (TryGetUser(userName, out user))
{
var saltBytes = Convert.FromBase64String(user.PasswordSalt);
// Retrieve a sequence of bytes for the password.
bool isValid;
if (user.HashAlgorithm == "PBKDF2")
{
LogMessage($"user password: {user.Password}");
LogMessage($"user salt: {user.PasswordSalt}");
LogMessage($"user salt&pwd: {Encoding.Unicode.GetString(CombineSaltAndPassword(saltBytes, userPassword))}");
// We can't reuse ComputeHashBase64 as the internally generated salt repeated calls to Crypto.HashPassword() return different results.
isValid = true;
//Crypto.VerifyHashedPassword(user.Password, Encoding.Unicode.GetString(CombineSaltAndPassword(saltBytes, userPassword)));
//PasswordHash.ValidatePassword(user.Password, Encoding.Unicode.GetString(CombineSaltAndPassword(saltBytes, userPassword)));
//BCryptHelper.CheckPassword(user.Password, Encoding.Unicode.GetString(CombineSaltAndPassword(saltBytes, userPassword)));
}
else
{
isValid = SecureStringEquality(user.Password, ComputeHashBase64(user.HashAlgorithm, saltBytes, userPassword));
}
//if (isValid && user.HashAlgorithm != DefaultHashAlgorithm)
//{
// var keepOldConfiguration = _appConfigurationAccessor.GetConfiguration("Orchard.Users.KeepOldPasswordHash");
// if (String.IsNullOrEmpty(keepOldConfiguration) || keepOldConfiguration.Equals("false", StringComparison.OrdinalIgnoreCase))
// {
// user.HashAlgorithm = DefaultHashAlgorithm;
// user.Password = ComputeHashBase64(user.HashAlgorithm, saltBytes, userPassword);
// }
//}
return isValid;
}
}
catch (Exception ex)
{
LogMessage(ex.Message);
// Raise an exception if an error occurs.
throw new ProviderException(ex.Message, ex.InnerException);
}
// Return false (authentication failed) if authentication fails to this point.
return false;
}
bool IFtpRoleProvider.IsUserInRole(string sessionId, string siteName,string userName, string userRole)
{
LogMessage($"check role: {userName} - {userRole}");
return true;
}
public FtpProcessStatus HandlePostprocess(FtpPostprocessParameters postProcessParameters)
{
LogMessage("Running Post Process"); //this message never appears
return FtpProcessStatus.FtpProcessContinue;
}
public void Log(FtpLogEntry logEntry)
{
//LogMessage(logEntry.);
}
private void LogMessage(string logEntry)
{
using (var sw = new StreamWriter(_logfile, true))
{
// Retrieve the current date and time for the log entry.
var dt = DateTime.Now;
sw.WriteLine("{0}\t{1}\tMESSAGE:{2}",
dt.ToShortDateString(),
dt.ToLongTimeString(),
logEntry);
sw.Flush();
}
}

Post to a closed group where Im admin

I have the following code(facebook C# SDK) to post to facebook wall :
public long? UploadPost(string intLinkTitle, string inMessage, string inLinkCaption, string inLinkUrl, string inLinkDescription, string inLinkUrlPicture)
{
object obj;
Facebook.JsonObject jsonObj;
FacebookClient client;
string access_token = ConfigurationManager.AppSettings["FacebookPageAccessToken"].ToString();
client = new FacebookClient(access_token);
var args = new Dictionary<string, object>();
args["message"] = inMessage;
args["caption"] = inLinkCaption;
args["description"] = inLinkDescription;
args["name"] = intLinkTitle;
args["picture"] = inLinkUrlPicture;
args["link"] = inLinkUrl;
if ((obj = client.Post("/" + ConfigurationManager.AppSettings["FacebookPageId"].ToString() + "/feed", args)) != null)
{
if ((jsonObj = obj as Facebook.JsonObject) != null)
{
if (jsonObj.Count > 0)
return long.Parse(jsonObj[0].ToString().Split('_').Last().ToString());
}
}
return null;
}
}
This works great as long as I post to my public facebook website page but when changing the FacebookPageId to a group id instead I get (FacebookApiException - #200) Permissions error.
My user are admin of both the group and the page.
I have tried to post the message from the Graph API Explorer with the following line : 294632750660619/feed/?message=test but there is a syntax problem here, have also tried 294632750660619/feed?message=test but with no success.
How do I post to the closed facebook group?
Okay, I found the correct way. This is what I hade to do :
Go to the https://developers.facebook.com/ and create a new application
Settings > Add platform(Website and set the site URL(for example localhost..)
Set the app to go live(status & review > Yes), to do this a email adress needs to be set under settings > Contact Email
Go to the Graph API Explorer
Choose the new app from the dropdown
Click Get Access Token
Choose correct permissions(user_groups, user_status, user_photos, manage_pages, publish_actions, read_insights and read_stream) and click Get Access Token. Now we bot a short lived tooken
Generate a extended user token (valid for 60 days) by using this URL(change parameters(3)) : https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id=[app-id]&client_secret=[app-secret]&fb_exchange_token=[short-lived-token]
Use the generated none expiring access token in the application
Validate the Access token here : https://developers.facebook.com/tools/debug/access_token/
Use this code to upload post :
public long? UploadPost(string intLinkTitle, string inMessage, string inLinkCaption, string inLinkUrl, string inLinkDescription, string inLinkUrlPicture)
{
object obj;
Facebook.JsonObject jsonObj;
FacebookClient client;
string access_token = ConfigurationManager.AppSettings["FacebookPageAccessToken"].ToString();
client = new FacebookClient(access_token);
var args = new Dictionary<string, object>();
args["message"] = inMessage;
args["caption"] = inLinkCaption;
args["description"] = inLinkDescription;
args["name"] = intLinkTitle;
args["picture"] = inLinkUrlPicture;
args["link"] = inLinkUrl;
if ((obj = client.Post("/" + ConfigurationManager.AppSettings["FacebookPageId"].ToString() + "/feed", args)) != null)
{
if ((jsonObj = obj as Facebook.JsonObject) != null)
{
if (jsonObj.Count > 0)
return long.Parse(jsonObj[0].ToString().Split('_').Last().ToString());
}
}
return null;
}
And to get feed, use this :
private void GetFeed()
{
object obj;
Facebook.JsonObject jsonObj;
Facebook.JsonObject jsonPaging;
FacebookClient client;
int pageCount = 0;
string access_token;
string URL;
DateTime fetchFaceBookFeedFromDate;
DateTime? oldestPostFetched = null;
fetchFaceBookFeedFromDate = DateTime.Now.AddDays(-30);
access_token = ConfigurationManager.AppSettings["FacebookPageAccessToken"].ToString();
URL = "/" + ConfigurationManager.AppSettings["FacebookPageId"].ToString() + "/feed";
client = new FacebookClient(access_token);
while (URL.Length > 0 && pageCount < 1000)
{
if ((obj = client.Get(URL)) != null)
{
if ((jsonObj = obj as Facebook.JsonObject) != null && jsonObj.Count > 0)
{
if (jsonObj[0] is Facebook.JsonArray)
oldestPostFetched = SaveFacebookForumThread(jsonObj[0] as Facebook.JsonArray, fetchFaceBookFeedFromDate);
if (jsonObj.Keys.Contains("paging") && (jsonPaging = jsonObj["paging"] as Facebook.JsonObject) != null && jsonPaging.Keys.Contains("next"))
URL = jsonPaging["next"].ToString();
else
break;
}
}
pageCount++;
if (oldestPostFetched.HasValue && fetchFaceBookFeedFromDate > oldestPostFetched)
break;
}
}

Including SAML2.0 token in WCF service call without using WIF

I'm trying to set up a WCF service protected by ADFS. I'm currently able to request a token and send it with the request using WIF and Thinktecture IdentityModel 4.5 with the following code:
static SecurityToken GetToken()
{
var factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
"https://fs2.server2012.local/adfs/services/trust/13/usernamemixed")
{
TrustVersion = TrustVersion.WSTrust13
};
if (factory.Credentials != null)
{
factory.Credentials.UserName.UserName = #"username";
factory.Credentials.UserName.Password = "password";
}
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
KeyType = KeyTypes.Symmetric,
AppliesTo = new EndpointReference(
"https://wcfservicecertificate/wcfservice/Service.svc/wstrust"),
};
var channel = factory.CreateChannel();
RequestSecurityTokenResponse rstr;
return channel.Issue(rst, out rstr);
}
With this I can call the WCF service by using ChannelFactory.CreateChannelWithIssuedToken:
var factory = new ChannelFactory<IService>(binding,
new EndpointAddress("https://wcfservicecertificate/wcfservice/Service.svc/wstrust"));
if (factory.Credentials != null)
{
factory.Credentials.SupportInteractive = false;
factory.Credentials.UseIdentityConfiguration = true;
}
var proxy = factory.CreateChannelWithIssuedToken(GetToken());
var result= proxy.GetData(2);
This works as expected but can only be used on (mobile) windows platforms. I would also like to be able to use the same principle on iOS and Android. Using this article I was able to request a security token from ADFS using the following code:
const string soapMessage =
#"<s:Envelope xmlns:s=""http://www.w3.org/2003/05/soap-envelope""
xmlns:a=""http://www.w3.org/2005/08/addressing""
xmlns:u=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"">
<s:Header>
<a:Action s:mustUnderstand=""1"">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action>
<a:To s:mustUnderstand=""1"">https://fs2.server2012.local/adfs/services/trust/13/UsernameMixed</a:To>
<o:Security s:mustUnderstand=""1"" xmlns:o=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"">
<o:UsernameToken u:Id=""uuid-6a13a244-dac6-42c1-84c5-cbb345b0c4c4-1"">
<o:Username>username</o:Username>
<o:Password Type=""http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText"">password</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body>
<trust:RequestSecurityToken xmlns:trust=""http://docs.oasis-open.org/ws-sx/ws-trust/200512"">
<wsp:AppliesTo xmlns:wsp=""http://schemas.xmlsoap.org/ws/2004/09/policy"">
<a:EndpointReference>
<a:Address>https://wcfservicecertificate/wcfservice/Service.svc/wstrust</a:Address>
</a:EndpointReference>
</wsp:AppliesTo>
<trust:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</trust:KeyType>
<trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType>
<trust:TokenType>urn:oasis:names:tc:SAML:2.0:assertion</trust:TokenType>
</trust:RequestSecurityToken>
</s:Body>
</s:Envelope>";
var webClient = new WebClient();
webClient.Headers.Add("Content-Type", "application/soap+xml; charset=utf-8");
var result = webClient.UploadString(
address: "https://fs2.server2012.local/adfs/services/trust/13/UsernameMixed",
method: "POST",
data: soapMessage);
This results in a SAML2.0 token which I would like to send in a request to our WCF service in order to authenticate. There are various sources (including the article mentioned earlier) which state that this should be possible but I've yet to find a solution.
Any help would be appreciated.
You can use one of hybrid solutions which use SAML with OAuth or other authorization technologies. This is more secure against phising techniques. For SAML only approach, you can refer to following link: How to pass security tokenfrom one wcf service to another wcf service. It is said that you need to enable saveBootstrapTokens property on webconfig.
This link can be useful too: Availability of Bootstrap Tokens
This can easily be done without using WIF. Lets completely avoid WIF and the .Net framework and do it in Java for illustration purposes. First make a call to the Security Token Service using the template approach like you have done. You then need to extract the SAML from the response, Base64 encode it and stuff it in the Autorization header of the subsequent request to your protected WCF service. You may also need to do the same with a ProofKey if you are coding for Non-Repudiation. Also I'm only showing authentication using username/password for brevity as Certificate Authentication involves much more work - you have to hash (SHA1 )part of the message then encrypt the hash with the private key of the cert and then add this as a xml element to the original message etc...
Here is the java helper code:
import java.io.*;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.Instant;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HttpsURLConnection;
public class SecurityService {
private String _username;
private String _password;
private String _stsUrl;
private String _samlAssertion;
private String _samlEncoded;
private String _binarySecret;
private String _workingDirectory;
private String _platformUrl;
private String _soapBody;
private Integer _responseCode;
private Integer _plaformResponseCode;
private String _response;
private String _platformResponse;
private String _xproofSignature;
private Map<String, String> _headerDictionary;
public void setUsername(String username) {
this._username = username;
}
public void setPassword(String password) {
this._password = password;
}
public void setStsUrl(String stsUrl) {
this._stsUrl = stsUrl;
}
public String getStsUrl() {
return _stsUrl;
}
public void setplatformUrl(String platformUrl) {
this._platformUrl = platformUrl;
}
public String getSamlAssertion() {
return _samlAssertion;
}
public String getSamlEncoded() {
return _samlEncoded;
}
public String getSoapBody() {
return _soapBody;
}
public Integer getResponseCode() {
return _responseCode;
}
public Integer getPlatformResponseCode() {
return _plaformResponseCode;
}
public String getResponse() {
return _response;
}
public String getPlatformResponse() {
return _platformResponse;
}
public String getXProofSignature() {
return _xproofSignature;
}
public String getBinarySecret() {
return _binarySecret;
}
public String gePlatFormUrl() {
return _platformUrl;
}
public void setHeaderDictionary(Map<String, String> headerDictionary){
this._headerDictionary = headerDictionary;
}
public Map<String, String> getHeaderDictionary(){
return _headerDictionary;
}
public SecurityService() throws Exception {
}
public SecurityService(Boolean useConfig) throws Exception {
if (useConfig) {
this._workingDirectory = System.getProperty("user.dir") + "\\app.config";
this.getProperties();
}
}
public void sendAuthenticatedGet() throws Exception {
URL obj = new URL(_platformUrl);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// optional default is GET
con.setRequestMethod("GET");
// Add request header
con.setRequestProperty("Authorization", "Saml " + _samlEncoded);
con.setRequestProperty("X-ProofSignature", _xproofSignature);
_plaformResponseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
_platformResponse = response.toString();
}
public void sendAuthenticatedPost(String body) throws Exception {
URL obj = new URL(_platformUrl);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
//add request header
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/json");
// Add request header
con.setRequestProperty("Authorization", "Saml " + _samlEncoded);
con.setRequestProperty("X-ProofSignature", _xproofSignature);
// Add Azure Subscription Key using generic Add Headers method
if (_headerDictionary != null) {
for (String key : _headerDictionary.keySet()) {
con.setRequestProperty(key, _headerDictionary.get(key));
}
}
_soapBody = body;
// Send post request
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
//wr.writeBytes(urlParameters);
wr.writeBytes(_soapBody);
wr.flush();
wr.close();
_responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
_response = response.toString();
}
// HTTP POST request
public void sendPostToSts() throws Exception {
URL obj = new URL(_stsUrl);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
//add request header
con.setRequestMethod("POST");
con.setRequestProperty("Content-Type", "application/soap+xml");
String body = getTemplateCertificate();
_soapBody = (((body.replace("[Created]", Instant.now().toString())).replace("[Expires]", Instant.now()
.plusSeconds(300).toString())).replace("[username]", _username)).replace("[password]", _password).replace("[stsUrl]", _stsUrl);
// Send post request
con.setDoOutput(true);
DataOutputStream wr = new DataOutputStream(con.getOutputStream());
//wr.writeBytes(urlParameters);
wr.writeBytes(_soapBody);
wr.flush();
wr.close();
_responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
_response = response.toString();
// Get Binary Secret
// <trust:BinarySecret></trust:BinarySecret>
final Pattern patternBinarySecret = Pattern.compile("<trust:BinarySecret>(.+?)</trust:BinarySecret>");
final Matcher matcherBinarySecret = patternBinarySecret.matcher(response.toString());
matcherBinarySecret.find();
_binarySecret = matcherBinarySecret.group(1);
// Get the SAML Assertion
final Pattern patternEncryptedAssertion = Pattern.compile("<trust:RequestedSecurityToken>(.+?)</trust:RequestedSecurityToken>");
final Matcher matcherEncryptedAssertion = patternEncryptedAssertion.matcher(response.toString());
matcherEncryptedAssertion.find();
_samlAssertion = matcherEncryptedAssertion.group(1);
byte[] proofKeyBytes = _binarySecret.getBytes("UTF-8");
String encoded = Base64.getEncoder().encodeToString(proofKeyBytes);
byte[] decoded = Base64.getDecoder().decode(encoded);
// SAML Stuff - Works beautifully
byte[] samlBytes = _samlAssertion.getBytes("UTF-8");
_samlEncoded = Base64.getEncoder().encodeToString(samlBytes);
_xproofSignature = this.encode(_samlAssertion, _binarySecret);
}
private static String readFile( String file ) throws IOException {
BufferedReader reader = new BufferedReader( new FileReader(file));
String line = null;
StringBuilder stringBuilder = new StringBuilder();
String ls = System.getProperty("line.separator");
try {
while( ( line = reader.readLine() ) != null ) {
stringBuilder.append( line );
stringBuilder.append( ls );
}
return stringBuilder.toString();
} finally {
reader.close();
}
}
// Embedded WS-Trust template for username/password RST
private static String getTemplate () {
return "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:a=\"http://www.w3.org/2005/08/addressing\" xmlns:u= \"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\"><s:Header><a:Action s:mustUnderstand= \"1\">http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue</a:Action><a:MessageID>urn:uuid:cfea5555-248c-46c3-9b4d- 54936b7f815c</a:MessageID><a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo><a:To s:mustUnderstand=\"1\">[stsUrl]</a:To><o:Security s:mustUnderstand=\"1\" xmlns:o=\"http://docs.oasis- open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\"><u:Timestamp u:Id=\"_0\"><u:Created>[Created] </u:Created><u:Expires>[Expires]</u:Expires></u:Timestamp><o:UsernameToken u:Id=\"uuid-e273c018-1da7-466e-8671-86f6bfe7ce3c- 17\"><o:Username>[username]</o:Username><o:Password Type=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username- token-profile-1.0#PasswordText\">[password] </o:Password></o:UsernameToken></o:Security></s:Header><s:Body><trust:RequestSecurityToken xmlns:trust=\"http://docs.oasis- open.org/ws-sx/ws-trust/200512\"><wsp:AppliesTo xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy \"><wsa:EndpointReference xmlns:wsa=\"http://www.w3.org/2005/08/addressing \"><wsa:Address>https://mbplatform/</wsa:Address></wsa:EndpointReference></wsp:AppliesTo><trust:RequestType>http://docs.oasis- open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType><trust:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token- profile-1.1#SAMLV2.0</trust:TokenType></trust:RequestSecurityToken></s:Body></s:Envelope>";
}
private String encode(String key, String data) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
return Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(data.getBytes("UTF-8")));
}
private void getProperties() throws Exception {
Properties prop = new Properties();
String fileName = _workingDirectory;
InputStream is = new FileInputStream(fileName);
prop.load(is);
_username = prop.getProperty("app.username");
_password = prop.getProperty("app.password");
_platformUrl = prop.getProperty("app.platformUrl");
_stsUrl = prop.getProperty("app.stsUrl");
}
}
and here is example usage:
SecurityService mbss = new SecurityService(true);
mbss.sendPostToSts();
System.out.println("CONTACTING AZURE SECURITY TOKEN SERVICE");
System.out.println("\nSending 'POST' request to URL : " + mbss.getStsUrl());
System.out.println("\nPost parameters : \n" + mbss.getSoapBody());
System.out.println("\nResponse Code : " + mbss.getResponseCode());
System.out.println("\nHERE IS THE SAML RESPONSE\n");
System.out.println(mbss.getResponse());
System.out.println("\nHERE IS THE BINARY SECRET\n");
System.out.println(mbss.getBinarySecret());
System.out.println("\nHERE IS THE SAML ASSERTION\n");
System.out.println(mbss.getSamlAssertion());
System.out.println("\nHERE IS THE ENCODED SAML ASSERTION\n");
System.out.println(mbss.getSamlEncoded());
System.out.println("\nHERE IS THE X-PROOF SIGNATURE\n");
System.out.println(mbss.getXProofSignature());
System.out.println("\nNOW CONTACTING WCF SERVICES WITH SECURITY HEADER\n");
mbss.sendAuthenticatedGet();
System.out.println("\nSending 'GET' request to URL : " + mbss.gePlatFormUrl());
System.out.println("Response Code : " + mbss.getPlatformResponseCode());
System.out.println("\nHERE ARE THE RESULTS FOLKS...ENJOY\n");
System.out.println(mbss.getPlatformResponse());

Categories