public IEnumerable <AdamItem> Folder(int appId, string contentType, Guid guid, string field, string subfolder, string newFolder, bool usePortalRoot) { Log.Add($"get folders for a:{appId}, i:{guid}, field:{field}, subfld:{subfolder}, new:{newFolder}, useRoot:{usePortalRoot}"); var state = new AdamSecureState(BlockBuilder, appId, contentType, field, guid, usePortalRoot, Log); if (state.UserIsRestricted && !state.FieldPermissionOk(GrantSets.ReadSomething)) { return(null); } // get root and at the same time auto-create the core folder in case it's missing (important) var folder = state.ContainerContext.Folder(); // try to see if we can get into the subfolder - will throw error if missing if (!string.IsNullOrEmpty(subfolder)) { folder = state.ContainerContext.Folder(subfolder, false); } // start with a security check... var dnnFolder = FolderManager.Instance.GetFolder(folder.Id); // validate that dnn user have write permissions for folder in case dnn file system is used (usePortalRoot) if (usePortalRoot && !SecurityChecks.CanEdit(dnnFolder)) { throw Http.PermissionDenied("can't create new folder - permission denied"); } var newFolderPath = string.IsNullOrEmpty(subfolder) ? newFolder : Path.Combine(subfolder, newFolder).Replace("\\", "/"); // now access the subfolder, creating it if missing (which is what we want state.ContainerContext.Folder(newFolderPath, true); return(Items(appId, contentType, guid, field, subfolder, usePortalRoot)); }
internal AdamItem(IFolderInfo original, bool usePortalRoot, AdamSecureState state) { IsFolder = true; Id = original.FolderID; ParentId = original.ParentID; Path = original.DisplayPath; Name = original.DisplayName; Size = 0; Type = "folder"; Created = original.CreatedOnDate; Modified = original.LastModifiedOnDate; AllowEdit = usePortalRoot ? SecurityChecks.CanEdit(original) : !state.UserIsRestricted || state.FieldPermissionOk(GrantSets.WriteSomething); }
internal AdamItem(IFileInfo original, bool usePortalRoot, AdamSecureState state) { IsFolder = false; Id = original.FileId; ParentId = original.FolderId; Path = original.RelativePath; Name = original.FileName; Size = original.Size; Type = "unknown"; // will be set from the outside Created = original.CreatedOnDate; Modified = original.LastModifiedOnDate; AllowEdit = usePortalRoot ? SecurityChecks.CanEdit(original) : !state.UserIsRestricted || state.FieldPermissionOk(GrantSets.WriteSomething); }
public bool ExtensionIsOk(string fileName, out HttpResponseException preparedException) { if (!SecurityChecks.IsAllowedDnnExtension(fileName)) { preparedException = Http.NotAllowedFileType(fileName, "Not in whitelisted CMS file types."); return(false); } if (SecurityChecks.IsKnownRiskyExtension(fileName)) { preparedException = Http.NotAllowedFileType(fileName, "This is a known risky file type."); return(false); } preparedException = null; return(true); }
/// <summary> /// Initializes the object and performs all the initial security checks /// </summary> public AdamSecureState(IBlockBuilder blockBuilder, int appId, string contentType, string field, Guid guid, bool usePortalRoot, ILog log) : base(blockBuilder, appId, contentType, log) { // only do checks on field/guid if it's actually accessing that, if it's on the portal root, don't. if (!usePortalRoot) { Field = field; Guid = guid; } var firstChecker = PermissionCheckers.First().Value; var userMayAdminSomeFiles = firstChecker.UserMay(GrantSets.WritePublished); UserMayAdminSiteFiles = firstChecker.GrantedBecause == Conditions.EnvironmentGlobal || firstChecker.GrantedBecause == Conditions.EnvironmentInstance; UserIsRestricted = !(usePortalRoot ? UserMayAdminSiteFiles : userMayAdminSomeFiles); Log.Add($"AdamSecureState - field:{field}, guid:{guid}, adminSome:{userMayAdminSomeFiles}, restricted:{UserIsRestricted}"); SecurityChecks.ThrowIfAccessingRootButNotAllowed(usePortalRoot, UserIsRestricted); Log.Add("check if feature enabled"); if (UserIsRestricted && !Feats.Enabled(FeaturesForRestrictedUsers)) { throw Http.PermissionDenied( $"low-permission users may not access this - {Feats.MsgMissingSome(FeaturesForRestrictedUsers)}"); } PrepCore(App, guid, field, usePortalRoot); if (string.IsNullOrEmpty(contentType) || string.IsNullOrEmpty(field)) { return; } Attribute = Definition(appId, contentType, field); if (!FileTypeIsOkForThisField(out var exp)) { throw exp; } }
public bool SuperUserOrAccessingItemFolder(string path, out HttpResponseException preparedException) { preparedException = null; return(!UserIsRestricted || SecurityChecks.DestinationIsInItem(Guid, Field, path, out preparedException)); }
public bool Rename(int appId, string contentType, Guid guid, string field, string subfolder, bool isFolder, int id, string newName, bool usePortalRoot) { Log.Add($"rename a:{appId}, i:{guid}, field:{field}, subf:{subfolder}, isfld:{isFolder}, new:{newName}, useRoot:{usePortalRoot}"); var state = new AdamSecureState(BlockBuilder, appId, contentType, field, guid, usePortalRoot, Log); if (!state.UserIsPermittedOnField(GrantSets.WriteSomething, out var exp)) { throw exp; } // check that if the user should only see drafts, he doesn't see items of published data if (!state.UserIsNotRestrictedOrItemIsDraft(guid, out var permissionException)) { throw permissionException; } // try to see if we can get into the subfolder - will throw error if missing var current = state.ContainerContext.Folder(subfolder, false); if (isFolder) { var folderManager = FolderManager.Instance; var fld = folderManager.GetFolder(id); // validate that dnn user have write permissions for folder in case dnn file system is used (usePortalRoot) if (usePortalRoot && !SecurityChecks.CanEdit(fld)) { throw Http.PermissionDenied("can't rename folder - permission denied"); } if (!state.SuperUserOrAccessingItemFolder(fld.PhysicalPath, out exp)) { throw exp; } if (fld.ParentID != current.Id) { throw Http.BadRequest("can't rename folder - not found in folder"); } folderManager.RenameFolder(fld, newName); } else { var fileManager = FileManager.Instance; var file = fileManager.GetFile(id); // validate that dnn user have write permissions for folder where is file in case dnn file system is used (usePortalRoot) if (usePortalRoot && !SecurityChecks.CanEdit(file)) { throw Http.PermissionDenied("can't rename file - permission denied"); } if (!state.SuperUserOrAccessingItemFolder(file.PhysicalPath, out exp)) { throw exp; } if (file.FolderId != current.Id) { throw Http.BadRequest("can't rename file - not found in folder"); } // never allow to change the extension if (file.Extension != newName.Split('.').Last()) { newName += "." + file.Extension; } fileManager.RenameFile(file, newName); } Log.Add("rename complete"); return(true); }
public IFile UploadOne(Stream stream, string originalFileName, string contentType, Guid guid, string field, string subFolder, bool usePortalRoot, bool skipFieldAndContentTypePermissionCheck) { Log.Add($"upload one a:{_appId}, i:{guid}, field:{field}, subfold:{subFolder}, useRoot:{usePortalRoot}"); var state = new AdamSecureState(BlockBuilder, _appId, contentType, field, guid, usePortalRoot, Log); HttpResponseException exp; if (!skipFieldAndContentTypePermissionCheck) { if (!state.UserIsPermittedOnField(GrantSets.WriteSomething, out exp)) { throw exp; } // check that if the user should only see drafts, he doesn't see items of published data if (!state.UserIsNotRestrictedOrItemIsDraft(guid, out var permissionException)) { throw permissionException; } } var folder = state.ContainerContext.Folder(); if (!string.IsNullOrEmpty(subFolder)) { folder = state.ContainerContext.Folder(subFolder, false); } // start with a security check... var dnnFolder = FolderManager.Instance.GetFolder(folder.Id); // validate that dnn user have write permissions for folder in case dnn file system is used (usePortalRoot) if (usePortalRoot && !SecurityChecks.CanEdit(dnnFolder)) { throw Http.PermissionDenied("can't upload - permission denied"); } // we only upload into valid adam if that's the scenario if (!state.SuperUserOrAccessingItemFolder(dnnFolder.PhysicalPath, out exp)) { throw exp; } #region check content-type extensions... // Check file size and extension var fileName = String.Copy(originalFileName); if (!state.ExtensionIsOk(fileName, out exp)) { throw exp; } // check metadata of the FieldDef to see if still allowed extension var additionalFilter = state.Attribute.Metadata.GetBestValue <string>("FileFilter"); if (!string.IsNullOrWhiteSpace(additionalFilter) && !state.CustomFileFilterOk(additionalFilter, originalFileName)) { throw Http.NotAllowedFileType(fileName, "field has custom file-filter, which doesn't match"); } // note 2018-04-20 2dm: can't do this for wysiwyg, as it doesn't have a setting for allowed file-uploads #endregion if (stream.Length > 1024 * MaxFileSizeKb) { throw new Exception($"file too large - more than {MaxFileSizeKb}Kb"); } // remove forbidden / troubling file name characters fileName = fileName .Replace("+", "plus") .Replace("%", "per") .Replace("#", "hash"); if (fileName != originalFileName) { Log.Add($"cleaned file name from'{originalFileName}' to '{fileName}'"); } // Make sure the image does not exist yet, cycle through numbers (change file name) fileName = RenameFileToNotOverwriteExisting(fileName, dnnFolder); // Everything is ok, add file to DNN var dnnFile = FileManager.Instance.AddFile(dnnFolder, Path.GetFileName(fileName), stream); var adamcontext = new AdamAppContext(BlockBuilder.Block.Tenant, state.App, BlockBuilder, 10, Log); var eavFile = new File(adamcontext) { Created = dnnFile.CreatedOnDate, Extension = dnnFile.Extension, FullName = dnnFile.FileName, Folder = dnnFile.Folder, FolderId = dnnFile.FolderId, Id = dnnFile.FileId, Modified = dnnFile.LastModificationTime }; return(eavFile); }