/// <summary> /// Performs the data-binding and builds the controls inside the <see cref="Repeater"/>. /// </summary> private void DataBind(DotvvmRequestContext context) { var dataSourceBinding = GetDataSourceBinding(); var index = 0; var dataSource = DataSource; if (dataSource != null) { var items = GetIEnumerableFromDataSource(dataSource).Cast<object>().ToArray(); if (lastBoundArray != null) { if (lastBoundArray.SequenceEqual(items)) return; } Children.Clear(); foreach (var item in items) { var placeholder = new DataItemContainer { DataItemIndex = index }; ItemTemplate.BuildContent(context, placeholder); Children.Add(placeholder); placeholder.SetBinding(DataContextProperty, GetItemBinding((IList)items, dataSourceBinding.GetKnockoutBindingExpression(), index)); Debug.Assert(placeholder.properties[DataContextProperty] != null); index++; } } }
/// <summary> /// Ensures the redirect required by the OWIN Security middleware is properly handled by DotVVM client library. /// </summary> public static void ApplyRedirectResponse(IOwinContext context, string redirectUri) { if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized) { DotvvmRequestContextExtensions.SetRedirectResponse(DotvvmRequestContext.GetCurrent(DotvvmMiddleware.ConvertHttpContext(context)), redirectUri, (int)HttpStatusCode.Redirect, true); } }
public void VerifyToken(DotvvmRequestContext context, string token) { if (context == null) throw new ArgumentNullException("context"); if (string.IsNullOrWhiteSpace(token)) throw new SecurityException("CSRF protection token is missing."); // Get application key helper var keyHelper = new ApplicationKeyHelper(context.Configuration.Security); // Get token var userIdentity = ProtectionHelpers.GetUserIdentity(context); var requestIdentity = ProtectionHelpers.GetRequestIdentity(context); byte[] tokenSid; try { var tokenData = Convert.FromBase64String(token); tokenSid = keyHelper.UnprotectData(tokenData, KDF_LABEL_TOKEN, userIdentity, requestIdentity); } catch (Exception ex) { // Incorrect Base64 formatting of crypto protection error throw new SecurityException("CSRF protection token is invalid.", ex); } // Get SID from cookie and compare with token one var cookieSid = this.GetOrCreateSessionId(context); if (!cookieSid.SequenceEqual(tokenSid)) throw new SecurityException("CSRF protection token is invalid."); }
/// <summary> /// Processes the request. /// </summary> public async Task ProcessRequest(DotvvmRequestContext context) { bool failedAsUnauthorized = false; Exception exception = null; try { await ProcessRequestCore(context); } catch (DotvvmInterruptRequestExecutionException) { // the response has already been generated, do nothing return; } catch (UnauthorizedAccessException ex) { failedAsUnauthorized = true; exception = ex; } if (failedAsUnauthorized) { // unauthorized error context.OwinContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; await DotvvmErrorPageMiddleware.RenderErrorResponse(context.OwinContext, exception); } }
/// <summary> /// Fixes the response created by the OWIN Security Challenge call to be accepted by DotVVM client library. /// </summary> public static void ApplyRedirectResponse(IOwinContext context, string redirectUri) { if (context.Response.StatusCode == (int)HttpStatusCode.Unauthorized) { DotvvmRequestContext.SetRedirectResponse(context, redirectUri, (int)HttpStatusCode.Redirect); } }
public async Task WriteViewModelResponse(DotvvmRequestContext context, DotvvmView view) { // return the response context.OwinContext.Response.ContentType = "application/json; charset=utf-8"; var serializedViewModel = context.GetSerializedViewModel(); await context.OwinContext.Response.WriteAsync(serializedViewModel); }
public void RenderPostbackUpdatedControls(DotvvmRequestContext context, DotvvmView page) { var stack = new Stack<DotvvmControl>(); stack.Push(page); do { var control = stack.Pop(); object val; if (control.properties != null && control.properties.TryGetValue(PostBack.UpdateProperty, out val) && val is bool && (bool)val) { using (var w = new StringWriter()) { control.Render(new HtmlWriter(w, context), context); var clientId = control.GetDotvvmUniqueId() as string; if (clientId == null) { throw new DotvvmControlException(control, "This control cannot use PostBack.Update=\"true\" because it has dynamic ID. This happens when the control is inside a Repeater or other data-bound control and the RenderSettings.Mode=\"Client\"."); } context.PostBackUpdatedControls[clientId] = w.ToString(); } } else { foreach (var child in control.Children) { stack.Push(child); } } } while (stack.Count > 0); }
/// <summary> /// Called after the command is invoked. /// </summary> protected internal override void OnCommandExecuted(DotvvmRequestContext context, ActionInfo actionInfo, Exception exception) { if (exception != null) { OnException(context, actionInfo, exception); } }
/// <summary> /// Builds the <see cref="DotvvmView"/> for the specified HTTP request, resolves the master page hierarchy and performs the composition. /// </summary> public DotvvmView BuildView(DotvvmRequestContext context) { // get the page markup var markup = markupFileLoader.GetMarkupFileVirtualPath(context); // build the page var pageBuilder = controlBuilderFactory.GetControlBuilder(markup); var contentPage = pageBuilder.BuildControl(controlBuilderFactory) as DotvvmView; FillsDefaultDirectives(contentPage, context.Configuration); // check for master page and perform composition recursively while (IsNestedInMasterPage(contentPage)) { // load master page var masterPageFile = contentPage.Directives[ParserConstants.MasterPageDirective]; var masterPage = (DotvvmView)controlBuilderFactory.GetControlBuilder(masterPageFile).BuildControl(controlBuilderFactory); FillsDefaultDirectives(masterPage, context.Configuration); PerformMasterPageComposition(contentPage, masterPage); masterPage.ViewModelType = contentPage.ViewModelType; contentPage = masterPage; } // verifies the SPA request VerifySpaRequest(context, contentPage); return contentPage; }
public void TestInit() { configuration = DotvvmConfiguration.CreateDefault(); configuration.ServiceLocator.RegisterSingleton<IDataProtectionProvider>(() => new DpapiDataProtectionProvider("dotvvm test")); configuration.Security.SigningKey = Convert.FromBase64String("Uiq1FXs016lC6QaWIREB7H2P/sn4WrxkvFkqaIKpB27E7RPuMipsORgSgnT+zJmUu8zXNSJ4BdL73JEMRDiF6A1ScRNwGyDxDAVL3nkpNlGrSoLNM1xHnVzSbocLFDrdEiZD2e3uKujguycvWSNxYzjgMjXNsaqvCtMu/qRaEGc="); configuration.Security.EncryptionKey = Convert.FromBase64String("jNS9I3ZcxzsUSPYJSwzCOm/DEyKFNlBmDGo9wQ6nxKg="); serializer = new DefaultViewModelSerializer(configuration); context = new DotvvmRequestContext() { Configuration = configuration, OwinContext = new Microsoft.Owin.Fakes.StubIOwinContext() { RequestGet = () => new Microsoft.Owin.Fakes.StubIOwinRequest() { UriGet = () => new Uri("http://localhost:8628/Sample1"), UserGet = () => new WindowsPrincipal(WindowsIdentity.GetAnonymous()), HeadersGet = () => new HeaderDictionary(new Dictionary<string, string[]>()), EnvironmentGet = () => new Dictionary<string, object>() { { "owin.RequestPathBase", "" } } } }, ResourceManager = new ResourceManager(configuration), Presenter = configuration.RouteTable.GetDefaultPresenter(), ViewModelSerializer = serializer }; }
/// <summary> /// Processes the request. /// </summary> public async Task ProcessRequest(DotvvmRequestContext context) { bool failedAsUnauthorized = false; Exception exception = null; try { await ProcessRequestCore(context); } catch (DotvvmInterruptRequestExecutionException) { // the response has already been generated, do nothing return; } catch (UnauthorizedAccessException ex) { failedAsUnauthorized = true; exception = ex; } if (failedAsUnauthorized) { // unauthorized error context.OwinContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; await DotvvmErrorPageMiddleware.RenderErrorResponse(context.OwinContext, exception); } }
public void CommandResolver_Valid_SimpleTest() { var path = new[] { new ValueBindingExpression(vm => ((dynamic)vm[0]).A[0], "A()[0]") }; var commandId = "someCommand"; var command = new CommandBindingExpression(vm => ((TestA)vm[0]).Test(((TestA)vm[0]).StringToPass, ((dynamic)vm[1]).NumberToPass), commandId); var testObject = new { A = new[] { new TestA() { StringToPass = "******" } }, NumberToPass = 16 }; var viewRoot = new DotvvmView() { DataContext = testObject }; viewRoot.SetBinding(Controls.Validation.TargetProperty, new ValueBindingExpression(vm => vm.Last(), "$root")); var placeholder = new HtmlGenericControl("div"); placeholder.SetBinding(DotvvmBindableObject.DataContextProperty, path[0]); viewRoot.Children.Add(placeholder); var button = new Button(); button.SetBinding(ButtonBase.ClickProperty, command); placeholder.Children.Add(button); var resolver = new CommandResolver(); var context = new DotvvmRequestContext() { ViewModel = testObject }; context.ModelState.ValidationTargetPath = KnockoutHelper.GetValidationTargetExpression(button); resolver.GetFunction(viewRoot, context, path.Select(v => v.Javascript).ToArray(), commandId).Action(); Assert.AreEqual(testObject.NumberToPass, testObject.A[0].ResultInt); Assert.AreEqual(testObject.A[0].ResultString, testObject.A[0].ResultString); }
/// <summary> /// Process an individual request. /// </summary> public override Task Invoke(IOwinContext context) { // create the context var dotvvmContext = new DotvvmRequestContext() { OwinContext = context, Configuration = configuration, ResourceManager = new ResourceManager(configuration) }; // attempt to translate Googlebot hashbang espaced fragment URL to a plain URL string. string url; if (!TryParseGooglebotHashbangEscapedFragment(context.Request.QueryString, out url)) { url = context.Request.Path.Value; } url = url.Trim('/'); // find the route IDictionary<string, object> parameters = null; var route = configuration.RouteTable.FirstOrDefault(r => r.IsMatch(url, out parameters)); if (route != null) { // handle the request dotvvmContext.Route = route; dotvvmContext.Parameters = parameters; return route.ProcessRequest(dotvvmContext); } else { // we cannot handle the request, pass it to another component return Next.Invoke(context); } }
public async Task WriteHtmlResponse(DotvvmRequestContext context, DotvvmView view) { // return the response context.OwinContext.Response.ContentType = "text/html; charset=utf-8"; context.OwinContext.Response.Headers["Cache-Control"] = "no-cache"; var html = RenderPage(context, view); await context.OwinContext.Response.WriteAsync(html); }
protected internal override void OnPreRender(DotvvmRequestContext context) { if (!string.IsNullOrEmpty(FormatString)) { context.ResourceManager.AddCurrentCultureGlobalizationResource(); } base.OnPreRender(context); }
public override void CreateControls(DotvvmRequestContext context, DotvvmControl container) { var literal = new Literal(); literal.FormatString = FormatString; literal.SetBinding(Literal.TextProperty, GetValueBinding(ValueBindingProperty)); container.Children.Add(literal); }
/// <summary> /// Serializes the view model. /// </summary> public string SerializeViewModel(DotvvmRequestContext context) { if (SendDiff && context.ReceivedViewModelJson != null && context.ViewModelJson["viewModel"] != null) { context.ViewModelJson["viewModelDiff"] = JsonUtils.Diff((JObject)context.ReceivedViewModelJson["viewModel"], (JObject)context.ViewModelJson["viewModel"], true); context.ViewModelJson.Remove("viewModel"); } return context.ViewModelJson.ToString(JsonFormatting); }
protected internal override void OnPreRender(DotvvmRequestContext context) { if (context.IsSpaRequest) { // we need to render the HTML on postback when SPA request arrives SetValue(PostBack.UpdateProperty, true); } base.OnPreRender(context); }
/// <summary> /// Gets the markup file virtual path from the current request URL. /// </summary> public string GetMarkupFileVirtualPath(DotvvmRequestContext context) { // get file name var fileName = context.Route != null ? context.Route.VirtualPath : context.OwinContext.Request.Uri.LocalPath; if (!fileName.EndsWith(MarkupFile.ViewFileExtension, StringComparison.CurrentCultureIgnoreCase)) { throw new Exception("The view must be a file with the .dothtml extension!"); // TODO: exception handling } return fileName; }
/// <summary> /// Process an individual request. /// </summary> public override async Task Invoke(IOwinContext context) { if (Interlocked.Exchange(ref configurationSaved, 1) == 0) { VisualStudioHelper.DumpConfiguration(Configuration, Configuration.ApplicationPhysicalPath); } // create the context var dotvvmContext = new DotvvmRequestContext() { OwinContext = context, Configuration = Configuration, ResourceManager = new ResourceManager(Configuration), ViewModelSerializer = Configuration.ServiceLocator.GetService <IViewModelSerializer>() }; context.Set(HostingConstants.DotvvmRequestContextOwinKey, dotvvmContext); // attempt to translate Googlebot hashbang espaced fragment URL to a plain URL string. string url; if (!TryParseGooglebotHashbangEscapedFragment(context.Request.QueryString, out url)) { url = context.Request.Path.Value; } url = url.Trim('/'); // find the route IDictionary <string, object> parameters = null; var route = Configuration.RouteTable.FirstOrDefault(r => r.IsMatch(url, out parameters)); if (route != null) { // handle the request dotvvmContext.Route = route; dotvvmContext.Parameters = parameters; dotvvmContext.Query = context.Request.Query .ToDictionary(d => d.Key, d => d.Value.Length == 1 ? (object)d.Value[0] : d.Value); try { await route.ProcessRequest(dotvvmContext); return; } catch (DotvvmInterruptRequestExecutionException) { // the response has already been generated, do nothing return; } } // we cannot handle the request, pass it to another component await Next.Invoke(context); }
private byte[] GetOrCreateSessionId(DotvvmRequestContext context) { if (context == null) throw new ArgumentNullException("context"); if (string.IsNullOrWhiteSpace(context.Configuration.Security.SessionIdCookieName)) throw new FormatException("Configured SessionIdCookieName is missing or empty."); // Get cookie manager var mgr = new ChunkingCookieManager(); // TODO: Make this configurable // Get application key helper var keyHelper = new ApplicationKeyHelper(context.Configuration.Security); // Get cookie value var sidCookieValue = mgr.GetRequestCookie(context.OwinContext, context.Configuration.Security.SessionIdCookieName); if (string.IsNullOrWhiteSpace(sidCookieValue)) { // No SID - generate and protect new one var rng = new System.Security.Cryptography.RNGCryptoServiceProvider(); var sid = new byte[SID_LENGTH]; rng.GetBytes(sid); var protectedSid = keyHelper.ProtectData(sid, KDF_LABEL_SID); // Save to cookie sidCookieValue = Convert.ToBase64String(protectedSid); mgr.AppendResponseCookie( context.OwinContext, context.Configuration.Security.SessionIdCookieName, // Configured cookie name sidCookieValue, // Base64-encoded SID value new Microsoft.Owin.CookieOptions { HttpOnly = true, // Don't allow client script access Secure = context.OwinContext.Request.IsSecure // If request goes trough HTTPS, mark as secure only }); // Return newly generated SID return sid; } else { // Try to read from cookie try { var protectedSid = Convert.FromBase64String(sidCookieValue); var sid = keyHelper.UnprotectData(protectedSid, KDF_LABEL_SID); return sid; } catch (Exception ex) { // Incorrect Base64 formatting of crypto protection error throw new SecurityException("Value of the SessionID cookie is corrupted or has been tampered with.", ex); } } }
/// <summary> /// Gets the markup file virtual path from the current request URL. /// </summary> public string GetMarkupFileVirtualPath(DotvvmRequestContext context) { // get file name var fileName = context.Route != null ? context.Route.VirtualPath : context.OwinContext.Request.Uri.LocalPath; if (!fileName.EndsWith(MarkupFile.ViewFileExtension, StringComparison.CurrentCultureIgnoreCase)) { throw new Exception("The view must be a file with the .dothtml extension!"); // TODO: exception handling } return(fileName); }
/// <summary> /// Builds the view model for the client. /// </summary> public void BuildViewModel(DotvvmRequestContext context) { // serialize the ViewModel var serializer = CreateJsonSerializer(); var viewModelConverter = new ViewModelJsonConverter(context.IsPostBack, viewModelMapper) { UsedSerializationMaps = new HashSet<ViewModelSerializationMap>() }; serializer.Converters.Add(viewModelConverter); var writer = new JTokenWriter(); try { serializer.Serialize(writer, context.ViewModel); } catch (Exception ex) { throw new Exception($"Could not serialize viewModel of type { context.ViewModel.GetType().Name }. Serialization failed at property { writer.Path }. {GeneralViewModelRecomendations}", ex); } // persist CSRF token writer.Token["$csrfToken"] = context.CsrfToken; // persist encrypted values if (viewModelConverter.EncryptedValues.Count > 0) writer.Token["$encryptedValues"] = viewModelProtector.Protect(viewModelConverter.EncryptedValues.ToString(Formatting.None), context); // serialize validation rules var validationRules = SerializeValidationRules(viewModelConverter); // create result object var result = new JObject(); result["viewModel"] = writer.Token; result["url"] = context.OwinContext.Request.Uri.PathAndQuery; result["virtualDirectory"] = DotvvmMiddleware.GetVirtualDirectory(context.OwinContext); if (context.ResultIdFragment != null) { result["resultIdFragment"] = context.ResultIdFragment; } if (context.IsPostBack || context.IsSpaRequest) { result["action"] = "successfulCommand"; var renderedResources = new HashSet<string>(context.ReceivedViewModelJson?["renderedResources"]?.Values<string>() ?? new string[] { }); result["resources"] = BuildResourcesJson(context, rn => !renderedResources.Contains(rn)); } else { result["renderedResources"] = JArray.FromObject(context.ResourceManager.RequiredResources); } // TODO: do not send on postbacks if (validationRules.Count > 0) result["validationRules"] = validationRules; context.ViewModelJson = result; }
/// <summary> /// Called before the command is executed. /// </summary> protected internal override void OnCommandExecuting(DotvvmRequestContext context, ActionInfo actionInfo) { if (!string.IsNullOrEmpty(context.ModelState.ValidationTargetPath)) { // perform the validation context.ModelState.Errors.AddRange(viewModelValidator.ValidateViewModel(context.ModelState.ValidationTarget)); // return the model state when error occurs context.FailOnInvalidModelState(); } base.OnCommandExecuting(context, actionInfo); }
protected string RenderPage(DotvvmRequestContext context, DotvvmView view) { // embed resource links EmbedResourceLinks(view); // prepare the render context // get the HTML using (var textWriter = new StringWriter()) { var htmlWriter = new HtmlWriter(textWriter, context); view.Render(htmlWriter, context); return textWriter.ToString(); } }
/// <summary> /// Process an individual request. /// </summary> public override async Task Invoke(IOwinContext context) { if (Interlocked.Exchange(ref configurationSaved, 1) == 0) { VisualStudioHelper.DumpConfiguration(Configuration, Configuration.ApplicationPhysicalPath); } // create the context var dotvvmContext = new DotvvmRequestContext() { OwinContext = context, Configuration = Configuration, ResourceManager = new ResourceManager(Configuration), ViewModelSerializer = Configuration.ServiceLocator.GetService<IViewModelSerializer>() }; context.Set(HostingConstants.DotvvmRequestContextOwinKey, dotvvmContext); // attempt to translate Googlebot hashbang espaced fragment URL to a plain URL string. string url; if (!TryParseGooglebotHashbangEscapedFragment(context.Request.QueryString, out url)) { url = context.Request.Path.Value; } url = url.Trim('/'); // find the route IDictionary<string, object> parameters = null; var route = Configuration.RouteTable.FirstOrDefault(r => r.IsMatch(url, out parameters)); if (route != null) { // handle the request dotvvmContext.Route = route; dotvvmContext.Parameters = parameters; dotvvmContext.Query = context.Request.Query .ToDictionary(d => d.Key, d => d.Value.Length == 1 ? (object) d.Value[0] : d.Value); try { await route.ProcessRequest(dotvvmContext); return; } catch (DotvvmInterruptRequestExecutionException) { // the response has already been generated, do nothing return; } } // we cannot handle the request, pass it to another component await Next.Invoke(context); }
internal override void OnPreRenderComplete(DotvvmRequestContext context) { EnsureControlHasId(); if (Children.Count != 1 || !(Children[0] is Literal)) { throw new Exception("The <dot:InlineScript>...</dot:InlineScript> control can only contain text content!"); } var script = ((Literal)Children[0]).Text; context.ResourceManager.AddStartupScript("inlinescript_" + ID, script, Constants.DotvvmResourceName); base.OnPreRenderComplete(context); }
/// <summary> /// Initializes the view model for the specified view. /// </summary> public object InitializeViewModel(DotvvmRequestContext context, DotvvmView view) { string viewModel; if (!view.Directives.TryGetValue(Constants.ViewModelDirectiveName, out viewModel)) { throw new Exception("Couldn't find a viewmodel for the specified view!"); // TODO: exception handling } var viewModelType = Type.GetType(viewModel); if (viewModelType == null) { throw new Exception(string.Format("Couldn't create a class of type '{0}'!", viewModel)); // TODO: exception handling } return CreateViewModelInstance(viewModelType); }
/// <summary> /// Processes the request. /// </summary> public async Task ProcessRequest(DotvvmRequestContext context) { try { await ProcessRequestCore(context); } catch (UnauthorizedAccessException) { context.OwinContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; } catch (DotvvmControlException ex) { ex.FileName = Path.Combine(ApplicationPath, ex.FileName); throw; } }
public void RenderPage(DotvvmRequestContext context, DotvvmView view) { // embed resource links EmbedResourceLinks(view); // prepare the render context var renderContext = new RenderContext(context); // get the HTML using (var textWriter = new StringWriter()) { var htmlWriter = new HtmlWriter(textWriter, context); view.Render(htmlWriter, renderContext); context.RenderedHtml = textWriter.ToString(); } }
/// <summary> /// Processes the request. /// </summary> public async Task ProcessRequest(DotvvmRequestContext context) { try { await ProcessRequestCore(context); } catch (UnauthorizedAccessException) { context.OwinContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; } catch (DotvvmControlException ex) { ex.FileName = Path.Combine(ApplicationPath, ex.FileName); throw; } }
/// <summary> /// If the request is SPA request, we need to verify that the page contains the same SpaContentPlaceHolder. /// Also we need to check that the placeholder is the same. /// </summary> private void VerifySpaRequest(DotvvmRequestContext context, DotvvmView page) { if (context.IsSpaRequest) { var spaContentPlaceHolders = page.GetAllDescendants().OfType<SpaContentPlaceHolder>().ToList(); if (spaContentPlaceHolders.Count > 1) { throw new Exception("Multiple controls of type <dot:SpaContentPlaceHolder /> found on the page! This control can be used only once!"); // TODO: exception handling } if (spaContentPlaceHolders.Count == 0 || spaContentPlaceHolders[0].GetSpaContentPlaceHolderUniqueId() != context.GetSpaContentPlaceHolderUniqueId()) { // the client has loaded different page which does not contain current SpaContentPlaceHolder - he needs to be redirected context.RedirectToUrl(context.OwinContext.Request.Uri.AbsoluteUri); } } }
public async Task ProcessStaticCommandRequest(DotvvmRequestContext context) { JObject postData; using (var jsonReader = new JsonTextReader(new StreamReader(context.OwinContext.Request.Body))) { postData = JObject.Load(jsonReader); } // validate csrf token context.CsrfToken = postData["$csrfToken"].Value <string>(); CsrfProtector.VerifyToken(context, context.CsrfToken); var command = postData["command"].Value <string>(); var arguments = postData["args"] as JArray; var lastDot = command.LastIndexOf('.'); var typeName = command.Remove(lastDot); var methodName = command.Substring(lastDot + 1); var methodInfo = Type.GetType(typeName).GetMethod(methodName); if (!Attribute.IsDefined(methodInfo, typeof(AllowStaticCommandAttribute))) { throw new DotvvmHttpException($"This method cannot be called from the static command. If you need to call this method, add the '{nameof(AllowStaticCommandAttribute)}' to the method."); } var target = methodInfo.IsStatic ? null : arguments[0].ToObject(methodInfo.DeclaringType); var methodArguments = arguments.Skip(methodInfo.IsStatic ? 0 : 1) .Zip(methodInfo.GetParameters(), (arg, parameter) => arg.ToObject(parameter.ParameterType)) .ToArray(); var actionInfo = new ActionInfo() { IsControlCommand = false, Action = () => methodInfo.Invoke(target, methodArguments) }; var filters = context.Configuration.Runtime.GlobalFilters .Concat(methodInfo.DeclaringType.GetCustomAttributes <ActionFilterAttribute>()) .Concat(methodInfo.GetCustomAttributes <ActionFilterAttribute>()) .ToArray(); var task = ExecuteCommand(actionInfo, context, filters); await task; object result = TaskUtils.GetResult(task); using (var writer = new StreamWriter(context.OwinContext.Response.Body)) { writer.WriteLine(JsonConvert.SerializeObject(result)); } }
public void Authorize(DotvvmRequestContext context) { // the user must not be anonymous if (context.OwinContext.Request.User == null || !context.OwinContext.Request.User.Identity.IsAuthenticated) { SetUnauthorizedResponse(context); } // if the role is set if (Roles != null && Roles.Length > 0) { if (!Roles.Any(r => context.OwinContext.Request.User.IsInRole(r))) { SetUnauthorizedResponse(context); } } }
private const string KDF_LABEL_TOKEN = "DotVVM.Framework.Security.DefaultCsrfProtector.Token"; // Key derivation label for protecting token public string GenerateToken(DotvvmRequestContext context) { if (context == null) throw new ArgumentNullException("context"); // Get SID var sid = this.GetOrCreateSessionId(context); // Get application key helper var keyHelper = new ApplicationKeyHelper(context.Configuration.Security); // Get token var userIdentity = ProtectionHelpers.GetUserIdentity(context); var requestIdentity = ProtectionHelpers.GetRequestIdentity(context); var tokenData = keyHelper.ProtectData(sid, KDF_LABEL_TOKEN, userIdentity, requestIdentity); // Return encoded token return Convert.ToBase64String(tokenData); }
/// <summary> /// Builds the view model for the client. /// </summary> public void BuildViewModel(DotvvmRequestContext context, DotvvmView view) { // serialize the ViewModel var serializer = CreateJsonSerializer(); var viewModelConverter = new ViewModelJsonConverter() { EncryptedValues = new JArray(), UsedSerializationMaps = new HashSet<ViewModelSerializationMap>() }; serializer.Converters.Add(viewModelConverter); var writer = new JTokenWriter(); serializer.Serialize(writer, context.ViewModel); // persist CSRF token writer.Token["$csrfToken"] = context.CsrfToken; // persist encrypted values if (viewModelConverter.EncryptedValues.Count > 0) writer.Token["$encryptedValues"] = viewModelProtector.Protect(viewModelConverter.EncryptedValues.ToString(Formatting.None), context); // serialize validation rules var validationRules = SerializeValidationRules(viewModelConverter); // create result object var result = new JObject(); result["viewModel"] = writer.Token; result["url"] = context.OwinContext.Request.Uri.PathAndQuery; result["virtualDirectory"] = DotvvmMiddleware.GetVirtualDirectory(context.OwinContext); if (context.IsPostBack || context.IsSpaRequest) { result["action"] = "successfulCommand"; var renderedResources = new HashSet<string>(context.ReceivedViewModelJson?["renderedResources"]?.Values<string>() ?? new string[] { }); result["resources"] = BuildResourcesJson(context, rn => !renderedResources.Contains(rn)); } else { result["renderedResources"] = JArray.FromObject(context.ResourceManager.RequiredResources); } // TODO: do not send on postbacks if (validationRules.Count > 0) result["validationRules"] = validationRules; context.ViewModelJson = result; }
private void DataBind(DotvvmRequestContext context) { Children.Clear(); var dataSourceBinding = GetDataSourceBinding(); // var dataSourcePath = dataSourceBinding.GetViewModelPathExpression(this, DataSourceProperty); var dataSource = DataSource; Action<string> sortCommand = null; if (dataSource is IGridViewDataSet) { sortCommand = ((IGridViewDataSet)dataSource).SetSortExpression; // dataSourcePath + ".SetSortExpression"; } else { var sortCommandBinding = GetCommandBinding(SortChangedProperty); if (sortCommandBinding != null) { sortCommand = s => sortCommandBinding.Delegate(new []{ s }.Concat(BindingExpression.GetDataContexts(this, true)).ToArray(), null); } } var index = 0; if (dataSource != null) { // create header row CreateHeaderRow(context, sortCommand); var items = GetIEnumerableFromDataSource(dataSource); foreach (var item in items) { // create row var placeholder = new DataItemContainer { DataItemIndex = index }; placeholder.SetBinding(DataContextProperty, GetItemBinding((IList)items, dataSourceBinding.GetKnockoutBindingExpression(), index)); Children.Add(placeholder); CreateRow(context, placeholder); index++; } } }
private async Task RenderResponse(IOwinContext context, bool isPost, string errorMessage, List <UploadedFile> uploadedFiles) { var outputRenderer = configuration.ServiceLocator.GetService <IOutputRenderer>(); if (isPost && context.Request.Headers.Get(Constants.DotvvmFileUploadAsyncHeaderName) == "true") { // modern browser - return JSON if (string.IsNullOrEmpty(errorMessage)) { await outputRenderer.RenderPlainJsonResponse(context, uploadedFiles); } else { await outputRenderer.RenderPlainTextResponse(context, errorMessage); context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; } } else { // old browser - return HTML var template = new FileUploadPageTemplate(); template.FormPostUrl = DotvvmRequestContext.TranslateVirtualPath("~/" + Constants.FileUploadHandlerMatchUrl, context); template.AllowMultipleFiles = context.Request.Query["multiple"] == "true"; if (isPost) { if (string.IsNullOrEmpty(errorMessage)) { template.StartupScript = string.Format("reportProgress(false, 100, {0})", JsonConvert.SerializeObject(uploadedFiles)); } else { template.StartupScript = string.Format("reportProgress(false, 100, {0})", JsonConvert.SerializeObject(errorMessage)); } } await outputRenderer.RenderHtmlResponse(context, template.TransformText()); } }
/// <summary> /// Processes the request. /// </summary> public async Task ProcessRequest(DotvvmRequestContext context) { try { await ProcessRequestCore(context); } catch (DotvvmInterruptRequestExecutionException) { // the response has already been generated, do nothing return; } catch (UnauthorizedAccessException) { context.OwinContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; } catch (DotvvmControlException ex) { ex.FileName = Path.Combine(ApplicationPath, ex.FileName); throw; } }
protected Task ExecuteCommand(ActionInfo action, DotvvmRequestContext context, IEnumerable <ActionFilterAttribute> methodFilters) { // run OnCommandExecuting on action filters foreach (var filter in methodFilters) { filter.OnCommandExecuting(context, action); } object result = null; try { result = action.Action(); } catch (Exception ex) { if (ex is TargetInvocationException) { ex = ex.InnerException; } if (ex is DotvvmInterruptRequestExecutionException) { throw new DotvvmInterruptRequestExecutionException("The request execution was interrupted in the command!", ex); } context.CommandException = ex; } // run OnCommandExecuted on action filters foreach (var filter in methodFilters.Reverse()) { filter.OnCommandExecuted(context, action, context.CommandException); } if (context.CommandException != null && !context.IsCommandExceptionHandled) { throw new Exception("Unhandled exception occured in the command!", context.CommandException); } return(result as Task ?? (result == null ? TaskUtils.GetCompletedTask() : Task.FromResult(result))); }
/// <summary> /// Process an individual request. /// </summary> public override Task Invoke(IOwinContext context) { // create the context var dotvvmContext = new DotvvmRequestContext() { OwinContext = context, Configuration = Configuration, ResourceManager = new ResourceManager(Configuration) }; // attempt to translate Googlebot hashbang espaced fragment URL to a plain URL string. string url; if (!TryParseGooglebotHashbangEscapedFragment(context.Request.QueryString, out url)) { url = context.Request.Path.Value; } url = url.Trim('/'); // find the route IDictionary <string, object> parameters = null; var route = Configuration.RouteTable.FirstOrDefault(r => r.IsMatch(url, out parameters)); if (route != null) { // handle the request dotvvmContext.Route = route; dotvvmContext.Parameters = parameters; dotvvmContext.Query = context.Request.Query .ToDictionary(d => d.Key, d => d.Value.Length == 1 ? (object)d.Value[0] : d.Value); return(route.ProcessRequest(dotvvmContext)); } else { // we cannot handle the request, pass it to another component return(Next.Invoke(context)); } }
/// <summary> /// Ensures the redirect required by the ASP.NET Core Security middleware is properly handled by DotVVM client library. /// </summary> public static Task ApplyRedirectResponse(HttpContext context, string redirectUri) { DotvvmRequestContextExtensions.SetRedirectResponse(DotvvmRequestContext.GetCurrent(DotvvmMiddleware.ConvertHttpContext(context)), redirectUri, (int)HttpStatusCode.Redirect, allowSpaRedirect: false); throw new DotvvmInterruptRequestExecutionException(); }
public void ProcessStaticCommandRequest(DotvvmRequestContext context) { JObject postData; using (var jsonReader = new JsonTextReader(new StreamReader(context.OwinContext.Request.Body))) { postData = JObject.Load(jsonReader); } // validate csrf token context.CsrfToken = postData["$csrfToken"].Value <string>(); CsrfProtector.VerifyToken(context, context.CsrfToken); var command = postData["command"].Value <string>(); var arguments = postData["args"] as JArray; var lastDot = command.LastIndexOf('.'); var typeName = command.Remove(lastDot); var methodName = command.Substring(lastDot + 1); var methodInfo = Type.GetType(typeName).GetMethod(methodName); if (!Attribute.IsDefined(methodInfo, typeof(StaticCommandCallableAttribute))) { throw new DotvvmHttpException("method validation failed"); } var target = methodInfo.IsStatic ? null : arguments[0].ToObject(methodInfo.DeclaringType); var marguments = arguments.Skip(1).Zip(methodInfo.GetParameters(), (arg, parameter) => arg.ToObject(parameter.ParameterType)).ToArray(); var actionInfo = new ActionInfo() { IsControlCommand = false, Action = () => methodInfo.Invoke(target, marguments) }; var filters = methodInfo.GetCustomAttributes <ActionFilterAttribute>(); foreach (var filter in filters) { filter.OnCommandExecuting(context, actionInfo); } Exception exception = null; object result = null; try { result = methodInfo.Invoke(target, marguments); } catch (Exception ex) { if (ex is TargetInvocationException) { ex = ex.InnerException; } if (ex is DotvvmInterruptRequestExecutionException) { throw new DotvvmInterruptRequestExecutionException("The request execution was interrupted in the command!", ex); } exception = ex; } foreach (var filter in filters) { filter.OnCommandExecuted(context, actionInfo, exception); } if (exception != null) { throw new Exception("unhandled exception in command", exception); } using (var writer = new StreamWriter(context.OwinContext.Response.Body)) { writer.WriteLine(JsonConvert.SerializeObject(result)); } }
public async Task ProcessRequestCore(DotvvmRequestContext context) { if (context.OwinContext.Request.Method != "GET" && context.OwinContext.Request.Method != "POST") { // unknown HTTP method context.OwinContext.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed; throw new DotvvmHttpException("Only GET and POST methods are supported!"); } if (context.OwinContext.Request.Headers["X-PostbackType"] == "StaticCommand") { ProcessStaticCommandRequest(context); return; } var isPostBack = DetermineIsPostBack(context.OwinContext); context.IsPostBack = isPostBack; context.ChangeCurrentCulture(context.Configuration.DefaultCulture); // build the page view var page = DotvvmViewBuilder.BuildView(context); page.SetValue(Internal.RequestContextProperty, context); // run the preinit phase in the page DotvvmControlCollection.InvokePageLifeCycleEventRecursive(page, LifeCycleEventType.PreInit); // locate and create the view model context.ViewModel = ViewModelLoader.InitializeViewModel(context, page); page.DataContext = context.ViewModel; // get action filters var globalFilters = context.Configuration.Runtime.GlobalFilters.ToList(); var viewModelFilters = context.ViewModel.GetType().GetCustomAttributes <ActionFilterAttribute>(true).ToList(); // run OnViewModelCreated on action filters foreach (var filter in globalFilters.Concat(viewModelFilters)) { filter.OnViewModelCreated(context); } // init the view model lifecycle if (context.ViewModel is IDotvvmViewModel) { ((IDotvvmViewModel)context.ViewModel).Context = context; await((IDotvvmViewModel)context.ViewModel).Init(); } // run the init phase in the page DotvvmControlCollection.InvokePageLifeCycleEventRecursive(page, LifeCycleEventType.Init); if (!isPostBack) { // perform standard get if (context.ViewModel is IDotvvmViewModel) { await((IDotvvmViewModel)context.ViewModel).Load(); } // run the load phase in the page DotvvmControlCollection.InvokePageLifeCycleEventRecursive(page, LifeCycleEventType.Load); } else { // perform the postback string postData; using (var sr = new StreamReader(context.OwinContext.Request.Body)) { postData = await sr.ReadToEndAsync(); } ViewModelSerializer.PopulateViewModel(context, postData); // validate CSRF token CsrfProtector.VerifyToken(context, context.CsrfToken); if (context.ViewModel is IDotvvmViewModel) { await((IDotvvmViewModel)context.ViewModel).Load(); } // validate CSRF token CsrfProtector.VerifyToken(context, context.CsrfToken); // run the load phase in the page DotvvmControlCollection.InvokePageLifeCycleEventRecursive(page, LifeCycleEventType.Load); // invoke the postback command ActionInfo actionInfo; ViewModelSerializer.ResolveCommand(context, page, postData, out actionInfo); if (actionInfo != null) { // get filters var methodFilters = actionInfo.Binding.ActionFilters == null?globalFilters.Concat(viewModelFilters).ToArray() : globalFilters.Concat(viewModelFilters).Concat(actionInfo.Binding.ActionFilters).ToArray(); // run OnCommandExecuting on action filters foreach (var filter in methodFilters) { filter.OnCommandExecuting(context, actionInfo); } Exception commandException = null; try { var result = actionInfo.Action(); if (result is Task) { await(Task) result; } } catch (Exception ex) { if (ex is TargetInvocationException) { ex = ex.InnerException; } if (ex is DotvvmInterruptRequestExecutionException) { throw new DotvvmInterruptRequestExecutionException("The request execution was interrupted in the command!", ex); } commandException = ex; } // run OnCommandExecuted on action filters foreach (var filter in methodFilters) { filter.OnCommandExecuted(context, actionInfo, commandException); } if (commandException != null && !context.IsCommandExceptionHandled) { throw new Exception("Unhandled exception occured in the command!", commandException); } } } if (context.ViewModel is IDotvvmViewModel) { await((IDotvvmViewModel)context.ViewModel).PreRender(); } // run the prerender phase in the page DotvvmControlCollection.InvokePageLifeCycleEventRecursive(page, LifeCycleEventType.PreRender); // run the prerender complete phase in the page DotvvmControlCollection.InvokePageLifeCycleEventRecursive(page, LifeCycleEventType.PreRenderComplete); // generate CSRF token if required if (string.IsNullOrEmpty(context.CsrfToken)) { context.CsrfToken = CsrfProtector.GenerateToken(context); } // run OnResponseRendering on action filters foreach (var filter in globalFilters.Concat(viewModelFilters)) { filter.OnResponseRendering(context); } // render the output ViewModelSerializer.BuildViewModel(context); if (!context.IsInPartialRenderingMode) { // standard get await OutputRenderer.WriteHtmlResponse(context, page); } else { // postback or SPA content OutputRenderer.RenderPostbackUpdatedControls(context, page); ViewModelSerializer.AddPostBackUpdatedControls(context); await OutputRenderer.WriteViewModelResponse(context, page); } if (context.ViewModel != null) { ViewModelLoader.DisposeViewModel(context.ViewModel); } }
public static DotvvmRequestContext GetDotvvmContext(this IOwinContext owinContext) { return(DotvvmRequestContext.GetCurrent(owinContext)); }