I have implemented Static Publisher on my site.
I have a page that is set in the CMS to only be viewed by certain groups.
Prior to installing StaticPublisher, this worked fine - when a user visited the protected page in the browser, they were redirected to Security/Login, the Member Login Form was displayed and upon authentication they were sent on their way.
With StaticPublisher installed, when the user hits the protected page, they are redirected to Security/Login, but no form is displayed - only a notice notifying them what name the user is logged in as.
Is it possible to have password protected pages using StaticPublisher? Do I need to configure Static Publisher in a certain way to allow password protected pages?
Static publisher will not vary content based on a Member's access level (or much else). Since it saves pages as static HTML, the SilverStripe backend is never accessed for the HTTP request, and instead the raw HTML page is served to the user.
You can exclude certain pages and page types from static publishing however. If you don't need caching for this specific page or section, this might be your best option.
From the docs:
public function allPagesToCache() {
$urls = array();
$pages = SiteTree::get();
// ignored page types
$ignored = array('UserDefinedForm');
foreach($pages as $page) {
// check to make sure this page is not in the classname
if(!in_array($page->ClassName, $ignored)) {
$urls = array_merge($urls, (array)$page->subPagesToCache());
}
}
return $urls;
}
Alternatively, you can use htaccess rules to require HTTP Basic Authentication to access certain cached pages. This will operate outside of SilverStripe's authentication/authorization system though (and you'll have to manually define usernames and passwords in a .htpasswd file).
Related
I use keycloak (OAuth2/Open ID Connect) to authorize and authenticate users in a Blazor Server Side application. Furthermore I use the services.AddAuthentication().AddOpenIdConnect() extension from Microsoft.
After successfull login I want to redirect the user to a certain page based on its claims. So f.e. if the user has the claim "gis" he should be redirected to "/gis" or if he has the claim "claim123" he should be redirected to page "/page456".
Right now I solve this problem by overriding OnInitialized in the MainLayout.razor and look if the user has a certain claim and then redirect him with the help of the NavigationManager.
#inject NavigationManager navMan;
//...
protected override void OnInitialized()
{
if (_user.HasClaim(t => t.Type == "claim123"))
{
navMan.NavigateTo("/page456");
}
}
The problem with this approach is, that it will first load the default index home page ( "/" ) and then after a short period of time will reload to the new destination. This "flickering" is not very nice and I thought there must be a better way to directly redirect after successful "/openid-connect" redirection. Can I may use some kind of middleware or one of the OpenIdConnectEvents?
I have an APP using restful server. I want to store PDF's, images, etc. in folders on my server. How can I make the folders private on server, yet allow App to access only certain folders depending on their app access.
I have different users in app and security/tokens established, etc. But if they upload an image for their avatar (and now PDF's), they get stored in folders on the server, and I just display with image source=https://blahblah.com/org1/images/user232.jpg.
How can I make that not accessible to outside (like just going to browser), yet make accessible to app if they have correct login privilege's for that organization/user? And then further extend that logic to more sensative PDF's, and other docs uploaded through app. I didn't want to store in SQL since then harder to use simple image display tools and I already have upload and media managers using folders structures.
I can see how to secure if logging onto server through browser (credentials), but can't see how you connect App with that security level and maintain it for the session.
For future readers. Most of the work was done on the restful (ASP.NET) side. I first tried using authorization/Authentication in web.config and having Allow and deny. This allowed a redirect of a user to a login page; however, it didn't do it if they entered an image exactly correct on website.
Found HTTPHandlers (adding in webconfig ) where I could write code that would be executed once the user entered the specific Image address xyz/abc/image.png. I found this a bit feeling like a hack.
So lastly modified my
routes.MapRoute(
name: "staticFileRoute",
url: "publicstor/{*file}",
defaults: new { controller = "Home", action = "HandleStatic" }
And add a function like this to home controller.
[System.Web.Http.HttpGet]
public ActionResult HandleStatic(string file)
{
if (Session["OrgId"] == null) //todo need to add full security check.
{
return View("Login");
}
else //Either coming from app or coming from web interface
{
string mimeType = MimeInfo.GetMimeType(Path.GetExtension(file));
return File(file, mimeType);
}
}
The final bit is on the Xamarin side to now pass security when getting an image. Since just a simple Xamarin.Forms.Image doesn't have a way to pass login info or tokens/authentication I used
https://forums.xamarin.com/discussion/145575/image-from-url-needing-auth
And established an appwide webclient that logged in generally once forcing my restful to go through security validation, then just accessed the images/documents through out my app from that webclient. So far so good. Hopefully there are no holes.
This gives the gist to a future reader.
I have a question on how to programmatically instruct ASP.NET to skip resolving a request from the output cache.
Imagine you've got a page output cached (e.g. http://domain/page.aspx) by means of applying cache policy settings from a CMS to the HttpResponse at runtime. On a per request basis depending on i.e. the current user is authenticated + a member of a set of known groups (or matched by business logic), I would like to instruct ASP.NET to skip resolving a request from the output cache.
The scenario is that two different users are on the system at the same time (or more). User A is authenticated + a member of a set of known groups, and user B is anonymous. Regardless of the page being output cached, I want the authenticated user to browse all pages as if no output caching was enabled - ever; at the same time, I would like ASP.NET to continue serving output cached pages to anonymous users (or, users who isn't matched by business logic).
The typical suggestion is to use VaryByHeader, VaryByParam etc. and pollute the output cache - not good, but while digging in the output cache module using Reflector, I noticed that the output cache module skips the current request in case a couple of known "cache-control" headers are present. As far as I'm concerned about headers, these are sent from the browser if the user forces a fresh copy to be rendered by hitting F5 or ENTER in the address bar.
So, what I'm doing is simply setting the "cache-control" header to "no-cache" in a custom http module in an event that goes before the ResolveRequestCache event to which the output cache subscribes. Like this:
context.Request.Headers["Cache-Control"] = "no-cache";
All is nice and dandy, however, if the HttpCachePolicy.SetValidUntilExpires(true) cache policy is set, ASP.NET disregards the request header previously set and serves the request from the output cache.
As an alternative, I guess I could write additional code in a post-processing event in the same http module to ensure that HttpCachePolicy.SetValidUntilExpires(false) is called, in case output caching has been configured, but I think it would be a more clean solution to actually be able to instruct ASP.NET to simply skip resolving the request from the output cache. I can imagine a lot of ackward solutions to this question, but I'm after the right one.
For reference, I have been trying most if not all relevant methods of the HttpCachePolicy class e.g.:
HttpResponse.Cache.SetNoServerCaching()).
You can add an HttpCacheValidateHandler to your application in which you can implement any logic you want. It will be executed during the ResolveRequestCache event which is fired after Authentication and Authorization have been executed. The key is to return HttpValidationStatus.IgnoreThisRequest in the case where you want to bypass the Cache.
See this sample HttpModule for reference:
public class CacheModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest +=
(s, e) => context.Context.Response.Cache
.AddValidationCallback(CacheHandler, null);
}
private static void CacheHandler(
HttpContext context, object data,
ref HttpValidationStatus validationstatus)
{
// bypass cache for all users with skipCache cookie
validationstatus =
context.Request.Cookies["skipCache"] != null
? HttpValidationStatus.IgnoreThisRequest
: HttpValidationStatus.Valid;
}
public void Dispose()
{
}
}
I'm not sure whether there is a supported method of doing this after something is cached.
But why don't you like the VaryByHeader option? Assuming there is a header you can look at to differentiate between logged in and not logged in users, that should work. It wouldn't pollute the cache if you have logic to only populate the output cache if the user is not logged in. Logged in users would alway get cache misses.
I have multiple asp.net sites. When a user logs unto one of the sites, I want to store a cookie telling me that a user has logged on. When the user later visits one of the other sites I have, I would like to read the cookie from that site.
AFAIK you neither can read cookies from or write cookies to other sites, so what could a workaround be?
Perhaps making a redirect to http://www.othersite.com/SaveCookie.aspx ?
Give me some ideas :-)
One of our clients has exactly the same requirement (logging into multiple sites on different domains), complicated by the fact that one of the sites requires that the user is logged in to a classic ASP application, a .NET 1.1 application and a .NET 3.5 application running on different hardware, but under the same domain...
We've basically implemented a system of round-robin style redirects, where each domain logs the user in, then bounces them on to the next domain until they return to the original domain at which point they are redirected to their original request.
So (pages and domains changed to protect the innocent):
User requests www.example1.com/page1.aspx
A cookie is set that tells us the user was attempting to access page1.aspx, and the user is sent to the www.example1.com/login.aspx
The user logs in, and is then redirected to www.example2.com/processlogin.aspx?token=EncryptedToken
ProcessLogin.aspx checks for a cookie telling it where to direct the user, if it can't find one, it decrypts the token, logs the user in on example2.com, and then redirects them to www.example1.com/processlogin.aspx?token=EncryptedToken (or example3.com - repeat as required)
As in 4, ProcessLogin.aspx checks for the cookie, finds it, deletes it and redirects the user to /page1.aspx.
If the user later on visits a page on www.example2.com, before the authentication ticket timeout, they will still be logged in on that site as well.
Edit to respond to comment
That depends on how you are making the "request to the other pages". If you make the request from your code behind, what you're doing is effectively setting the cookie on the server, rather than on the users browser.
Cookies need to be issued by the server to the client browser, and that is done in the headers of the page response - so you need to direct the users browser to a page on the other site to issue the cookie from that domain.
You could generate a request to the other page in an IFrame, or try and do it in a self closing pop-up window - but that has other issues like pop-up blockers, flickering windows, etc.
After some investigation we found that a round-robin set of redirects like this was the simplest and most reliable solution.
A very basic code setup:
An .aspx page, containing a Login control, with a method "OnLoggedIn" attached to the LoggedIn event of the control:
void OnLoggedIn(object sender, EventArgs e){
string returnUrl = Request.QueryString["returnUrl"];
// Create new cookie, store value, and add to cookie collection
HttpCookie myCookie = new HttpCookie("WhereTo");
myCookie["ReturnUrl"] = ReturnUrl;
Response.Cookies.Add(myCookie);
// Redirect user to roundtrip login processor on next domain.
// Work out domain as required.
string redirect = GetNextDomain();
// Add encoded user token
redirect += "?token=" + EncodeUserToken();
// Redirect the user, and end further processing on this thread
Response.Redirect(redirect, true);
}
Then on both servers you have ProcessLogin.aspx, that has something like this in it:
protected void Page_Load(object sender, EventArgs e){
// Look for redirect cookie
if (Request.Cookies["WhereTo"]["ReturnUrl"] != null){
// Save value from cookie
string redirect = Request.Cookies["WhereTo"]["ReturnUrl"];
// Delete original cookie by creating an empty one, and setting it
// to expire yesterday, and add it to the response.
HttpCookie myCookie = new HttpCookie("WhereTo");
myCookie.Expires = DateTime.Now.AddDays(-1d);
Response.Cookies.Add(myCookie);
// Redirect the user, and stop processing
Response.Redirect(redirect, true);
}
// Still here, so log in and redirect
string encryptedToken = Request.QueryString["token"];
if (!string.IsNullOrEmpty(encryptedToken)){
// Decrypt token, and log user in
// This will vary depending on your authentication mechanism
PerformLogin(encryptedToken);
}
// Redirect user to roundtrip login processor on next domain.
// Work out domain as required.
string redirect = GetNextDomain();
// Add encoded user token - no need to recalculate, it will be the same
redirect += "?token=" + encryptedToken;
// Redirect the user, and end further processing on this thread
Response.Redirect(redirect, true);
}
You're looking for a Single Sign-On (SSO) solution.
If it's possible for you to host your sites at different subdomains below the same domain, you can save cookies that are shared for the whole domain, e.g.:
"site1.yourdomain.com" and
"site2.yourdomain.com"
can both read cookies saved to the domain "yourdomain.com"
Another alternative is to tell the other site about the login via a request to it, as in your redirect suggestion. You could do this in several ways, e.g. by loading the page in an iframe, sending the data directly from one server to another, and so on. None of these are particularly elegant, though, and in the case of login, as Tomas Lycken says, you should really be going for a proper SSO implementation.
Here is the scenario, a user opens up non-secure page from our WebApp, let's call it PageA, in their browser and then clicks a link in there that takes them to a secure instance of PageB. Once in PageB the user can subsequently click a link that takes them back to a secure instance of PageA (which they already viewed and is in OutputCache). I observed that even though PageA is being accessed over a different URL after visiting PageB (the secure one) it's actually pulling the prior cached copy rather making a fresh one. I verified this behavior in a debugging session, and was surprised that ASP.Net used the same OutputCache item for a secure copy of the page.
My question is why is it this way? And how do I tell the ASP.Net OutPutCache to treat access from secure URL as a different/unique item than the non-secure equivalent?
[Background]
We recently switched our Web Sites images over to use Scene7/Akamai for all images. As a result of this we added code to use different Scene7 url's when viewing a given page on a secure connection. This OutputCache issue is not allowing for the logic that outputs the secure url's to execute, and is resulting in ugly browser warnings.
This doesn't answer the question as worded but it may eliminate your need to vary by scheme. If you are hard coding the "http://" for the Scene7 urls you can change them to scheme-relative urls.
<img src="http://site.scene7.com/images/someimage.jpg" />
=>
<img src="//site.scene7.com/images/someimage.jpg" />
That will cause the browser to automatically ask for the resource with the same scheme as the referring page. That's assuming you have an SSL certificate for your scene7 domain of course.
I think that you can do a VaryByCustom="scheme" and add this to your Global.asax.cs file (inlcuding a couple other others that I use as well app version & user):
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (custom.Equals("version", StringComparison.CurrentCultureIgnoreCase))
{
Assembly asm = Assembly.GetExecutingAssembly();
string[] parts = asm.FullName.Split(',');
string version = parts[1].Trim().ToLower();
return version;
}
else if (custom.Equals("user", StringComparison.CurrentCultureIgnoreCase))
{
var user = Membership.Users.CurrentUser;
return null == user ? string.Empty : user.Id.ToString();
}
else if (custom.Equals("scheme", StringComparison.CurrentCultureIgnoreCase))
{
var scheme = context.Request.IsSecureConnection ? "https" : "http";
return scheme;
}
else
return base.GetVaryByCustomString(context, custom);
}
I've never tried it but you might be able to use the Outputcache VaryByHeader property and the "host" header, which specifies the Internet host and port number of the resource being requested.
The question I'd have is why are you redirecting to PageA over secure after from PageB. If its a non-secure page, couldn't you fix the PageB redirect to always redirect to non-secure.