How can I write to a file in wwwroot with Asp.Net core 2.0 Webapi - asp.net-core

I need a very simple API to allow for the Posting of certain keys.
This keys should be written on a file, but I am having trouble after deploying the app, as I can read the file on a GET Request but the posting does not work.
The message it gives me is
"detail": "Access to the path '....\Keys\Keys.json' is denied.",
Code I am using to write to file:
var path = "wwwroot/Keys/Keys.json";
var result = new List <FireBaseKeysModel> ( );
if (System.IO.File.Exists (path)) {
var initialJson = System.IO.File.ReadAllText (path);
var convertedJson =
JsonConvert.DeserializeObject <List <FireBaseKeysModel>> (initialJson);
try {
result.AddRange (convertedJson);
}
catch {
//
}
}
result.Add(new FireBaseKeysModel() {
AccountId = accountId,
AditionalInfo = addicionalInfo,
DeviceInfo = deviceInfo,
RegistrationKey = registrationKey,
ClientId = clientId
});
var json = JsonConvert.SerializeObject (result.ToArray ( ));
System.IO.File.WriteAllText (path, json);
Anyway I can fix this without needint to change permissions on the server itself?

I have similar task that I need to take logged-in users' upload files and store them on the server. I chose to store them under the folder structure wwwroot/uploads/{ environment }/{ username }/{ YYYY }/{ MM }/{ DD }/.
I am not giving you the exact answer to your problem but these are the steps you might want to try.
Enable static file usage
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
// With the usage of static file extensions, you shouldn't need to
// set permissions to folders, if you decide to go with wwwroot.
app.UseStaticFiles();
...
}
Storage service
public interface IStorageService
{
Task<string> UploadAsync(string path, IFormFile content, string
nameWithoutExtension = null);
}
public class LocalFileStorageService : IStorageService
{
private readonly IHostingEnvironment _env;
public LocalFileStorageService(IHostingEnvironment env)
{
_env = env;
}
public async Task<string> UploadAsync(string path, IFormFile content,
string nameWithoutExtension = null)
{
if (content != null && content.Length > 0)
{
string extension = Path.GetExtension(content.FileName);
// Never trust user's provided file name
string fileName = $"{ nameWithoutExtension ?? Guid.NewGuid().ToString() }{ extension }";
// Combine the path with web root and my folder of choice,
// "uploads"
path = Path.Combine(_env.WebRootPath, "uploads", path).ToLower();
// If the path doesn't exist, create it.
// In your case, you might not need it if you're going
// to make sure your `keys.json` file is always there.
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
// Combine the path with the file name
string fullFileLocation = Path.Combine(path, fileName).ToLower();
// If your case, you might just need to open your
// `keys.json` and append text on it.
// Note that there is FileMode.Append too you might want to
// take a look.
using (var fileStream = new FileStream(fullFileLocation, FileMode.Create))
{
await Content.CopyToAsync(fileStream);
}
// I only want to get its relative path
return fullFileLocation.Replace(_env.WebRootPath,
String.Empty, StringComparison.OrdinalIgnoreCase);
}
return String.Empty;
}
}

There should not be a way to fix it without modifying permissions on that folder. (Since you are using System.IO I'm assuming this is Windows and IIS). The worker process usually uses the account that is running the application pool.
By default this account should only have read access to that folder. Without giving him, at least write permission, there should be no way to work around it.
Small off-topic comment: I would not hardcode the wwwroot folder, since the name of that folder is object to configuration and could very well change, I'd use the built in IHostingEnvironment and dependency injection to get the path:
private IHostingEnvironment _env;
public FooController(IHostingEnvironment env) {
_env = env;
}
var webrootFolder = _env.WebRootPath

Related

How to allow multiple roles to access route through RouteClaimsRequirement

In a regular type scenario, where a Route is available, say to only "Premium" users, ocelot.global.json would have RouteClaimsRequirement like this:
"RouteClaimsRequirement" : { "Role" : "Premium" }
This would get translated to a KeyValuePair<string, string>(), and it works nicely.
However, if I were to open a route to 2 types of users, eg. "Regular" and "Premium", how exactly could I achieve this?
I found a way through overriding of default Ocelot middleware. Here are some useful code snippets:
First, override the default AuthorizationMiddleware in Configuration() in Startup.cs:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var config = new OcelotPipelineConfiguration
{
AuthorisationMiddleware
= async (downStreamContext, next) =>
await OcelotJwtMiddleware.CreateAuthorizationFilter(downStreamContext, next)
};
app.UseOcelot(config).Wait();
}
As you can see, I am using a custom OcelotJwtMiddleware class up there. Here is that class, pasted:
public static class OcelotJwtMiddleware
{
private static readonly string RoleSeparator = ",";
public static Func<DownstreamContext, Func<Task>, Task> CreateAuthorizationFilter
=> async (downStreamContext, next) =>
{
HttpContext httpContext = downStreamContext.HttpContext;
var token = httpContext.Request.Cookies[JwtManager.AuthorizationTokenKey];
if (token != null && AuthorizeIfValidToken(downStreamContext, token))
{
await next.Invoke();
}
else
{
downStreamContext.DownstreamResponse =
new DownstreamResponse(new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
};
private static bool AuthorizeIfValidToken(DownstreamContext downStreamContext, string jwtToken)
{
IIdentityProvider decodedObject = new JwtManager().Decode<UserToken>(jwtToken);
if (decodedObject != null)
{
return downStreamContext.DownstreamReRoute.RouteClaimsRequirement["Role"]
?.Split(RoleSeparator)
.FirstOrDefault(role => role.Trim() == decodedObject.GetRole()) != default;
}
return false;
}
}
JwtManager class here is just my small utility made using the default Jwt NuGet package, nothing special. Also, JWT is being stored as a Cookie, which is not safe, but doesn't matter here. If you happen to copy paste your code, you will have small errors relating to this, but just switch it out with your own implementations of auth tokens.
After these 2 snippets were implemented, ocelot.global.json can have RouteClaimsRequirement such as this:
"RouteClaimsRequirement" : { "Role" : "Premium, Regular" }
This will recognize both clients with Regular in their Cookies, as well as those with Premium.

Naming conventions for view pages and setting controller action for view

I am unsure on how I should be naming my View pages, they are all CamelCase.cshtml, that when viewed in the browser look like "http://www.website.com/Home/CamelCase".
When I am building outside of .NET my pages are named like "this-is-not-camel-case.html". How would I go about doing this in my MVC4 project?
If I did go with this then how would I tell the view to look at the relevant controller?
Views/Home/camel-case.cshtml
Fake edit: Sorry if this has been asked before, I can't find anything via search or Google. Thanks.
There are a few ways you can do this:
Name all of your views in the style you would like them to show up in the url
This is pretty simple, you just add the ActionName attribute to all of your actions and specify them in the style you would like your url to look like, then rename your CamelCase.cshtml files to camel-case.cshtml files.
Use attribute routing
Along the same lines as above, there is a plugin on nuget to enable attribute routing which lets you specify the full url for each action as an attribute on the action. It has convention attributes to help you out with controller names and such as well. I generally prefer this approach because I like to be very explicit with the routes in my application.
A more framework-y approach
It's probably possible to do something convention based by extending the MVC framework, but it would be a decent amount of work. In order to select the correct action on a controller, you'd need to map the action name on its way in to MVC to its CamelCase equivalent before the framework uses it to locate the action on the controller. The easiest place to do this is in the Route, which is the last thing to happen before the MVC framework takes over the request. You'll also need to convert the other way on the way out so the urls generated look like you want them to.
Since you don't really want to alter the existing method to register routes, it's probably best write a function in application init that loops over all routes after they have been registered and wraps them with your new functionality.
Here is an example route and modifications to application start that achieve what you are trying to do. I'd still go with the route attribute approach however.
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
WrapRoutesWithNamingConvention(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
}
private void WrapRoutesWithNamingConvention(RouteCollection routes)
{
var wrappedRoutes = routes.Select(m => new ConventionRoute(m)).ToList();
routes.Clear();
wrappedRoutes.ForEach(routes.Add);
}
private class ConventionRoute : Route
{
private readonly RouteBase baseRoute;
public ConventionRoute(RouteBase baseRoute)
: base(null, null)
{
this.baseRoute = baseRoute;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var baseRouteData = baseRoute.GetRouteData(httpContext);
if (baseRouteData == null) return null;
var actionName = baseRouteData.Values["action"] as string;
var convertedActionName = ConvertHyphensToPascalCase(actionName);
baseRouteData.Values["action"] = convertedActionName;
return baseRouteData;
}
private string ConvertHyphensToPascalCase(string hyphens)
{
var capitalParts = hyphens.Split('-').Select(m => m.Substring(0, 1).ToUpper() + m.Substring(1));
var pascalCase = String.Join("", capitalParts);
return pascalCase;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
var valuesClone = new RouteValueDictionary(values);
var pascalAction = valuesClone["action"] as string;
var hyphens = ConvertPascalCaseToHyphens(pascalAction);
valuesClone["action"] = hyphens;
var baseRouteVirtualPath = baseRoute.GetVirtualPath(requestContext, valuesClone);
return baseRouteVirtualPath;
}
private string ConvertPascalCaseToHyphens(string pascal)
{
var pascalParts = new List<string>();
var currentPart = new StringBuilder();
foreach (var character in pascal)
{
if (char.IsUpper(character) && currentPart.Length > 0)
{
pascalParts.Add(currentPart.ToString());
currentPart.Clear();
}
currentPart.Append(character);
}
if (currentPart.Length > 0)
{
pascalParts.Add(currentPart.ToString());
}
var lowers = pascalParts.Select(m => m.ToLower());
var hyphens = String.Join("-", lowers);
return hyphens;
}
}
}

Custom error pages in mvc 4 application, setup with Windows authentication

I have an intranet application setup with windows authentication. Like in most applications, certain parts of the application are accessible to specific roles only. When a user not in desired role would try to access that area, he should be shown a friendly "You do not have permission to view this page" view.
I searched and looked at several resources that guides to extend the Authorize Attribute. I tried that approach, but it simply doesn't work. I still get the IIS error message and the breakpoint in this custom attributes never gets hit. The breakpoint in my extended attibute doen't get hit even when a user in role visits the page. So, I am wondering if I am missing anything ?
This is what I have -
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeRedirect : AuthorizeAttribute
{
private const string IS_AUTHORIZED = "isAuthorized";
public string RedirectUrl = "~Areas/Errors/Http401";
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool isAuthorized = base.AuthorizeCore(httpContext);
httpContext.Items.Add(IS_AUTHORIZED, isAuthorized);
return isAuthorized;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
var isAuthorized = filterContext.HttpContext.Items[IS_AUTHORIZED] != null ? Convert.ToBoolean(filterContext.HttpContext.Items[IS_AUTHORIZED]) : false;
if(!isAuthorized && filterContext.RequestContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.RequestContext.HttpContext.Response.Redirect(RedirectUrl);
}
}
}
CONTROLLER -
[AuthorizeRedirect]
[HttpPost, ValidateInput(true)]
public ActionResult NewPart(PartsViewModel vmodel) {..}
Any ideas?
Thanks
I think you could use custom error pages instead. Use AuthorizeAttribute to restrict access by callers to an action method.
[Authorize (Roles="Editor, Moderator", Users="Ann, Gohn")]
public ActionResult RestrictedAction()
{
// action logic
}
Then you could use one of the ways those are proposed by #Marco. I like handle HTTP status code within Application_EndRequest. So, it is possible to solve your problem using by following:
protected void Application_EndRequest()
{
int status = Response.StatusCode;
if (Response.StatusCode == 401)
{
Response.Clear();
var rd = new RouteData();
rd.DataTokens["area"] = "Areas";
rd.Values["controller"] = "Errors";
rd.Values["action"] = "Http401";
IController c = new ErrorsController();
c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
}
}
To clearly specifiey what happens to an existing response when the HTTP status code is an error, you should use existingResponse attribute of <httpErrors> element in your configuration file. If you want to the error page appears immediately, then use Replace value, in otherwise - PassThrough (see details in my issue).

ASP.NET Bundling and minification include dynamic files from database

I'm developing a multi-tenancy MVC 4 application on which the user has some theming possibilities.
He can override every single resource (css, js, jpg, png, ect...) by adding a relative path to a theming table e.g. /Scripts/booking.js
Which tenant to use is figured out by the URL e.g. http://myapp/tenant/Booking/New this is simply the name of the connection string which should be used.
Therefore if a request is made for a specific resource I first need to check if there is an overridden version of this resource in the database and use it if found.
Now I'd like to implement the new bundling and minification features which microsoft provides in the System.Web.Optimization namespace. But I couldn't figure out how to achieve this with the files in the database.
I've prototyped my own JsMinify implementation to achieve this
public class MyJsMinify : JsMinify
{
private static byte[] GetContentFile(FileInfo filePath)
{
string fullName = filePath.FullName;
int indexOf = fullName.IndexOf("content", StringComparison.OrdinalIgnoreCase);
string substring = fullName.Substring(indexOf + 8).Replace(#"\\", "/").Replace(#"\", "/");
ThemingService themingService = ObjectFactory.GetInstance<ThemingService>();
Theming myTheming = themingService.Find(new ThemingFilter { FilePathLike = substring });
if (myTheming == null)
{
return themingService.GetContentFile(fullName);
}
return myTheming.FileData;
}
public override void Process(BundleContext context, BundleResponse response)
{
StringBuilder newContent = new StringBuilder();
foreach (FileInfo fileInfo in response.Files)
{
using (MemoryStream memoryStream = new MemoryStream(GetContentFile(fileInfo)))
{
using (StreamReader myStreamReader = new StreamReader(memoryStream, true))
{
newContent.AppendLine(myStreamReader.ReadToEnd());
}
}
}
response.Content = newContent.ToString();
base.Process(context, response);
}
}
This seems to work if I'm in Release mode but while developing I'd like to get each single script referenced independently. This is automatically done throughout the bundling and minification framework. The Resource URL's generated by the framework looks like the following
<script src="/myapp/Content/Scripts/jquery-1.9.0.js"></script>
but should look like this
<script src="/myapp/tenant/Content/Scripts/jquery-1.9.0.js"></script>
I've configured the following Routes:
routeCollection.MapRoute("Content1", "{mandator}/Content/{*filePath}", new { mandator = defaultMandator, controller = "Environment", action = "ContentFile" }, new { mandator = mandatorConstraints });
routeCollection.MapRoute("Content2", "Content/{*filePath}", new { mandator = defaultMandator, controller = "Environment", action = "ContentFile" }, new { mandator = mandatorConstraints });
The ContentFile Method looks like this
[AcceptVerbs(HttpVerbs.Get)]
[AcceptType(HttpTypes.All)]
[OutputCache(CacheProfile = "ContentFile")]
public ActionResult ContentFile(string filePath)
{
if (string.Compare(filePath, "Stylesheets/Import.css", StringComparison.OrdinalIgnoreCase) == 0)
{
return GetContentImport(CssFileArray, "Stylesheets/");
}
if (string.Compare(filePath, "Stylesheets/ImportOutlook.css", StringComparison.OrdinalIgnoreCase) == 0)
{
return GetContentImport(OutlookCssFileArray, "Stylesheets/");
}
if (string.Compare(filePath, "Scripts/OutlookAddin/Import.js", StringComparison.OrdinalIgnoreCase) == 0)
{
return GetContentImport(OutlookJsFileArray, "Scripts/");
}
return new FileContentResult(GetContentFile(filePath), MimeType(filePath));
}
Does anybody have an idea how I could achieve this?
Is there a multi-tenancy pattern to follow?
So I'm not sure I completely understand your scenario, but I believe this is what VirtualPathProviders could be used for.
We added support in the 1.1-alpha1 release, so bundles will automatically use the VirtualPathProvider registered with ASP.NET to fetch the contents of the file.
If you were to write a custom VPP that is able to always return the correct version of ~/Scripts/booking.js, everything should just work.

Logging to a local file in Flex

I have my application frontend developed in Flex 3.
For logging, we are using traces and Logger at times yet we dont have a specific way to store logs in a local file of User's machine.
In fact, what I learned from Adobe livedocs is that flashplayer manages itself all logs in flashlog.txt file.
Is there any other way I can maintain a copy of logs? flashlog.txt gets cleared everytime we perform "Logout".
You have not mentioned whether your application is a desktop application, or a browser based.
In case of a desktop application you can write a new class,
import mx.core.mx_internal;
use namespace mx_internal;
public class LoggingFileTarget extends LineFormattedTarget {
private const DEFAULT_LOG_PATH:String = "C:/mylogfile.txt";
private var log:File;
public function LoggingFileTarget(logFile:File = null) {
if(logFile != null) {
log = logFile;
} else {
log = new File(DEFAULT_LOG_PATH);
}
}
public function get logURI():String {
return log.url;
}
mx_internal override function internalLog(message:String):void {
write(message);
}
private function write(msg:String):void {
var fs:FileStream = new FileStream();
try {
fs.open(log, FileMode.APPEND);
fs.writeUTFBytes(msg + "\n");
fs.close();
} catch(e:Error) {
trace("FATAL:: Unable to write to log file.");
}
}
public function clear():void {
var fs:FileStream = new FileStream();
fs.open(log, FileMode.WRITE);
fs.writeUTFBytes("");
fs.close();
}
}
In case of a browser based application, you can keep writing either to an in-memory string, or to a local shared object. Using a shared local object, keep appending to logs, and then collate via a web call.