为django的未托管remoteStorage服务器应用程序
项目描述
django-remotestorage
Unhosted remoteStorage 服务器实现
此应用程序是早期remoteStorage API版本的存储(服务器)实现,指定于此
http://www.w3.org/community/unhosted/wiki/RemoteStorage-2011.10
其中一些部分(尤其是webfinger、oauth2,因为当时我使用了当时可用的较新规范)可能与当前API兼容
https://tools.ietf.org/id/draft-dejong-remotestorage-00.txt
但由于remoteStorage.js 0.7.0实验性API当时仍在积极开发中,我尚未测试它是否与当前实现兼容。
包(以及django应用程序)以前被称为django-unhosted,并最终进行了重命名。如果您正在使用django-unhosted包,请阅读迁移说明,位于安装部分。
remoteStorage
想法是您可以在host1上拥有一个存储账户(无论什么策略和身份验证)以及host2上的某些Web应用程序(比如,某些视觉编辑器,想想MS Word)。
要在Web应用程序中编辑文档,通常host2需要实现某种用户注册、存储(如docs.google.com)等功能,以便编辑文档。
使用remoteStorage,这个存储不需要在host2上,因此您不需要在那里实现一些复杂的策略和经过身份验证的存储来启动一个功能齐全的Web应用程序——它可以打开和保存文档到支持该协议的任何远程主机(基本上是WebDAV的GET/PUT,再加上OAuth2)。
host1可以是您的VPS、客户端机器本身(特别是直接使用IPv6,或者通过某些服务如pagekite提供的IPv4)以及某些可靠的云提供商等。
为了完全理解它是如何工作的,我建议查看OAuth2、WebDAV、CORS和Webfinger,这些都是实现协议所使用的技术。
这个Django应用程序完全实现了host1的面向Web的存储,包括用户注册表单(可选,用户可以通过其他Django应用程序或通过Django管理界面添加,否则),客户端访问管理界面以及一个简单的演示客户端。
安全性
由于应用程序是面向公共互联网的接口,您的数据可能很重要,而我并非安全专家或专家,我建议在存储任何敏感数据之前进行渗透测试或验证代码。
与安全漏洞相比,数据丢失或损坏更容易预防(顺便说一句,备份在这里也大有裨益),因此,请再次自行查看代码并找出我在盲点(不用说,缺乏技能)中找不到的问题。
其他存储服务器实现中明显(对外部分析而言)的安全缺陷的示例可以在这里找到,从中学到教训。
安装
要求
(可选,推荐)South - 用于自动数据库模式更新
oauth2app目前不在PyPI上,但可以使用pip直接从GitHub安装。
应用程序的各个接口使用一些外部资源,如Twitter Bootstrap CSS文件(从bootstrapcdn.com提供)和remoteStorage.js,如果它们在STATIC_ROOT中可用,则可以本地URL提供,请参阅“自定义/接口”以获取详细信息。
从django-unhosted迁移
该软件包过去称为django-unhosted,但在为时已晚之前决定重新命名。
遗憾的是,没有简单的方法可以重命名Django应用程序和Python包,尤其是如果不想让旧的包名称永远存在,因此必须采取一些手动步骤才能迁移到新的(django-remotestorage)应用程序/包。
卸载django-unhosted Python包(通过pip卸载、OS工具或手动删除模块路径)。
将所有名称中包含“django_unhosted”的数据库表重命名为以“django_remotestorage”开头。可以使用许多易于使用的GUI工具(例如pgadmin、phpPgAdmin、phpMyAdmin、phpSQLiteAdmin等)或本机CLI接口(例如sqlite3 /path/to/db.sqlite、psql、mysql等)来完成此操作。
更新settings.py和urlconf文件,从“django_remotestorage”模块导入内容,而不是从“django_unhosted”模块导入。如果在使用settings.py时,将所有变量名中的“UNHOSTED_”替换为“REMOTESTORAGE_”。
如果您有自定义的urlconf和/或模板,请将“unhosted”命名空间替换为“remotestorage”。
这应该相当简单,但如果描述的过程对您不起作用,请随时打开问题或联系开发者。
部署/配置
Django应用作为“django项目”的一部分进行部署,其最小化形式仅包含几个配置文件,指定要使用的数据库以及哪些应用应该处理哪些URL。
TL;DR
从头开始简单安装/设置可能看起来是这样的。
安装应用程序本身(或不安装,也可以将其检出至项目目录中)
pip install django-remotestorage
…或者直接从git master分支安装
pip install 'git+https://github.com/RemoteStorage/django-remotestorage.git#egg=django-remotestorage'
…或者您可以手动操作
git clone https://github.com/RemoteStorage/django-remotestorage.git cd django-remotestorage python setup.py install pip install -r requirements.txt # or download/install each by hand as well
前面提到的“pip”工具通常随操作系统提供,否则请参阅pip安装文档。除了安装pip本身外,不要使用“easy_install”进行其他任何事情。
以类似方式安装oauth2app
pip install 'git+https://github.com/hiidef/oauth2app.git#egg=oauth2app'
然后创建并配置一个Django项目
cd django-admin.py startproject myproject cd myproject # Update settings.py (sqlite3 is used as db here) and urls.py sed -i \ -e 's/'\''ENGINE'\''.*/"ENGINE": "django.db.backends.sqlite3",/' \ -e 's/'\''NAME'\''.*/"NAME": "db.sqlite",/' \ -e 's/STATIC_ROOT *=/STATIC_ROOT="./static"/' \ myproject/settings.py echo -e >>myproject/settings.py \ 'from django_remotestorage.settings_base import update_settings' \ '\nupdate_settings(__name__)' sed -i \ -e '1afrom django_remotestorage.urls import remotestorage_patterns' \ -e 's/# Examples:.*/("", include(remotestorage_patterns)),\n\n\0/' \ myproject/urls.py # Create database schema and link app static files to STATIC_ROOT ./manage.py syncdb --noinput ./manage.py migrate django_remotestorage ./manage.py collectstatic --noinput --link # Run simple dev server ./manage.py runserver
(由于webfinger协议需要某种类型的XRD身份验证,例如https,因此在这样的简单设置中它将无法正常工作)
以下是配置过程的更详细说明。
Django项目配置
主要思想是在Django项目中更新两个配置文件 - settings.py和urls.py。
有几种方法可以更新Django settings.py以使用应用程序
如果它是Django项目中唯一的app,并且还没有自定义的settings.py,可以直接将来自django_remotestorage.settings_base模块的选项导入其中。
为此,将以下行添加到“{your_app_name}/settings.py”文件的末尾(或 wherever DJANGO_SETTINGS_MODULE被使用)文件中
from django_remotestorage.settings_base import *
这将从那里导入所有选项(必须更改的最小值)覆盖原始文件中定义的选项。
请注意,要覆盖的选项列表包括INSTALLED_APPS、MIDDLEWARE_CLASSES等,这些选项通常被自定义,并且通常特定于安装的Django版本,因此您可以选择在settings.py的开头插入该导入行,这样在它之后定义的所有内容都将覆盖导入的选项。
如果已经有了自定义的settings.py文件,可以使用django_remotestorage.settings_base.update_settings辅助函数来更新配置,而无需盲目覆盖任何选项。
它可以像这样在settings.py文件末尾使用
from django_remotestorage.settings_base import update_settings update_settings(__name__)
它所做的全部更改的完整列表可以在django_remotestorage.settings_base模块的开头的“updates”字典中找到。
“update_settings”函数还接受可选的“only”和“ignore”关键字(期望一个选项名称的可迭代对象),这些关键字可用于控制哪些参数应该更新或明确保留不变。
这应该是一个更安全、更灵活且面向未来的方法,将必要的选项更新与现有的(特定于站点的)配置合并。
手动更新文件。
大多数设置默认值可以在Django文档中找到。
对于类列表类型的选项,可以省略重复的值。请注意,MIDDLEWARE_CLASSES的顺序很重要。
OAUTH2_CLIENT_KEY_LENGTH = 1024 OAUTH2_SCOPE_LENGTH = 2048 TEMPLATE_CONTEXT_PROCESSORS = ( ...whatever is already there... 'django.core.context_processors.csrf', 'django.core.context_processors.request', 'django.contrib.messages.context_processors.messages', 'django_remotestorage.utils.external_resources_context', ) TEMPLATE_LOADERS = ( ...whatever is already there... 'django_remotestorage.apps.webfinger.xrd_gen.Loader', ) MIDDLEWARE_CLASSES = ( ...whatever is already there... <remove 'django.middleware.csrf.CsrfViewMiddleware', if it's there> ...whatever is already there, except for ConditionalGet / FetchFromCache... 'django.contrib.messages.middleware.MessageMiddleware', ...ConditionalGetMiddleware and FetchFromCacheMiddleware (and such), if used... ) INSTALLED_APPS = ( ...whatever is already there... 'django.contrib.messages', 'django_remotestorage', 'oauth2app', 'south', )
如果没有使用,应从INSTALLED_APPS中省略“south”。
无论如何,如果你刚刚创建了 django 项目(使用“django-admin.py startproject”或其他方式),请确保查看其 settings.py 文件并至少编辑 DATABASES、MEDIA_* 和 STATIC_* 选项。你也许还想要设置其他(可选)的设置,例如 TIME_ZONE、ADMINS、LOGGING 等。
至于 urls.py,只需添加以下行到 URL 模式(在此之前先导入 django_remotestorage.urls 模块)
('', include(remotestorage_patterns)),
它会看起来像这样
... from django_remotestorage.urls import remotestorage_patterns ... urlpatterns = patterns('', ('', include(remotestorage_patterns)), ...
这将把所有应用的 URL 添加到根路径(关于这些路径的完整列表,请参阅模块代码)。要选择性地禁用某些组件,请参阅“自定义”部分。
数据库模式
接下来,创建应用所需的数据库模式(如果遇到“Settings cannot be imported”错误,确保你在与“settings.py”文件相同的路径下运行)
django-admin.py syncdb
如果已安装(并在 INSTALLED_APPS 中指定)South 应用,则你也应该使用其迁移来创建可用的表
django-admin.py migrate django_remotestorage
此命令可以在 django-remotestorage 应用更新后运行(应该运行),以应用对数据库模式的任何可能的更改。
运行
几乎所有支持 WSGI 协议的服务器都可以与 django 一起使用——这里没有特定于应用的内容,只是普通的 django,通常用作后端与某些 httpd 通过 wsgi 一起使用。
有关部署过程的通用说明,请参阅 django 文档部署过程。
自定义
组件
该应用由几个独立的组件(子应用)组成(通过 django_remotestorage.urls 绑定到 URL 路径)
Webfinger(名称:webfinger,URL:{include_prefix}/.well-known/host-meta,{include_prefix}/webfinger;见django_remotestorage.apps.webfinger.urls,其他子应用也有类似的 urlconf 文件)。
OAuth2(名称:oauth2,URL:{include_prefix}/oauth2)。
存储 API(名称:api,URL:{include_prefix}/api)。
账户/客户端管理(名称:“account”,URL:{include_prefix}/account)。也可以通过以下名称部分启用: “account_auth”(登录/注销表单/链接)、“account_auth_management”(注册表单)、“account_client_management”(登录用户的应用/客户端访问管理界面)。 “account”是这些接口的别名。
演示客户端(名称:demo,URL:{include_prefix}/)
某些组件提供相互链接(例如,webfinger 在提供的 XRD/JSON 数据中提供对 OAuth2 和 API 的链接),解析为“remotestorage:{app}:{view_name}”,因此你可以将这些应用重新绑定到任何 URL,只要为django “reverse()” 函数和“url”模板标记提供相同的命名空间/view_name。
直接包含“django_remotestorage.urls.remotestorage_patterns”(而不是单个组件的 urlconfs)时,可以在 settings.py 中设置“REMOTESTORAGE_COMPONENTS”选项为一个可迭代组件,例如
REMOTESTORAGE_COMPONENTS = 'webfinger', 'oauth2', 'api'
…将仅启用存储 API、OAuth2 和 Webfinger 子应用——功能性远程存储节点的基本最低要求。除非启用了其他方式来验证 django 用户(如django.contrib.auth.views.login或django.contrib.admin),否则可能还需要启用“account_auth”接口以通过 OAuth2 授权。
如果从 urlconf 中完全省略“account”(或其部分)和“demo”应用(如果不需要),则 OAuth2 访问确认界面中不会有任何链接到它们。它们的界面页面和功能将无法访问。
“api”和“oauth2”子应用也不与其他组件链接,因此可以单独使用它们以及相互之间(例如,如果授权服务器和存储位于不同的主机上),但为了 API 能够验证认证令牌,它们必须共享数据库。
OAuth2
强烈建议在首次运行syncdb之前,增加数据库字段长度(使用oauth2app设置)。
OAUTH2_CLIENT_KEY_LENGTH = 1024(默认:30)
OAUTH2_SCOPE_LENGTH = 2048(默认:255)
请参阅“已知问题 / OAuth2”部分,了解更多为何需要这样做的原因。
另一个重要的可调参数是OAUTH2_ACCESS_TOKEN_EXPIRATION(默认:3600 = 1小时),这至少在remoteStorage.js 0.6.9(截至撰写时为“稳定版”)中,实际上设置了一个最大间隔,即在需要访问OAuth2界面并获取新访问令牌之间。因为remoteStorage.js似乎无法刷新这些令牌。
Webfinger
如果针对该域的webfinger和host-meta请求需要携带比仅为remoteStorage更多的数据,可以通过完全替换webfinger应用或为其添加自定义模板来扩展。
Webfinger应用使用“webfinger/host_meta.{xml,json}”和“webfinger/webfinger.{xml,json}”模板,由django_remotestorage.apps.webfinger.xrd_gen.Loader提供或动态生成(如果模板提供者找不到,针对json)。
请参阅django_remotestorage/templates/webfinger/{host_meta,webfinger}.xml.example中的示例xml模板。
存储 / WebDAV
提供的remoteStorage由(可配置的)Django存储API支持。
默认情况下,使用DEFAULT_FILE_STORAGE存储类。可以通过“REMOTESTORAGE_DAV_STORAGE”参数(传递给get_storage_class)指定不同的存储类。
存储API实现的示例可能包括
django-storages(S3、CouchDB、SQL、FTP、MongoDB、CloudFiles等)
django-dropbox(Dropbox)
django-riak-engine(Riak)
django-tahoestorage(Tahoe-LAFS)
基本上,对于任何数据存储技术都有相应的客户端 - 只需搜索、安装并将REMOTESTORAGE_DAV_STORAGE(或DEFAULT_FILE_STORAGE)设置为它。
可以通过MEDIA_URL和MEDIA_ROOT 设置配置默认存储(FileStorage)参数,有关详细信息,请参阅django文档“管理文件”部分。
还有一些优化参数
REMOTESTORAGE_DAV_SENDFILE(布尔值,默认:False)
在请求时,通过“X-Sendfile”头将Storage.path(如果后端支持)传递给httpd前端,而不是实际内容,这样响应就可以由前端守护程序直接提供,而不涉及后端应用。
REMOTESTORAGE_DAV_ACCEL(字符串,默认:None)
返回带有“X-Accel-Redirect”头设置为指定前缀(可以为空字符串)加上请求路径的空HttpResponse,这样实际响应就可以由apache mod_accel提供。
REMOTESTORAGE_DAV_REDIRECT(布尔值,默认:False)
如果设置了非空字符串的MEDIA_URL,则使用Storage.url方法生成的MEDIA_URL返回重定向。
在http“Authorization”头中检查由oauth2app生成的bearer令牌之后,才提供这些url服务。
不要配置httpd在不进行授权的情况下提供MEDIA_URL路径,因为任何人都可以通过猜测文件路径或从js获取/重用它们来绕过OAuth2并访问remoteStorage中的任何内容,这很容易被利用。
接口
主要是常规操作 - 将自己的模板放在settings.py中指定的加载器中。
在这些页面上提供的外部资源可以被放置到STATIC_ROOT中,由本地的httpd服务。有关详情,请参阅django_remotestorage.utils.external_resources_context上下文处理器。
如果通过https提供服务这些接口,请特别小心地将资源本地化 - 如果中间人可以放置任何javascript(通过纯http加载)到页面,那么这并没有增加安全性。
请注意,如果不需要,可以禁用所有/任何UI,只需使用“组件”部分中描述的REMOTESTORAGE_COMPONENTS选项,或者不在urlconf中包含它们,只选择实际需要的组件。
定制的一个常见案例是将整个应用程序放入某个子路径(例如示例中的“/remotestorage”)可以通过将此放入项目的根urls.py中解决。
from django.conf.urls import patterns, include, url from django_remotestorage.apps.webfinger.urls import host_meta_patterns from django_remotestorage.urls import remotestorage_patterns urlpatterns = patterns('', url(r'', include(host_meta_patterns)), url(r'^remotestorage/', include(remotestorage_patterns)), )
这样,演示客户端将在“/remotestorage”URL下可用,并且所有链接都将包含该前缀(例如,来自webfinger的授权链接将指向“/remotestorage/oauth2/authorize”)。
但是,请确保webfinger应用程序的host_meta视图可在众所周知的位置“/.well-known/host-meta”上找到,因此从根中有一个“host_meta_patterns”特殊情况的链接。
命令
access_token_cleanup [options] [ username … ]
从数据库中删除已过期的OAuth访问令牌(如果指定了用户名(们))。
可以偶尔通过cron运行(使用–verbosity=0以抑制活动报告),以防止令牌数量无限增长,删除非刷新或即将到期的(带有负的–grace-period)令牌。
用法示例
% ./manage.py access_token_cleanup -v2 -n -t 3600 test Removing token: id=1, user=test, client_name=localhost, expired=2012-07-31 03:24:30+06:00. 1 access token(s) removed.
已知问题
这些问题与实现相关,而不是与协议本身的问题(这并不意味着后者没有问题,只是这不是讨论它们的地方)。
Webfinger
目前没有对签名XRD的支持。如果TLS不可用,则可以使用签名静态xml“模板”(或只是文件,从httpd服务)作为解决方案。
OAuth2
存储对象路径(例如“public/myphoto.jpg”)被remoteStorage用作OAuth2“作用域”。oauth2app基本上保持一个这些的单一表(将它们视为一组有限的预先设定的功能)。
问题在于这里
oauth2app将“作用域”存储为255个字符的键,而路径/集合名称可能更长。上游请求已被合并(截至2012年7月19日),因此请使用任何较新版本,并在settings.py中使用足够大的OAUTH2_SCOPE_LENGTH参数(它实际上不会影响现代数据库的性能,只是使您的生命变得更困难)。
目前,oauth2app会检查请求中指定的AccessRange(作用域)模型的存在,即使其中一些可能没有用户授权,也要求临时创建这些混乱。上游请求:https://github.com/hiidef/oauth2app/pull/32
维护(在这种情况下无用的)表涉及一些额外的代码/数据库开销。
remoteStorage.js 0.6.9(目前是“稳定”版本)有一个已知问题,即传递旧版“path1,path2”作为“作用域”,进一步复杂化了oauth2app的情况(它会将其视为一个单一的功能,按照规范)如果传递了多个路径。
所使用的解决方案是通过检测缺少“:rw”后缀来检测旧格式,并通过发出重定向来更新地址中的“作用域”。
请注意,由于路径可能包含逗号,“path1,path2”可能是模糊的(因为这个问题),它可以被处理为“path1:rw”和“path2:rw”或“path1,path2:rw”。当前实现在没有冒号分隔的后缀的情况下选择前者解释。
remoteStorage.js 0.6.9(目前为“稳定”版本)使用应用站点的域名作为OAuth2的client_id,在oauth2app中对应于“key”字段,默认长度为32个字符,可能不足以表示某些域名,但可以通过django项目的settings.py中的OAUTH2_CLIENT_KEY_LENGTH参数进行配置。请务必在syncdb之前完成此操作,或者在以后更新表列。
可能的解决方案是使用哈希作为内部client_id,并将带有“client_id=hostname.com”的remoteStorage请求重定向到类似“client_id=sha1:bbc21f0ccb5dfbf81f5043d78aa”的形式。
目前我不明白为什么client_id应该是随机的或没有意义的,如果有理由的话,请报告问题,可能随时可以部署一些自动迁移到哈希的操作。
oauth2app目前不在PyPI上,但可以使用pip直接从GitHub安装。
WebDAV
CSRF中间件(django.middleware.csrf.CsrfViewMiddleware)必须禁用,因为remoteStorage.js不将django csrf令牌传递给PUT(和类似)请求。但是,通过装饰器为应用表单有选择性地启用。
数据目前存储在Django Storage中,而路径元数据通过Django数据库API存储,这引入了两个故障点(以及两个之间同步丢失的可能性),因为一个数据没有另一个就毫无用处。
似乎没有简单的方法绕过这个问题——在Storage键中存储路径数据与任何驱动程序都不兼容,将内容推送到那里在内容不是由python(例如httpd)提供服务时将不起作用,并且将文件存储在数据库中仅适用于相对较小的文件。
因此,请确保同时备份数据库和实际存储,或者编写一些特定于存储的冗余代码来存储元数据。例如,可以向post-save django信号添加钩子,该钩子将从StorageObject.data.name获取存储路径,并将一些“{name}.meta”文件与序列化的模型数据一起存储。
待办事项
客户端(请求访问的应用)欺骗——向其返回假的“授权范围”,但实际上将其存储在某处以拒绝实际访问或提供随机垃圾。
目的是防止twitter和android平台上常见的应用程序总是请求一切,用户只能选择“全有或全无”的情况。
向客户端管理界面添加检查已存储/访问资源的功能。
联系/支持
欢迎您访问freenode IRC上的#unhosted或#remotestorage频道freenode IRC,您总是可以找到愿意帮助您理解、设置和解决任何问题的作者和人士(包括开发者)。
邮件列表、twitter和其他间接沟通渠道可以在Unhosted运动网站上找到。
当然,还可以在github仓库中为开源问题。