The solution we came to was to modify and customize the CKFinder ASP Connector. Big thanks to the CKSource team for helping us to get this running.
ConnectorConfig.cs
namespace CKSource.CKFinder.Connector.WebApp
{
using System.Configuration;
using System.Linq;
using CKSource.CKFinder.Connector.Config;
using CKSource.CKFinder.Connector.Core.Acl;
using CKSource.CKFinder.Connector.Core.Builders;
using CKSource.CKFinder.Connector.Host.Owin;
using CKSource.CKFinder.Connector.KeyValue.FileSystem;
using CKSource.FileSystem.Amazon;
//using CKSource.FileSystem.Azure;
//using CKSource.FileSystem.Dropbox;
//using CKSource.FileSystem.Ftp;
using CKSource.FileSystem.Local;
using Owin;
public class ConnectorConfig
{
public static void RegisterFileSystems()
{
FileSystemFactory.RegisterFileSystem<LocalStorage>();
//FileSystemFactory.RegisterFileSystem<DropboxStorage>();
FileSystemFactory.RegisterFileSystem<AmazonStorage>();
//FileSystemFactory.RegisterFileSystem<AzureStorage>();
//FileSystemFactory.RegisterFileSystem<FtpStorage>();
}
public static void SetupConnector(IAppBuilder builder)
{
var allowedRoleMatcherTemplate = ConfigurationManager.AppSettings["ckfinderAllowedRole"];
var authenticator = new RoleBasedAuthenticator(allowedRoleMatcherTemplate);
var connectorFactory = new OwinConnectorFactory();
var connectorBuilder = new ConnectorBuilder();
var connector = connectorBuilder
.LoadConfig()
.SetAuthenticator(authenticator)
.SetRequestConfiguration(
(request, config) =>
{
config.LoadConfig();
var defaultBackend = config.GetBackend("default");
var keyValueStoreProvider = new FileSystemKeyValueStoreProvider(defaultBackend);
config.SetKeyValueStoreProvider(keyValueStoreProvider);
// Remove dummy resource type
config.RemoveResourceType("dummy");
var queryParameters = request.QueryParameters;
// This code lacks some input validation - make sure the user is allowed to access passed appId
string appId = queryParameters.ContainsKey("appId") ? Enumerable.FirstOrDefault(queryParameters["appId"]) : string.Empty;
// set up an array of StringMatchers for folder to hide!
StringMatcher[] hideFoldersMatcher = new StringMatcher[] { new StringMatcher(".*"), new StringMatcher("CVS"), new StringMatcher("thumbs"), new StringMatcher("__thumbs") };
// image type resource setup
var fileSystem_Images = new AmazonStorage(secret: "SECRET-HERE",
key: "KEY-HERE",
bucket: "BUCKET-HERE",
region: "us-east-1",
root: string.Format("images/{0}/userimages/", appId),
signatureVersion: "4");
string[] allowedExtentions_Images = new string[] {"gif","jpeg","jpg","png"};
config.AddBackend("s3Images", fileSystem_Images, string.Format("CDNURL-HERE/images/{0}/userimages/", appId), false);
config.AddResourceType("Images", resourceBuilder => {
resourceBuilder.SetBackend("s3Images", "/")
.SetAllowedExtensions(allowedExtentions_Images)
.SetHideFoldersMatchers(hideFoldersMatcher)
.SetMaxFileSize( 5242880 );
});
// file type resource setup
var fileSystem_Files = new AmazonStorage(secret: "SECRET-HERE",
key: "KEY-HERE",
bucket: "BUCKET-HERE",
region: "us-east-1",
root: string.Format("docs/{0}/userfiles/", appId),
signatureVersion: "4");
string[] allowedExtentions_Files = new string[] {"csv","doc","docx","gif","jpeg","jpg","ods","odt","pdf","png","ppt","pptx","rtf","txt","xls","xlsx"};
config.AddBackend("s3Files", fileSystem_Files, string.Format("CDNURL-HERE/docs/{0}/userfiles/", appId), false);
config.AddResourceType("Files", resourceBuilder => {
resourceBuilder.SetBackend("s3Files", "/")
.SetAllowedExtensions(allowedExtentions_Files)
.SetHideFoldersMatchers(hideFoldersMatcher)
.SetMaxFileSize( 10485760 );
});
})
.Build(connectorFactory);
builder.UseConnector(connector);
}
}
}
Items of note:
- Added
using System.Linq;
so that FirstOrDefault
works when getting the appId
- We removed some of the fileSystems (Azure,Dropbox,Ftp) because we do not use those in our integration
- In the CKFinder
web.config
file, we create a 'dummy' resource type because the Finder requires at least one to be present, but we then remove it during connector config and replace it with our desired resource types <resourceTypes><resourceType name="dummy" backend="default"></resourceType>resourceTypes>
- Please note and take care that you're placing some sensitive information in this file. Please consider how you version control this (or not) and you may want to take additional actions to make this more secure
Initializing a CKEditor4/CKFinder3 instance
<script src="/js/ckeditor/ckeditor.js"></script>
<script src="/js/ckfinder3/ckfinder.js"></script>
<script type="text/javascript">
var myEditor = CKEDITOR.replace( 'bodyContent', {
toolbar: 'Default',
width: '100%',
startupMode: 'wysiwyg',
filebrowserBrowseUrl: '/js/ckfinder3/ckfinder.html?type=Files&appId=12345',
filebrowserUploadUrl: '/js/ckfinder3/connector?command=QuickUpload&type=Files&appId=12345',
filebrowserImageBrowseUrl: '/js/ckfinder3/ckfinder.html?type=Images&appId=12345',
filebrowserImageUploadUrl: '/js/ckfinder3/connector?command=QuickUpload&type=Images&appId=12345',
uploadUrl: '/js/ckfinder3/connector?command=QuickUpload&type=Images&responseType=json&appId=12345'
});
</script>
Items of note:
- Due to other integration requirements, are using the Manual Integration method here, which requires us to manually define our filebrowserUrls
- Currently, adding
&pass=appId
to your filebrowserUrls or adding config.pass = 'appId';
to your config.js
file does not properly pass
the desired value through to the editor
- I believe this only fails when using the Manual Integration method (it should work correctly if you're using
CKFinder.setupCKEditor()
)
ckfinder.html
<!DOCTYPE html>
<!--
Copyright (c) 2007-2019, CKSource - Frederico Knabben. All rights reserved.
For licensing, see LICENSE.html or https://ckeditor.com/sales/license/ckfinder
-->
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>CKFinder 3 - File Browser</title>
</head>
<body>
<script src="ckfinder.js"></script>
<script>
var urlParams = new URLSearchParams( window.location.search );
var myAppId = ( urlParams.has( 'appId' ) ) ? urlParams.get( 'appId' ) : '';
if ( myAppId !== '' ) {
CKFinder.start( { pass: 'appId', appId: myAppId } );
} else {
document.write( 'Error loading configuration.' );
}
</script>
</body>
</html>
Items of note:
- This all seems to work much more smoothly when integrating into CKEditor5, but when integrating into CKEditor4, we experience a lot of issues getting the appId value to
pass
properly into the editor when utilizing the Manual Integration method for CKFinder
- We modify the ckfinder.html file here to look for the desired url params and
pass
them into the CKFinder instance as it's started. This ensures they are passed through the entirety of the Finder instance
- Check out this question for some great further details about this process as well as a more generic method of passing n params into your Finder instances: How do I pass custom values to CKFinder3 when instantiating a CKEditor4 instance?