private bool HandleResponseForClientCache(HttpHeaderTools headerTools) { if (RequestedNodeHead == null) { return(false); } var cacheSetting = GetCacheHeaderSetting(headerTools); if (!cacheSetting.HasValue) { return(false); } // cache header (public or private) depends on whether the content is available for anyone var accessibleForVisitor = SystemAccount.Execute(() => Providers.Instance.SecurityHandler.HasPermission(User.Visitor, RequestedNodeHead.Id, PermissionType.Open)); // set MaxAge by type or extension or a global value as a fallback headerTools.SetCacheControlHeaders(cacheSetting.Value, accessibleForVisitor ? HttpCacheability.Public : HttpCacheability.Private); // in case of preview images do NOT return 304, because _undetectable_ permission changes // (on the image or on one of its parents) may change the preview image (e.g. display redaction or not). if (DocumentPreviewProvider.Current?.IsPreviewOrThumbnailImage(RequestedNodeHead) ?? false) { return(false); } // Handle If-Modified-Since and Last-Modified headers: end the response // if the content has not changed since the value posted by the client. var endResponse = headerTools.EndResponseForClientCache(RequestedNodeHead.ModificationDate); return(endResponse); }
/// <summary> /// Executes the boot sequence of the Repository by the passed <see cref="RepositoryStartSettings"/>. /// </summary> /// <example> /// Use the following code in your tool or other outer application: /// <code> /// var startSettings = new RepositoryStartSettings /// { /// PluginsPath = pluginsPath, // Local directory path of plugins if it is different from your tool's path. /// Console = Console.Out // Startup sequence will be traced to given writer. /// }; /// using (SenseNet.ContentRepository.Repository.Start(startSettings)) /// { /// // your code /// } /// </code> /// </example> /// <remarks> /// Repository will be stopped if the returned <see cref="RepositoryStartSettings"/> instance is disposed. /// </remarks> /// <returns>A new IDisposable <see cref="RepositoryInstance"/> instance.</returns> /// <returns></returns> public static RepositoryInstance Start(RepositoryStartSettings settings) { var instance = RepositoryInstance.Start(settings); SystemAccount.Execute(() => Root); return(instance); }
public static string GetAvatarUrl(Node node, int?width = null, int?height = null) { if (node == null) { return(string.Empty); } // Reload in elevated mode because we do not know how the provided // node has been loaded and we need its avatar url. var fullNode = SystemAccount.Execute(() => Node.LoadNode(node.Id)); var group = fullNode as Group; var user = fullNode as User; if (group != null) { var url = GetDefaultGroupAvatarPath(); url = AddWidthHeightParam(url, width, height); return(url); } if (user != null) { var avatarUrl = user.AvatarUrl; var url = string.IsNullOrEmpty(avatarUrl) ? GetDefaultUserAvatarPath() : avatarUrl; url = AddWidthHeightParam(url, width, height); return(url); } return(string.Empty); }
public static IApplicationBuilder UseSenseNetUser(this IApplicationBuilder app) { app.Use(async(context, next) => { // At this point the user is already authenticated, which means // we can trust the information in the identity: we load the // user in elevated mode (using system account). var identity = context?.User?.Identity; if (!string.IsNullOrEmpty(identity?.Name)) { // Currently we look for users by their login name. In the future we may add // a more flexible user discovery algorithm here. var user = SystemAccount.Execute(() => User.Load(identity.Name)); if (user != null) { User.Current = user; } else { SnTrace.Security.Write("Unknown user: {0}", identity.Name); } } await next.Invoke(); }); return(app); }
private static ServiceQueryContext GetQueryContext(SnQuery query, IQueryContext context) { return(new ServiceQueryContext { UserId = context.UserId, FieldLevel = PermissionFilter.GetFieldLevel(query).ToString(), DynamicGroups = SystemAccount.Execute(() => Node.Load <User>(context.UserId)?.GetDynamicGroups(0)?.ToArray() ?? new int[0]) }); }
private Stream GetConvertedStream(out string contentType, out BinaryFileName fileName) { // We have to treat images differently: the image handler takes care // of putting redactions on the preview image if permissions require that. if (RequestedNode is Image img) { var parameters = new Dictionary <string, object>(); if (Width.HasValue) { parameters.Add("width", Width.Value); } if (Height.HasValue) { parameters.Add("height", Height.Value); } if (Watermark != null) { parameters.Add("watermark", Watermark); } // At this point we are certain that the user can have the binary. // If the user does not have Open permission, it is still possible for them // to access a preview image. In this case we have to reload the image // to clear the headonly flag and let the user access the binary. if (img.IsPreviewOnly) { // ReSharper disable once AccessToModifiedClosure img = SystemAccount.Execute(() => Node.Load <Image>(img.Id)); } return(img.GetImageStream(PropertyName, parameters, out contentType, out fileName)); } // Get the stream through our provider to let 3rd party developers serve custom data. var stream = DocumentBinaryProvider.Instance.GetStream(RequestedNode, PropertyName, _context, out contentType, out fileName); if (stream == null) { return(null); } // try to treat the binary value as an image and resize it if (Width.HasValue && Height.HasValue) { return(Image.CreateResizedImageFile(stream, string.Empty, Width.Value, Height.Value, 0, contentType)); } return(stream); }
private static void AssertCommentFeature(Content content) { if (!(content?.ContentHandler is File)) { throw new SnNotSupportedException("Cannot comment on this type of content."); } var latestContent = content.IsLatestVersion ? content : SystemAccount.Execute(() => Content.Load(content.Id)); if (latestContent.ContentHandler.Locked && latestContent.ContentHandler.LockedById != User.Current.Id) { throw new InvalidOperationException("Content is locked by someone else."); } }
/// <summary> /// Executes the boot sequence of the Repository by the passed <see cref="RepositoryStartSettings"/>. /// </summary> /// <example> /// Use the following code in your tool or other outer application: /// <code> /// var startSettings = new RepositoryStartSettings /// { /// PluginsPath = pluginsPath, // Local directory path of plugins if it is different from your tool's path. /// Console = Console.Out // Startup sequence will be traced to given writer. /// }; /// using (SenseNet.ContentRepository.Repository.Start(startSettings)) /// { /// // your code /// } /// </code> /// </example> /// <remarks> /// Repository will be stopped if the returned <see cref="RepositoryStartSettings"/> instance is disposed. /// </remarks> /// <returns>A new IDisposable <see cref="RepositoryInstance"/> instance.</returns> /// <returns></returns> public static RepositoryInstance Start(RepositoryStartSettings settings) { if (!settings.ExecutingPatches) { // Switch ON this flag so that inner repository start operations // do not try to execute patches again recursively. settings.ExecutingPatches = true; //TODO: [auto-patch] this feature is not released yet //PackageManager.ExecuteAssemblyPatches(settings); } var instance = RepositoryInstance.Start(settings); SystemAccount.Execute(() => Root); return instance; }
public override MembershipExtension GetExtension(IUser user) { var context = HttpContext.Current; if (context != null && user != null) { //TODO: handle multiple group ids in context var cookieValue = context.Request.Cookies[Constants.SharingTokenKey]?.Value; var extension = SystemAccount.Execute(() => GetSharingExtension(context.Request.Params, cookieValue)); var extensionGroupId = extension.ExtensionIds.FirstOrDefault(); if (extensionGroupId > 0) { // the url value takes precedence var currentValue = context.Request.Params[Constants.SharingUrlParameterName]; if (string.IsNullOrEmpty(currentValue)) { currentValue = cookieValue; } // set cookie only if it is different from the current value if (currentValue != cookieValue) { SnTrace.Security.Write($"SharingMembershipExtender: setting sharing cookie containing group id {extensionGroupId}."); context.Response.Cookies.Set(new HttpCookie(Constants.SharingTokenKey, currentValue) { Expires = DateTime.UtcNow.AddDays(1) }); } } else if (cookieValue != null) { // No sharing group or invalid group or content: clear cookie // (only if there was a cookie before). context.Response.Cookies.Set(new HttpCookie(Constants.SharingTokenKey, string.Empty) { Expires = DateTime.UtcNow.AddDays(-1) }); } return(extension); } return(base.GetExtension(user)); }
public MembershipExtension GetExtension(IUser user) { if (_httpContext == null || user == null) { return(MembershipExtension.Placeholder); } //TODO: handle multiple group ids in context var cookieValue = _httpContext.Request.Cookies[Constants.SharingTokenKey]; var extension = SystemAccount.Execute(() => GetSharingExtension(cookieValue)); var extensionGroupId = extension.ExtensionIds.FirstOrDefault(); if (extensionGroupId > 0) { // the url value takes precedence var currentValue = _httpContext.Request.Query[Constants.SharingUrlParameterName]; if (string.IsNullOrEmpty(currentValue)) { currentValue = cookieValue; } // set cookie only if it is different from the current value if (currentValue != cookieValue) { SnTrace.Security.Write($"SharingMembershipExtender: setting sharing cookie containing group id {extensionGroupId}."); _httpContext.Response.Cookies.Append(Constants.SharingTokenKey, currentValue, new CookieOptions { Expires = DateTime.UtcNow.AddDays(1), HttpOnly = true, Secure = true }); } } else if (cookieValue != null) { // No sharing group or invalid group or content: clear cookie // (only if there was a cookie before). _httpContext.Response.Cookies.Delete(Constants.SharingTokenKey); } return(extension); }
private void ResetContent(Content content) { // Create "dummy" content var newContent = SystemAccount.Execute(() => { return(Content.CreateNew(content.ContentType.Name, content.ContentHandler.Parent, null)); }); Aspect[] aspects = null; if (content.ContentHandler.HasProperty(GenericContent.ASPECTS)) { // Get aspects aspects = content.ContentHandler.GetReferences(GenericContent.ASPECTS).Cast <Aspect>().ToArray(); // Reset aspect fields var gc = content.ContentHandler as GenericContent; if (gc != null) { content.RemoveAllAspects(); gc.AspectData = null; gc.ClearReference(GenericContent.ASPECTS); } } // Reset regular fields foreach (var field in content.Fields.Values) { var fieldName = field.Name; if (newContent.Fields.Any(f => f.Value.Name == fieldName) && !field.ReadOnly && !SafeFieldsInReset.Contains(fieldName)) { content[fieldName] = newContent[fieldName]; } } if (content.ContentHandler.HasProperty(GenericContent.ASPECTS)) { // Re-add all the aspects content.AddAspects(aspects); } }
private bool ContentTypeIsValid() { // The content type is valid if the parent content has an empty ContentTypes list (any type is allowed), // or the type of the content to be created is among the allowed content types (or is a derived type). if (_currentContent == null) { return(true); } var parent = SystemAccount.Execute(() => { return(_currentContent.ContentHandler.Parent as GenericContent); }); if (parent == null) { return(true); } if (_currentContent.ContentType.IsInstaceOfOrDerivedFrom("FieldSettingContent") && parent is ContentList) { return(true); } return(parent.GetAllowedChildTypeNames().Any(ctn => ctn == _currentContent.ContentType.Name)); }
private static void EnsureIndex(RepositoryBuilder builder) { var logger = builder.Services?.GetService <ILogger <RepositoryInstance> >(); logger?.LogInformation("Checking the index..."); // execute a query that should return multiple items if the index is not empty var indexDocExist = SystemAccount.Execute(() => ContentQuery.Query(SafeQueries.ContentTypes, QuerySettings.AdminSettings).Count > 10); if (indexDocExist) { return; } // This scenario auto-generates the whole index from the database. The most common case is // when a new web app domain (usually a container) is started in a load balanced environment. var populator = Providers.Instance.SearchManager.GetIndexPopulator(); var indexCount = 0; populator.IndexingError += (sender, eventArgs) => { logger?.LogWarning($"Error during building app start index for {eventArgs.Path}. " + $"(id: {eventArgs.NodeId}). {eventArgs.Exception?.Message}"); }; populator.NodeIndexed += (sender, eventArgs) => { Interlocked.Increment(ref indexCount); }; logger?.LogInformation("Rebuilding the index..."); populator.ClearAndPopulateAllAsync(CancellationToken.None, builder.Console ?? new LoggerConsole(logger)).GetAwaiter().GetResult(); logger?.LogInformation($"Indexing of {indexCount} nodes finished."); }
public PreviewComment(PreviewCommentData data) { Data = data; // Workaround: we only have a domain\username information here that cannot be used // to load a node head. We have to try to load the whole user node in elevated mode // and check for permissions after. var caller = AccessProvider.Current.GetOriginalUser(); var user = SystemAccount.Execute(() => string.IsNullOrEmpty(data.CreatedBy) ? null : User.Load(data.CreatedBy)); if (user == null || !SecurityHandler.HasPermission(caller, user.Id, PermissionType.Open)) { user = User.Somebody; } CreatedBy = new PreviewCommentUser { Id = user.Id, Path = user.Path, Username = user.Username, DisplayName = user.DisplayName, AvatarUrl = user.AvatarUrl }; }
internal static ODataRequest Parse(string path, PortalContext portalContext) { if (!portalContext.IsOdataRequest) { throw new InvalidOperationException("The Request is not an OData request."); } var req = new ODataRequest(); try { var relPath = path.Substring(path.IndexOf(Configuration.Services.ODataServiceToken, StringComparison.OrdinalIgnoreCase) + Configuration.Services.ODataServiceToken.Length); req.IsServiceDocumentRequest = relPath.Length == 0; var segments = relPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); var resSegments = new List <string>(); var prmSegments = new List <string>(); req.IsCollection = true; for (var i = 0; i < segments.Length; i++) { if (String.Compare(segments[i], "$metadata", StringComparison.OrdinalIgnoreCase) == 0) { req.IsMetadataRequest = true; break; } if (String.Compare(segments[i], "$value", StringComparison.OrdinalIgnoreCase) == 0) { req.IsRawValueRequest = true; break; } if (String.Compare(segments[i], "$count", StringComparison.OrdinalIgnoreCase) == 0) { req.CountOnly = true; break; } if (!req.IsCollection) { prmSegments.Add(segments[i]); continue; } var segment = segments[i]; if (i == 0 && segment.StartsWith("content(", StringComparison.OrdinalIgnoreCase) && segment.EndsWith(")")) { var idStr = segment.Substring(8).Trim('(', ')'); if (idStr[0] != '\'' && idStr[idStr.Length - 1] != '\'') { int id; if (!int.TryParse(idStr, out id)) { throw new ODataException(SNSR.Exceptions.OData.InvalidId, ODataExceptionCode.InvalidId); } req.IsCollection = false; req.RequestedContentId = id; resSegments.Add(segment); continue; } } if (!segment.EndsWith("')")) { resSegments.Add(segment); } else { req.IsCollection = false; var ii = segment.IndexOf("('"); var entityName = segment.Substring(ii).Trim('(', ')').Trim('\''); segment = segment.Substring(0, ii); if (segment.Length > 0) { resSegments.Add(segment); } resSegments.Add(entityName); } } var pathIsRelative = true; if (resSegments.Count == 0) { req.RepositoryPath = portalContext.Site?.Path; if (req.RepositoryPath == null) { pathIsRelative = false; } req.IsCollection = true; } else { pathIsRelative = String.Compare(resSegments[0], "root", StringComparison.OrdinalIgnoreCase) != 0; if (pathIsRelative) { pathIsRelative = !resSegments[0].StartsWith("root(", StringComparison.OrdinalIgnoreCase); } if (prmSegments.Count > 0) { req.IsMemberRequest = true; req.PropertyName = prmSegments[0]; } } Content content; if (req.RequestedContentId > 0 && (content = SystemAccount.Execute(() => Content.Load(req.RequestedContentId))) != null) { req.RepositoryPath = content.Path; } else { var newPath = String.Concat("/", String.Join("/", resSegments)); if (pathIsRelative) { if (portalContext.Site == null) { newPath = "/"; } else { newPath = newPath == "/" ? portalContext.Site.Path : string.Concat(portalContext.Site.Path, newPath); } } req.RepositoryPath = newPath; } req.ParseQuery(path, portalContext); } catch (Exception e) { req.RequestError = e; } return(req); }
/// <summary> /// Returns a new <see cref="TrashBag"/> instance that packages the /// given <see cref="GenericContent"/> instance. /// </summary> /// <param name="node">The <see cref="GenericContent"/> instance that will be wrapped.</param> public static TrashBag BagThis(GenericContent node) { var bin = TrashBin.Instance; if (bin == null) { return(null); } if (node == null) { throw new ArgumentNullException("node"); } // creating a bag has nothing to do with user permissions: Move will handle that TrashBag bag = null; var wsId = 0; var wsRelativePath = string.Empty; var ws = SystemAccount.Execute(() => node.Workspace); if (ws != null) { wsId = ws.Id; wsRelativePath = node.Path.Substring(ws.Path.Length); } using (new SystemAccount()) { bag = new TrashBag(bin) { KeepUntil = DateTime.UtcNow.AddDays(bin.MinRetentionTime), OriginalPath = RepositoryPath.GetParentPath(node.Path), WorkspaceRelativePath = wsRelativePath, WorkspaceId = wsId, DisplayName = node.DisplayName, Link = node, Owner = node.Owner }; bag.Save(); CopyPermissions(node, bag); // add delete permission for the owner SecurityHandler.CreateAclEditor() .Allow(bag.Id, node.OwnerId, false, PermissionType.Delete) .Apply(); } try { Node.Move(node.Path, bag.Path); } catch (Exception ex) { SnLog.WriteException(ex); bag.Destroy(); throw new InvalidOperationException("Error moving item to the trash", ex); } return(bag); }
protected virtual void BuildSearchForm() { if (string.IsNullOrEmpty(SearchFormCtd)) { return; } var nt = (from t in ActiveSchema.NodeTypes where t.NodeTypePath.Equals(SearchFormCtd.Remove(0, 33)) select t).FirstOrDefault(); if (nt == null) { return; } var parent = SystemAccount.Execute(() => Repository.SystemFolder); var c = Content.CreateNew(nt.Name, parent, null); var s = State; if (_state != null && !string.IsNullOrWhiteSpace(_state.ExportQueryFields)) { var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(_state.ExportQueryFields); XmlNodeList allFields = xmlDoc.SelectNodes("/ContentMetaData/Fields/*"); // do this in elevated mode to avoid errors related to non-writable fields, like CreationDate using (new SystemAccount()) { var transferringContext = new ImportContext(allFields, "", c.Id == 0, true, false); // import flat properties c.ImportFieldData(transferringContext, false); // update references transferringContext.UpdateReferences = true; c.ImportFieldData(transferringContext, false); } } // override content filed from url parameters foreach (KeyValuePair <string, Field> keyValuePair in c.Fields) { var portletSpecKey = GetPortletSpecificParamName(keyValuePair.Key); var requestValue = GetValueFromRequest(portletSpecKey); if (!string.IsNullOrEmpty(requestValue) && c.Fields.ContainsKey(keyValuePair.Key)) { c.Fields[keyValuePair.Key].Parse(requestValue); } } var cv = string.IsNullOrEmpty(SearchFormRenderer) ? ContentView.Create(c, this.Page, ViewMode.InlineNew) : ContentView.Create(c, this.Page, ViewMode.InlineNew, SearchFormRenderer); // Attach search event var iCsView = cv as IContentSearchView; if (iCsView != null) { iCsView.Search += new EventHandler(ContentSearchView_Search_OnClick); } else { var btn = cv.FindControl(SearchBtnId) as Button; if (btn == null) { btn = new Button { ID = SearchBtnId, Text = SenseNetResourceManager.Current.GetString(ResourceClassName, "SearchBtnText"), CssClass = "sn-submit" }; cv.Controls.Add(btn); } btn.Click += new EventHandler(ContentSearchView_Search_OnClick); } SearchForm = cv; }
private static bool IsAdmin() { return(SystemAccount.Execute(() => AccessProvider.Current.GetOriginalUser().IsInGroup(Group.Administrators))); }
protected virtual void Application_Error(object sender, EventArgs e, HttpApplication application) { int?originalHttpCode = null; var ex = application.Server.GetLastError(); var httpException = ex as HttpException; if (httpException != null) { originalHttpCode = httpException.GetHttpCode(); } var unknownActionException = ex as UnknownActionException; if (unknownActionException != null) { SnTrace.Web.Write("UnknownActionException: " + unknownActionException.Message); originalHttpCode = 404; } // if httpcode is contained in the dontcare list (like 404), don't log the exception var skipLogException = originalHttpCode.HasValue && dontCareErrorCodes.Contains(originalHttpCode.Value); if (!skipLogException) { try { SnLog.WriteException(ex); } catch { // if logging failed, cannot do much at this point } } if (ex.InnerException?.StackTrace != null && (ex.InnerException.StackTrace.IndexOf("System.Web.UI.PageParser.GetCompiledPageInstanceInternal", StringComparison.InvariantCulture) != -1)) { return; } if (HttpContext.Current == null) { return; } HttpResponse response; try { response = HttpContext.Current.Response; } catch (Exception) { response = null; } response?.Headers.Remove("Content-Disposition"); // HACK: HttpAction.cs (and possibly StaticFileHandler) throws 404 and 403 HttpExceptions. // These are not exceptions to be displayed, but "fake" exceptions to handle 404 and 403 requests. // Therefore, here we set the statuscode and return, no further logic is executed. if (originalHttpCode.HasValue && (originalHttpCode == 404 || originalHttpCode == 403)) { if (response != null) { response.StatusCode = originalHttpCode.Value; } HttpContext.Current.ClearError(); HttpContext.Current.ApplicationInstance.CompleteRequest(); return; } var errorPageHtml = string.Empty; var exception = ex; if (exception.InnerException != null) { exception = exception.InnerException; } var statusCode = GetStatusCode(exception); if (response != null) { if (!HttpContext.Current.Request.Url.AbsoluteUri.StartsWith("http://localhost")) { if (originalHttpCode.HasValue) { response.StatusCode = originalHttpCode.Value; } // If there is a specified status code in statusCodeString then set Response.StatusCode to it. // Otherwise go on to global error page. if (statusCode != null) { application.Response.StatusCode = statusCode.StatusCode; application.Response.SubStatusCode = statusCode.SubStatusCode; response.Clear(); HttpContext.Current.ClearError(); HttpContext.Current.ApplicationInstance.CompleteRequest(); return; } application.Response.TrySkipIisCustomErrors = true; // keeps our custom error page defined below instead of using the page of IIS - works in IIS7 only if (application.Response.StatusCode == 200) { application.Response.StatusCode = 500; } Node globalErrorNode = null; var site = Site.Current; if (site != null) { var path = string.Concat("/Root/System/ErrorMessages/", site.Name, "/UserGlobal.html"); globalErrorNode = SystemAccount.Execute(() => Node.LoadNode(path)); } if (globalErrorNode != null) { var globalBinary = globalErrorNode.GetBinary("Binary"); var stream = globalBinary.GetStream(); if (stream != null) { var str = new StreamReader(stream); errorPageHtml = str.ReadToEnd(); } } else { errorPageHtml = GetDefaultUserErrorPageHtml(application.Server.MapPath("/"), true); } } else { // if the page is requested from localhost errorPageHtml = GetDefaultLocalErrorPageHtml(application.Server.MapPath("/"), true); } } else { // TODO: SQL Error handling } errorPageHtml = InsertErrorMessagesIntoHtml(exception, errorPageHtml); application.Response.TrySkipIisCustomErrors = true; // If there is a specified status code in statusCodeString then set Response.StatusCode to it. // Otherwise go on to global error page. if (statusCode != null) { application.Response.StatusCode = statusCode.StatusCode; application.Response.SubStatusCode = statusCode.SubStatusCode; response?.Clear(); HttpContext.Current.ClearError(); HttpContext.Current.ApplicationInstance.CompleteRequest(); } else { if (application.Response.StatusCode == 200) { application.Response.StatusCode = 500; } } if (response != null) { response.Clear(); response.Write(errorPageHtml); } HttpContext.Current.ClearError(); HttpContext.Current.ApplicationInstance.CompleteRequest(); }
/// <summary> /// Returns a new <see cref="TrashBag"/> instance that packages the /// given <see cref="GenericContent"/> instance. /// </summary> /// <param name="node">The <see cref="GenericContent"/> instance that will be wrapped.</param> public static TrashBag BagThis(GenericContent node) { var bin = TrashBin.Instance; if (bin == null) { return(null); } if (node == null) { throw new ArgumentNullException("node"); } // creating a bag has nothing to do with user permissions: Move will handle that TrashBag bag = null; var currentUserId = User.Current.Id; var wsId = 0; var wsRelativePath = string.Empty; var ws = SystemAccount.Execute(() => node.Workspace); if (ws != null) { wsId = ws.Id; wsRelativePath = node.Path.Substring(ws.Path.Length); } using (new SystemAccount()) { bag = new TrashBag(bin) { KeepUntil = DateTime.UtcNow.AddDays(bin.MinRetentionTime), OriginalPath = RepositoryPath.GetParentPath(node.Path), WorkspaceRelativePath = wsRelativePath, WorkspaceId = wsId, DisplayName = node.DisplayName, Link = node, Owner = node.Owner }; bag.Save(); CopyPermissions(node, bag); // Add Delete permission for the owner to let them remove it later and also // AddNew permission to let the move operation below actually move // the content into the TrashBag. Providers.Instance.SecurityHandler.CreateAclEditor() .Allow(bag.Id, node.OwnerId, false, PermissionType.Delete, PermissionType.AddNew) .Allow(bag.Id, currentUserId, true, PermissionType.Delete, PermissionType.AddNew) .Apply(); } try { Node.Move(node.Path, bag.Path); } catch (SenseNetSecurityException ex) { SnLog.WriteException(ex); bag.Destroy(); if (ex.Data.Contains("PermissionType") && (string)ex.Data["PermissionType"] == "Delete") { throw new InvalidOperationException("You do not have enough permissions to delete this content to the Trash.", ex); } throw new InvalidOperationException("Error moving item to the trash", ex); } catch (Exception ex) { SnLog.WriteException(ex); bag.Destroy(); throw new InvalidOperationException("Error moving item to the trash", ex); } return(bag); }