I have a website where I created a custom cookie and I am trying to read the cookie value inside my Custom Rewrite Provider running in IIS
Question in short: How to decrypt the cookie inside custom URL rewrite provider?
Below is the code for creating custom cookie
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1,
model.Email,
DateTime.Now,
DateTime.Now.AddDays(7),
true,
"deepak",
FormsAuthentication.FormsCookiePath);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie fCookie = new HttpCookie("customCookie", encryptedTicket);
fCookie.Expires = DateTime.Now.AddDays(7);
fCookie.Path = "/";
Response.Cookies.Add(fCookie);
Below code is to read the cookie value inside my Custom Rewrite Provider running in IIS
public class ParseUserNameProvider : IRewriteProvider, IProviderDescriptor
{
public IEnumerable<SettingDescriptor> GetSettings()
{
throw new NotImplementedException();
}
public void Initialize(IDictionary<string, string> settings, IRewriteContext rewriteContext)
{}
public string Rewrite(string value)
{
string[] val = value.Split('=');
string name = "";
if (val != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(val[1]);
if(authTicket!=null)
{
name = authTicket.Name;
}
}
return name;
}
}
Error raised as shown below
Rewrite Settings in IIS - InBound
Redirect URL
http://1x2.xx.1x8.x8:1111/Report/Report?name={ParseUserNameProvider:{C:0}}
Conditions
I learned this from : http://www.iis.net/learn/extensions/url-rewrite-module/developing-a-custom-rewrite-provider-for-url-rewrite-module
Note: This post is NOT duplicate of Custom Rewrite Provider for URL Rewrite Module because I am getting different error.
If there are any other Cookies with that domain they'll be included as part of val[1] in one long string. I had considerable difficulty making IIS reliably pass through just one cookie so I pulled though all the cookies into val[1] string and then split that into an array of all the cookie values then just selected the one I needed. If in doubt get your provider to out put the val[1] string as a customer error so you can see what it's seeing.
throw new Exception(val[1]);
Once you can see what's actually being received you work out how you need to split it.
Related
So, I have an MVC5 site that uses the default routing template {controller}/{action}/{id} and this works fine. Most everything in the site requires a login (i.e. [Authorize] attribute is used almost everywhere), and this works fine.
Well, now I have a need to allow anonymous access to select pages when a certain kind of link pattern is used: App/{token}/{action}. The {token} is a random string associated with something in my database. I can issue and deactivate these tokens at will.
I got this new App/{token}/{action} routing working by implementing a custom RouteBase that parses the incoming URL for these tokens, and, crucially, adds the the token value to the RouteData.DataTokens so that my App controller can make use of it without needing an explicit action argument for it. So, I added this new route to the route table ahead of the default routing like this:
// new route here
routes.Add("AppToken", new AnonAppAccessRoute());
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Here is the problem/question: adding this now route has now made my default route stop working -- everything is now going through AnonAppAccessRoute which of course is meant to work only for a few things. I don't understand how to make my AnonAppAccessRoute apply only to URLs with a certain pattern. The MapRoute method accepts a URL pattern, but Adding a route doesn't seem to let you put a filter on it. What am I missing? I've looked around quite a bit at various blogs and documentation about routing, but I've not found good info about using the DataTokens collection (which I feel is important to my approach), and I'm not seeing a good explanation of the difference between Adding a route explicitly vs calling MapRoute.
Here's the code of my custom RouteBase:
public class AnonAppAccessRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
string[] pathElements = httpContext.Request.Path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
if (pathElements.Length > 0)
{
string token = TryGetArrayElement(pathElements, 1);
if (!string.IsNullOrEmpty(token))
{
result = new RouteData(this, new MvcRouteHandler());
result.DataTokens.Add("appToken", token);
result.Values.Add("controller", "App");
result.Values.Add("action", TryGetArrayElement(pathElements, 2, "Index"));
}
}
return result;
}
private string TryGetArrayElement(string[] array, int index, string defaultValue = null)
{
try
{
return array[index];
}
catch
{
return defaultValue;
}
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}
I got this to work by dropping the custom RouteBase and instead used this MapRoute call like this:
routes.MapRoute(
name: "AppAnon",
url: "App/{token}/{action}",
defaults: new { controller = "App", action = "Index" }
);
Then, in my App controller, I did this in the Initialize override:
protected AppToken _appToken = null;
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
string token = requestContext.RouteData.Values["token"]?.ToString();
_appToken = Db.FindWhere<AppToken>("[Token]=#token", new { token });
if (!_appToken?.IsActive ?? false) throw new Exception("The token is not found or inactive.");
}
This way, my "token" is available to all controller actions via the _appToken variable, and already validated. I did not need to use RouteData.DataTokens. Note that my Db.FindWhere statement is ORM-specific and not really related to the question -- it's just how I look up a database record.
I'm trying to get the edit URL of a content as a string from backend, the catch is I'm inside a workflow activity, so I can't use Url.Action... or Url.ItemEditLink... or other UrlHelpers as if it were a controller or a view. Also, although I'm inside a workflow, the contents I need it for are not part of the workflowContext or the activityContext, so I can't use those or tokens either.
A solution could be to get the content metadata and the site baseUrl and try to build it manually, but I think this way is prone to errors.
Thanks.
This is how I build a Uri in an activity:
public class MyClass : Task
{
private readonly RequestContext _requestContext;
...
public MyActivity(RequestContext requestContext, ...)
{
_requestContext = requestContext;
...
}
...
public override IEnumerable<LocalizedString> Execute(WorkflowContext workflowContext, ActivityContext activityContext)
{
var content = ... get using ID
var helper = new UrlHelper(_requestContext);
var baseurl = new Uri(_orchardServices.WorkContext.CurrentSite.BaseUrl);
Uri completeurl = new Uri(baseurl, helper.ItemDisplayUrl(content));
yield return T("Done");
}
}
Turns out that I actually do build the Uri semi-manually, but I haven't had issues with this method. You may be able to use just the ItemDisplayUrl for navigation inside of Orchard; I had to get the full URL because the string gets sent to an outside program (Slack).
This is my first SO question so please let me know if this question is not very clear or if I am missing anything.
FYI SO prevented me from attaching links, so sorry for all the bad formatting.
Overview
I'm trying to read (and write) the "Actual work" for a resource in Project Server Online by using the CSOM library available by Microsoft. Reading and writing the assignments and Actual work is working perfectly, as long as I am reading the assignments for the currently authenticated user. If I attempt to read this for another resource, I receive a GeneralSecurityAccessDenied error.
I've done this in the past using Impersonation, which is supposed to be called transparently in the background if the user has the StatusBrokerPermission, but it doesn't seem to be working for me. Impersonation has been removed in 2013+, so that's no longer an option.
Problem summary
The CSOM is supposed to transparently enable statusing extensions to allow status updates to be made for resources other than the currently authenticated user (as long as the user has the status broker permission). This works fine for adding new assignments, but does not work when trying to update actual TimePhased hours via the TimePhased assignments. The assignments cannot be queried, and thus, we cannot call SubmitAllStatusUpdates to submit the hours.
Research
Usage scenarios for the CSOM: https:// msdn.microsoft.com/en-us/library/office/jj163082(v=office.15).aspx#pj15_WhatTheCSOM_UsageScenarios
Impersonation Deprecated: https:// msdn.microsoft.com/en-us/library/office/ee767690(v=office.15).aspx#pj15_WhatsNew_Deprecated)
Picture: Supposed to read on behalf of another user...
People with the same problem # 1: https:// social.technet.microsoft.com/Forums/projectserver/en-US/dccdb543-18a1-4a0e-a948-5d861305516e/how-to-get-resource-assignments-summary-view-data-project-server-online-2013?forum=projectonline)
People with the same problem # 2: http:// uzzai.com/ZB43wp95/ps2013-app-how-to-read-and-update-timephased-data-with-jsom-javascript-csom.html
People with the same problem # 4: https:// social.technet.microsoft.com/Forums/Sharepoint/en-US/be27d497-e959-44b6-97cb-8f19fe0278fe/csom-how-to-set-timephase-data-on-an-assignment?forum=project2010custprog
Other things I've tried
Using the CSOM with the MsOnlineClaimsHelper to retrieve the FedAuth cookies for a user (and assigning them using the CookieContainer).
Using the REST/OData API.
a) https:// URL.sharepoint.com/sites/pwa/_api/ProjectServer/EnterpriseResources('c39ba8f1-00fe-e311-8894-00155da45f0e')/Assignments/GetTimePhaseByUrl(start='2014-12-09',end='2014-12-09')/assignments
Enabling the "StatusBrokerPermission" for the user
Unchecking the “Only allow task updates via Tasks and Timesheets.” Option within the server settings screen (Task settings and display).
Creating a SharePoint-hosted app and using JSOM code equivalent to the CSOM code above.
a) The code we wrote was JavaScript being executed from within SharePoint app, so we did not need to provide authentication. The user who was logged in had the StatusBrokerPermission.
Using a Provider-hosted SharePoint app and using the CSOM code above. We tried using all authentication methods for CSOM above, with an additional test:
a) using Fiddler to view the FedAuth cookies being set by the SharePoint app authentication, and overriding the WebRequest to manually insert the FedAuth/rtFA cookies: webRequestEventArgs.WebRequestExecutor.WebRequest.CookieContainer = getStaticCookieContainer();
Using timesheets to submit time phased data.
a) We can only create a timesheet for the currently-authenticated user, and cannot populate timesheet lines with projects / assignments not available to him (or a GeneralItemDoesNotExist error is thrown).
Manually issuing a “SubmitAllStatusUpdates” CSOM request using fiddler, as a different user.
a) The purpose of this test was to determine if we can write time phased data, even if we can’t read it.
Making sure the project was checked out to the current user.
Using administrative delegation for a resource.
Setting all available options within project permissions.
Using the Project Web UI to enter the TimePhased data for other resources.
Using SharePoint permission mode instead of Project Permission Mode.
The code
See failing code screenshot here
using System;
using System.Security;
using Microsoft.ProjectServer.Client;
using Microsoft.SharePoint.Client;
namespace ProjectOnlineActuals
{
static class Program
{
const string projectSite = "https://URL.sharepoint.com/sites/pwa/";
private const string edward = "c39ba8f1-00fe-e311-8894-00155da45f0e";
private const string admin = "8b1bcfa4-1b7f-e411-af75-00155da4630b";
static void Main(string[] args)
{
TestActuals();
}
private static void TestActuals()
{
Console.WriteLine("Attempting test # 1 (login: admin, resource: admin)");
TestActuals("admin#URL.onmicrosoft.com", "123", admin);
Console.WriteLine("Attempting test # 2 (login: admin, resource: edward)");
TestActuals("adminy#hmssoftware.onmicrosoft.com", "123", edward);
Console.ReadLine();
}
private static void TestActuals(string username, string password, string resourceID)
{
try
{
using (ProjectContext context = new ProjectContext(projectSite))
{
DateTime startDate = DateTime.Now.Date;
DateTime endDate = DateTime.Now.Date;
Login(context, username, password);
context.Load(context.Web); // Query for Web
context.ExecuteQuery(); // Execute
Guid gResourceId = new Guid(resourceID);
EnterpriseResource enterpriseResource = context.EnterpriseResources.GetByGuid(gResourceId);
context.Load(enterpriseResource, p => p.Name, p => p.Assignments, p => p.Email);
Console.Write("Loading resource...");
context.ExecuteQuery();
Console.WriteLine("done! {0}".FormatWith(enterpriseResource.Name));
Console.Write("Adding new resource assignment to collection...");
enterpriseResource.Assignments.Add(new StatusAssignmentCreationInformation
{
Comment = "testing comment - 2016-02-17",
ProjectId = new Guid("27bf182c-2339-e411-8e76-78e3b5af0525"),
Task = new StatusTaskCreationInformation
{
Start = DateTime.Now,
Finish = DateTime.Now.AddDays(2),
Name = "testing - 2016-02-17",
}
});
Console.WriteLine("done!");
Console.Write("Trying to save new resource assignment...");
enterpriseResource.Assignments.Update();
context.ExecuteQuery();
Console.WriteLine("done!");
Console.Write("Loading TimePhase...");
TimePhase timePhase = enterpriseResource.Assignments.GetTimePhase(startDate.Date, endDate.Date);
context.ExecuteQuery();
Console.WriteLine("done!");
Console.Write("Loading TimePhase assignments...");
context.Load(timePhase.Assignments);
context.ExecuteQuery();
Console.WriteLine("done! Found {0} assignments.".FormatWith(timePhase.Assignments.Count));
Console.WriteLine("Updating TimePhase assignments...");
foreach (var assignment in timePhase.Assignments)
{
Console.WriteLine("Updating assignment: {0}. ActualWork: {1}".FormatWith(assignment.Name, assignment.ActualWork));
assignment.ActualWork = "9h";
assignment.RegularWork = "3h";
assignment.RemainingWork = "0h";
}
timePhase.Assignments.SubmitAllStatusUpdates("Status update comment test 2016-02-17");
context.ExecuteQuery();
Console.WriteLine("done!");
Console.WriteLine("Success (retrieved & updated {0} time phase assignments)!".FormatWith(timePhase.Assignments.Count));
}
}
catch (Exception ex)
{
if (ex.ToString().Contains("GeneralSecurityAccessDenied"))
Console.WriteLine("ERROR! - GeneralSecurityAccessDenied");
else
throw;
}
finally
{
Console.WriteLine();
Console.WriteLine();
}
}
private static void Login(ProjectContext projContext, string username, string password)
{
var securePassword = new SecureString();
foreach (char c in password)
securePassword.AppendChar(c);
projContext.Credentials = new SharePointOnlineCredentials(username, securePassword);
}
static string FormatWith(this string str, params object[] args)
{
return String.Format(str, args);
}
}
}
Can anyone help??
This question already has answers here:
Create route for root path, '/', with ServiceStack
(3 answers)
Closed 3 years ago.
I've got a Fallback DTO that looks like the following:
[FallbackRoute("/{Path*}")]
public class Fallback
{
public string Path { get; set; }
}
Now, in my Service I would like to redirect to an HTML5 compliant URL, and this is what I've tried:
public object Get(Fallback fallback)
{
return this.Redirect("/#!/" + fallback.Path);
}
It is working all fine and dandy, except for the fact that query parameters are not passed along with the path. Using Request.QueryString does not work as no matter what I do it is empty. Here's what my current (non-working) solution looks like:
public object Get(Fallback fallback)
{
StringBuilder sb = new StringBuilder("?");
foreach (KeyValuePair<string, string> item in Request.QueryString)
{
sb.Append(item.Key).Append("=").Append(item.Value).Append("&");
}
var s = "/#!/" + fallback.Path + sb.ToString();
return this.Redirect(s);
}
TL;DR: I want to pass on query strings along with fallback path.
EDIT: It turns out I had two problems; now going to mysite.com/url/that/does/not/exist?random=param correctly redirects the request to mysite.com/#!/url/that/does/not/exist?random=param& after I changed the above loop to:
foreach (string key in Request.QueryString)
{
sb.Append(key).Append("=").Append(Request.QueryString[key]).Append("&");
}
But the fallback is still not being called at root, meaning mysite.com/?random=param won't trigger anything.
In essence, what I want to do is to have ServiceStack look for query strings at root, e.g., mysite.com/?key=value, apply some logic and then fire off a redirect. The purpose of this is in order for crawler bots to be able to query the site with a _escaped_fragment_ parameter and then be presented with an HTML snapshot prepared by a server. This is in order for the bots to be able to index single-page applications (more on this).
I'm thinking perhaps the FallbackRoute function won't cover this and I need to resort to overriding the CatchAllHandler.
I managed to find a solution thanks to this answer.
First create an EndpointHostConfig object in your AppHost:
var config = new EndpointHostConfig
{
...
};
Then, add a RawHttpHandler:
config.RawHttpHandlers.Add(r =>
{
var crawl = r.QueryString["_escaped_fragment_"];
if (crawl != null)
{
HttpContext.Current.RewritePath("/location_of_snapshots/" + crawl);
}
return null;
});
Going to mysite.com/?_escaped_fragment_=home?key=value will fire off a redirection to mysite.com/location_of_snapshots/home?key=value, which should satisfy the AJAX crawling bots.
N.B. It's possible some logic needs to be applied to the redirection to ensure that there won't be double forward slashes. I have yet to test that.
I need to check a .config file in an installation application (iis custom action in a WIX project). The user picks a website and enters a virtual directory name. I can't do a http read to retrieve the config file as ASP.NET does not serve config files.
How can I find the local disk path of the selected website and virtual directory?
After further searching, I ended up using DirectoryServices. I'm posting my solution here for others.
If there is a better way, please still post it.
static string FindVirtualDirectoryPath(string virtualDirectoryName)
{
return FindVirtualDirectoryPath(null, virtualDirectoryName);
}
static string FindVirtualDirectoryPath(string siteName, string virtualDirectoryName)
{
DirectoryEntry iis = new DirectoryEntry("IIS://localhost/W3SVC");
foreach (DirectoryEntry index in iis.Children)
{
if (index.SchemaClassName == "IIsWebServer")
{
int id = Convert.ToInt32(index.Name);
DirectoryEntry site = new DirectoryEntry(string.Concat("IIS://localhost/W3SVC/", id));
string iSiteName = site.Properties["ServerComment"].Value.ToString();
if (iSiteName == siteName || (string.IsNullOrEmpty(siteName) && id == 1))
{
DirectoryEntry rootVDir = new DirectoryEntry(string.Concat("IIS://localhost/W3SVC/", id, "/Root"));
foreach (DirectoryEntry vDir in rootVDir.Children)
{
if (vDir.SchemaClassName == "IIsWebVirtualDir" && vDir.Name.ToLower() == virtualDirectoryName.ToLower())
{
return vDir.Properties["Path"].Value.ToString();
}
}
}
}
}
return null;
}
Have you tried using the standard WiX IIsExtension for this? Just capture the user input in a property, and then use standard elements like iis:WebSite and iis:WebVirtualDir to create a virtual directory in appropriate web site.