为每个CKFinder 3实例的S3存储桶设置通用密钥前缀

时间:2019-02-28 20:13:34

标签: amazon-s3 ckfinder ckeditor5

如何使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/公共密钥前缀“文件夹”中,但是对于每个使用CKFinder的应用程序,我希望它从不同的“根”中读取:

/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连接非常简单方便……只要让我更具体一点即可。

1 个答案:

答案 0 :(得分:2)

我们想到的解决方案是修改和自定义CKFinder ASP Connector。非常感谢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文件中,由于Finder要求至少存在一个虚拟资源,因此我们创建了一种“虚拟”资源类型,但随后在连接器配置期间将其删除,并用所需的资源类型{{ 1}}
  • 请注意并注意您在此文件中放置了一些敏感信息。请考虑如何(或不可以)对此版本进行控制,并且您可能需要采取其他措施以使其更加安全

初始化CKEditor4 / CKFinder3实例

<resourceTypes><resourceType name="dummy" backend="default"></resourceType>resourceTypes>

注意事项:

  • 由于其他集成要求,此处使用的是“手动集成”方法,这要求我们手动定义filebrowserUrls
  • 当前,在文件浏览器网址中添加<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> 或在&pass=appId文件中添加config.pass = 'appId';并不能正确地config.js到达编辑器中所需的值
  • 我相信这只会在使用“手动积分”方法时失败(如果您使用的是pass,它将正确运行)

ckfinder.html

CKFinder.setupCKEditor()

注意事项:

  • 当集成到CKEditor5中时,这一切似乎都可以更顺畅地工作,但是当集成到CKEditor4中时,当使用CKFinder的“手动集成”方法将appId值正确地<!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> 插入编辑器时,我们会遇到很多问题
  • 我们在此处修改ckfinder.html文件以查找所需的URL参数,并在启动时将其pass插入CKFinder实例。这样可以确保它们在整个Finder实例中传递
  • 请查看此问题,以获取有关此过程的更多详细信息以及将 n 参数传递到Finder实例的更通用方法:How do I pass custom values to CKFinder3 when instantiating a CKEditor4 instance?