How to get SAML Response and SAML Token in Controller Action method - sustainsys-saml2

I am using this code in CALLback url using SUstainsys.SAaml2 library:
public ActionResult Callback()
{
var samlToken = (Saml2SecurityToken)null;
var samlResponses = HttpContext.Request.Form["SAMLResponse"]; HttpContext.Current.Request.Form["SAMLResponse"]; or elsewhere.
//if (form.Count() > 0)
//{
// var samlResponses = form.GetValues("SAMLResponse");
if (samlResponses != null)
{
foreach (var samlResponse in samlResponses)
{
try
{
var decodedSamlResponse = Convert.FromBase64String(samlResponse.ToString());
var reader = XmlReader.Create(new MemoryStream(decodedSamlResponse));
var serializer = new XmlSerializer(typeof(XmlElement));
var samlResponseElement = (XmlElement)serializer.Deserialize(reader);
var manager = new XmlNamespaceManager(samlResponseElement.OwnerDocument.NameTable);
manager.AddNamespace("saml2", "urn:oasis:names:tc:SAML:2.0:assertion");
var assertion = (XmlElement)samlResponseElement.SelectSingleNode("//saml2:Assertion", manager);
//var samltoken= Options.FromConfiguration.SPOptions.Saml2PSecurityTokenHandler.ReadToken(XmlReader.Create(new StringReader(assertion.OuterXml)));
samlToken = (Saml2SecurityToken)Options.FromConfiguration.SPOptions.Saml2PSecurityTokenHandler.ReadToken(XmlReader.Create(new StringReader(assertion.OuterXml)));
break;
}
catch { }
}
}
ViewBag.SamlResponse = samlResponses;
ViewBag.SamlToken = samlToken;
return View();
}
But I am getting null in SAML Response.

That functionality is built into the AcsCommand, you shouldn't do it yourself.
Look at the samples in the repo and use the HttpModule, Mvc Controller or Owin middleware (depending on what kind of application you have).

Related

Xamarin Essentials Unable to exchange Okta authorization code for token

I was using OpenID and we have to switch to Xamarin.Essentials.WebAuthenticator.
I can get an authorization code from Okta using WebAuthenticator.AuthenticateAsync().
But, everything I try to then translate that code into an access token returns 400 Bad Request.
Okta's API error is "E0000021: HTTP media type not supported exception" and it goes on to say, "Bad request. Accept and/or Content-Type headers likely do not match supported values."
I have tried to follow https://developer.okta.com/blog/2020/07/31/xamarin-essentials-webauthenticator as much as possible, but we are not using the hybrid grant type like he is.
We are using only Authorization Code, which means I have to make a secondary call, and I have spent two days trying to figure out how.
private async Task LoginOktaAsync()
{
try
{
var loginUrl = new Uri(BuildAuthenticationUrl()); // that method is down below
var callbackUrl = new Uri("com.oktapreview.dev-999999:/callback"); // it's not really 999999
var authenticationResult = await Xamarin.Essentials.WebAuthenticator.AuthenticateAsync(loginUrl, callbackUrl);
string authCode;
authenticationResult.Properties.TryGetValue("code",out authCode);
// Everything works fine up to this point. I get the authorization code.
var url = $"https://dev-999999.oktapreview.com/oauth2/default/v1/token"
+"?grant_type=authorization_code"
+$"&code={authCode}&client_id={OktaConfiguration.ClientId}&code_verifier={codeVerifier}";
var request = new HttpRequestMessage(HttpMethod.Post, url);
var client = new HttpClient();
var response = await client.SendAsync(request); // this generates the 400 error.
}
catch(Exception e)
{
Debug.WriteLine($"Error: {e.Message}");
}
}
Here are the methods that produce the login url and a couple of other things:
public string BuildAuthenticationUrl()
{
var state = CreateCryptoGuid();
var nonce = CreateCryptoGuid();
CreateCodeChallenge();
var url = $"https://dev-999999.oktapreview.com/oauth2/default/v1/authorize?response_type=code"
+ "&response_mode=fragment"
+ "&scope=openid%20profile%20email"
+ "&redirect_uri=com.oktapreview.dev-999999:/callback"
+$"&client_id={OktaConfiguration.ClientId}"
+$"&state={state}"
+$"&code_challenge={codeChallenge}"
+ "&code_challenge_method=S256"
+$"&nonce={nonce}";
return url;
}
private string CreateCryptoGuid()
{
using (var generator = RandomNumberGenerator.Create())
{
var bytes = new byte[16];
generator.GetBytes(bytes);
return new Guid(bytes).ToString("N");
}
}
private string CreateCodeChallenge()
{
codeChallenge = GenerateCodeToVerify();
codeVerifier = codeChallenge;
using (var sha256 = SHA256.Create())
{
var codeChallengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeChallenge));
return Convert.ToBase64String(codeChallengeBytes);
}
}
private string GenerateCodeToVerify()
{
var str = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
Random rnd = new Random();
for (var i = 0; i < 100; i++)
{
str += possible.Substring(rnd.Next(0,possible.Length-1),1);
}
return str;
}
'''
After much online research, I discovered the issue was with how I was doing my post to get the token. This is how I made it work:
public static Dictionary<string, string> JsonDecode(string encodedString)
{
var inputs = new Dictionary<string, string>();
var json = JValue.Parse(encodedString) as JObject;
foreach (KeyValuePair<string, JToken> kv in json)
{
if (kv.Value is JValue v)
{
if (v.Type != JTokenType.String)
inputs[kv.Key] = v.ToString();
else
inputs[kv.Key] = (string)v;
}
}
return inputs;
}
private async Task<string> ExchangeAuthCodeForToken(string authCode)
{
string accessToken = string.Empty;
List<KeyValuePair<string, string>> kvdata = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", authCode),
new KeyValuePair<string, string>("redirect_uri", OktaConfiguration.Callback),
new KeyValuePair<string, string>("client_id", OktaConfiguration.ClientId),
new KeyValuePair<string, string>("code_verifier", codeVerifier)
};
var content = new FormUrlEncodedContent(kvdata);
var request = new HttpRequestMessage(HttpMethod.Post, OktaConfiguration.TokenUrl)
{Content = content, Method = HttpMethod.Post};
HttpClient client = new HttpClient();
HttpResponseMessage response = await client.SendAsync(request);
string text = await response.Content.ReadAsStringAsync();
Dictionary<string, string> data = JsonDecode(text);
data.TryGetValue("access_token", out accessToken);
return accessToken;
}

Download mp3 file at browser through API .net core

Was trying to download an mp3 file in a browser through the API that I created. But instead of receiving an mp3 file. I keep getting JSON format response. I had referred from answer in return-file-in-asp-net-core-web-api, but still, I can't download the mp3 file.
Is there any mistake that I've overlooked, please kindly help?
This is my downloading method from UI
void DownloadRecording(RecordingHistory voicehistory)
{
try
{
using (var client = new WebClient())
{
client.DownloadFile("https://2d489fd863a2.ngrok.io/api/download/" + voicehistory.RecordingId + ".mp3", voicehistory.RecordingId + ".mp3");
}
}
catch { }
}
This is my api function for downloading mp3 from server
[HttpGet("download/{recordingFile}")]
public async Task<IActionResult> DownloadVoiceRecording(string recordingFile)
{
string filePath = Directory.GetCurrentDirectory() + #"\audio\Processed\" + recordingFile;
var memory = new MemoryStream();
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
var types = GetMimeTypes();
var ext = Path.GetExtension(filePath).ToLowerInvariant();
return File(filePath, types[ext], recordingFile);
}
private Dictionary<string, string> GetMimeTypes()
{
return new Dictionary<string, string>
{
{".mp3", "audio/mpeg"},
{".wav","audio/wav" }
};
}
This is the response I get from browser and Postman
{
"Version": "2.0.0.0",
"StatusCode": 200,
"Message": "Status 200 OK",
"Result":"��#� ... ... /// A lot of random symbol here
}
Because the first parameter of the return value File is a type of Stream, memory needs to be passed in.
[HttpGet("download/{recordingFile}")]
public async Task<IActionResult> DownloadVoiceRecording(string recordingFile)
{
string filePath = Directory.GetCurrentDirectory() + #"\audio\Processed\" + recordingFile;
var memory = new MemoryStream();
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
var types = GetMimeTypes();
var ext = Path.GetExtension(filePath).ToLowerInvariant();
return File(memory, types[ext], recordingFile);
}
I'm using Blazor for this. It turns out that there was an API response wrapper in Blazor APIReponse middleware. I had to put my API into an exception so it won't turn into JSON when I access it. It works finally.
Below is the APIReponse wrapper in Blazor.
var formattedRequest = await FormatRequest(request);
var originalBodyStream = httpContext.Response.Body;
using (var responseBody = new MemoryStream())
{
try
{
string responseBodyContent = null;
var response = httpContext.Response;
if (new string[] { "/api/localization", "/api/data", "/api/externalauth", "/api/download" }.Any(e => request.Path.StartsWithSegments(new PathString(e.ToLower()))))
await _next.Invoke(httpContext);
else
{
response.Body = responseBody;
await _next.Invoke(httpContext);
//wrap response in ApiResponse
if (httpContext.Response.StatusCode == Status200OK)
{
responseBodyContent = await FormatResponse(response);
await HandleSuccessRequestAsync(httpContext, responseBodyContent, Status200OK);
}
else
await HandleNotSuccessRequestAsync(httpContext, httpContext.Response.StatusCode);
}

How to post JSON data to Pardot API via Httpclient

I am trying to post the JSON data to Pardot. I have used the info from here to call the Pardot API and currently using Pardot form handler to post the data. I want to know if i could the data via Pardot API call by using CREATE or UPSERT instead of using a form handler.
Below is my code
class SendingDataToPardot
{
public string Login()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
var url = "https://pi.pardot.com/api/login/version/3";
string apiKey = null;
var loginInfo = new Dictionary<string, string>
{
{"email", "xx"},
{"password", "xxx"},
{"user_key", "xxx"}
};
var httpContent = new FormUrlEncodedContent(loginInfo);
using (var client = new HttpClient())
{
HttpResponseMessage response = client.PostAsync(url, httpContent).Result;
if (response.IsSuccessStatusCode)
{
string resultValue = response.Content.ReadAsStringAsync().Result;
apiKey = XDocument.Parse(resultValue).Element("rsp").Element("api_key").Value;
return apiKey;
}
else
{
return null;
}
}
}
public string POST()
{
string Api_Key = Login();
var url = "form handler url";
var contactFormData = new Dictionary<string, string>
{
{"email", "test#test.com"},
{"FirstName", "xxx"},
{"LastName", "xxxxx"},
{"Comments", "this is a test"}
};
var data= new FormUrlEncodedContent(contactFormData);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Api_Key);
HttpResponseMessage response = client.PostAsync(url, data).Result;
string result = response.Content.ReadAsStringAsync().Result;
return result;
}
}
}
}
For most of the APIs Pardot exposes, you need to do XML work with it.
Looks like you are using Java, so you might have luck using a public library, even if just for understanding communication patterns (we had to rewrite it for our purposes, but it did serve as a great blueprint).
Have a look at the https://github.com/Crim/pardot-java-client project and see if it helps you out.

ASP MVC RouteConfig with authentication return error repository not found

I tried to make an authorization in my asp with my git client, so my git client will be requested an authorization from my server. When i tried to send a request to my git client, it was showing an error
repository http://localhost/git/user/try.git/info/ref not found
Here is my routeconfig
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
#region remoteURL
routes.MapRoute(
name: "RemoteURL",
url: "git/{project}.git/{*verb}",
defaults: new { controller = "Git", action = "Smart" }
);
routes.MapRoute(
name: "Git",
url: "git/{project}/{*verb}",
defaults: new { controller = "Git", action = "Smart" }
);
#endregion
#region Account;
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
#endregion;
}
and this is my controller that use an attribute :
public class GitController : Controller
{
[SmartGit]
public ActionResult Smart(string project, string service, string verb)
{
switch (verb)
{
case "info/refs":
return InfoRefs(project, service);
case "git-upload-pack":
return ExecutePack(project, "git-upload-pack");
case "git-receive-pack":
return ExecutePack(project, "git-receive-pack");
default:
return RedirectToAction("Tree", "Repository", new { Name = project });
}
}
and then this is my attribute smartgit
public class SmartGitAttribute : SmartAuthorizeAttribute
{
private const string AuthKey = "GitCodeGitAuthorize";
private GitCodeContext db = new GitCodeContext();
private string project;
private string verb;
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
var right = false;
var userfound = false;
List<string> paramParsing = new List<string>();
//url.Split("")
//base.OnAuthorization(filterContext);
var controller = filterContext.Controller as GitController;
if (controller == null)
return;
// git.exe not accept cookies as well as no session available
var auth = controller.HttpContext.Request.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var bytes = Convert.FromBase64String(auth.Substring(6));
var certificate = Encoding.ASCII.GetString(bytes);
var index = certificate.IndexOf(':');
var password = certificate.Substring(index + 1);
var username = certificate.Substring(0, index);
//var user = controller.MembershipService.Login(username, password);
if (WebSecurity.Login(username, password))
{
WebSecurity.Login(username, password);
userfound = true;
}
}
var projectField = controller.ValueProvider.GetValue("project");
var serviceField = controller.ValueProvider.GetValue("service");
var verbField = controller.ValueProvider.GetValue("service");
//filterContext.Controller.ValueProvider
var project = projectField == null ? null : projectField.AttemptedValue;
var service = serviceField == null ? null : serviceField.AttemptedValue;
var verb = verbField == null ? null : serviceField.AttemptedValue;
if (string.IsNullOrEmpty(service) && userfound) // redirect to git browser
{
right = true;
}
else if (string.Equals(service, "git-receive-pack", StringComparison.OrdinalIgnoreCase) && userfound) // git push
{
//right = controller.RepositoryService.CanWriteRepository(project, username);
right = true;
}
else if (string.Equals(service, "git-upload-pack", StringComparison.OrdinalIgnoreCase) && userfound ) // git fetch
{
//right = controller.RepositoryService.CanReadRepository(project, username);
right = true;
}
if (!userfound)
{
if (WebSecurity.CurrentUserName == "")
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", "Basic realm=\"coba\"");
filterContext.Result = new HttpUnauthorizedResult();
}
else
{
throw new UnauthorizedAccessException();
}
}
}
I found my own mistake, maybe my response doesn't have enough information so i decide to add a few information in my SmartGitAttribute
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusDescription = "Unauthorized";
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", "Basic realm=\"Secure Area\"");
filterContext.HttpContext.Response.Write("401, please authenticate");
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.Result = new EmptyResult();
filterContext.HttpContext.Response.End();
this is reference that can help you to solve response authentication

How to decode SessionSecurityToken

Is it possible to decode a SessionSecurityToken?
I've set up a site to work with ThinkTecture IdentityServer using MachineKeySessionSecurityTokenHandler, and everything works as expected.
But now I need to pass the token to another service, but in an Authorization HTTP header instead of a cookie.
I've tried the following:
var cookie = HttpContext.Current.Request.Cookies[FederatedAuthentication.FederationConfiguration.CookieHandler.Name];
if (cookie != null)
{
var t = MachineKey.Unprotect(Convert.FromBase64String(cookie.Value), "System.IdentityModel.Services.MachineKeyTransform");
}
but this throws a System.Security.Cryptography.CryptographicException
Found the solution.
The only (easy) way of sending the data across the wire is to convert the SessionSecurityToken to a JwtSecurityToken and use the RawData property.
Sample implementation (dependent on ThinkTecture.IdentityModel):
public JwtSecurityToken ConvertSessionToJsonWebSecurityToken(SessionSecurityToken sessionToken)
{
var h = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.SecurityTokenHandlers[typeof(JwtSecurityToken)] as JwtSecurityTokenHandler;
if (h != null)
{
var issuer = ((ValidatingIssuerNameRegistry)h.Configuration.IssuerNameRegistry).IssuingAuthorities.First().Name;
var audience = h.Configuration.AudienceRestriction.AllowedAudienceUris.First().AbsoluteUri;
var signingKey = ((ValidatingIssuerNameRegistry)h.Configuration.IssuerNameRegistry).IssuingAuthorities.First().SymmetricKeys.First();
var securityKey = ((NamedKeyIssuerTokenResolver)h.Configuration.IssuerTokenResolver).SecurityKeys.First().Value.First();
// Create token
var t = h.CreateToken(
null,
null,
(ClaimsIdentity)sessionToken.ClaimsPrincipal.Identity,
new Lifetime(sessionToken.ValidFrom, sessionToken.ValidTo),
new SigningCredentials(
securityKey,
Algorithms.HmacSha256Signature,
Algorithms.Sha256Digest));
// Serialize token for validaiton
var s = h.WriteToken(t);
// Validate token
var validationParameters = new TokenValidationParameters()
{
AllowedAudience = audience,
ValidIssuer = issuer,
SigningToken = new BinarySecretSecurityToken(Convert.FromBase64String(signingKey))
};
h.ValidateToken(s, validationParameters);
// Return token with correct type
return h.ReadToken(s) as JwtSecurityToken;
}
return null;
}
[Test]
public void GetToken_WhenValidSessionTokenExist_ShouldReturnValidJwtToken()
{
JwtSecurityToken c;
FederatedAuthentication.SessionAuthenticationModule.TryReadJwtTokenFromCookie(container.GetInstance<ISecurityTokenOperations>(), out c)
Assert.That(!string.IsNullOrEmpty(c.RawData));
}