为每个 CKFinder 3 实例设置 S3 存储桶的公共键前缀

Set common key prefix for S3 bucket per CKFinder 3 instance

如何使 CKFinder ASP.net S3 集成从动态键前缀加载内容,而不仅仅是根位置?


我将 CKEditor 5 和 CKFinder 3 与 ASP.net 连接器一起使用,以允许将图像直接上传到 S3 存储桶。我们要将其全部连接到的 Web 应用程序不是 ASP.net 应用程序。

按照文档进行设置非常简单。

但是,我们的产品是 SaaS,所以每次启动 CKFinder 时,我都需要它针对我们存储桶中的不同键前缀。多个网站 运行 来自同一个应用程序,每个网站都应该能够通过 CKFinder 加载自己的图像库,而不能看到属于其他应用程序的图像。


我们的 CKFinder Web.config:

<backend name="s3Bucket" adapter="s3">
   <option name="bucket" value="myBucket" />
   <option name="key" value="KEYHERE" />
   <option name="secret" value="SECRETHERE" />
   <option name="region" value="us-east-1" />
   <option name="root" value="images" />
 </backend>

此配置将内容放入 /images/ 公共键前缀 "folder" 非常好,但对于使用 CKFinder 的每个应用程序,我希望它从不同的 "root" 读取:

/images/app1Id/
/images/app2Id/
/images/app3Id/

理想情况下,我想在调用 Editor/Finder 实例时设置它;类似于:

ClassicEditor.create( document.querySelector( '#textareaId' ), {
    ckfinder: {
        uploadUrl: '/ckfinder/connector?command=QuickUpload&type=Images&responseType=json',
        connectorRoot: '/images/app1Id/'
    },
    toolbar: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'ckfinder' ],
    heading: {
        options: [
            { model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' },
            { model: 'heading1', view: 'h1', title: 'Heading 1', class: 'ck-heading_heading1' },
            { model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' }
        ]
    }
});

这里我添加了connectorRoot: '/images/app1Id/'作为我想传递的例子。

有什么办法可以做这样的事情吗?我通读了 ASP.net 连接器文档,发现您可以构建自己的连接器并使用 pass 向其发送数据,但必须编译和维护自定义连接器听起来不是很有趣。这里的 S3 连接非常好而且很容易...如果它能让我更具体一点就好了。

我们得出的解决方案是修改和自定义 CKFinder ASP 连接器。非常感谢 CKSource 团队帮助我们获得这个 运行.


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);
        }
    }
}

注意事项:

  • 添加了 using System.Linq; 以便 FirstOrDefault 在获取 appId
  • 时有效
  • 我们删除了一些文件系统(Azure、Dropbox、Ftp),因为我们不在集成中使用它们
  • 在 CKFinder web.config 文件中,我们创建了一个 'dummy' 资源类型,因为 Finder 要求至少存在一个,但我们随后在连接器配置期间将其删除并替换为我们想要的资源类型 <resourceTypes><resourceType name="dummy" backend="default"></resourceType>resourceTypes>
  • 请注意并注意您在此文件中放置了一些敏感信息。请考虑您如何对此(或不)进行版本控制,并且您可能希望采取其他措施使其更安全

正在初始化一个 CKEditor4/CKFinder3 实例

<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>

注意事项:

  • 由于其他集成需求,这里使用的是手动集成方式,需要我们手动定义我们的filebrowserUrls
  • 目前,将 &pass=appId 添加到您的 filebrowserUrls 或将 config.pass = 'appId'; 添加到您的 config.js 文件无法正确地 pass 将所需的值传递给编辑器
  • 我相信这只会在使用手动集成方法时失败(如果您使用 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>

注意事项:

  • 这一切在集成到 CKEditor5 时似乎工作得更顺利,但是当集成到 CKEditor4 时,我们在使用手动集成方法将 appId 值 pass 正确地输入编辑器时遇到了很多问题对于 CKFinder
  • 我们在此处修改 ckfinder.html 文件以查找所需的 url 参数,并在启动时将它们 pass 到 CKFinder 实例中。这确保它们通过整个 Finder 实例
  • 查看此问题以获取有关此过程的更多详细信息以及将 n 参数传递到您的 Finder 实例的更通用方法: