This text is a work in progress—highly subject to change—and may not accurately describe any released version of the Apache™ Subversion® software. Bookmarking or otherwise referring others to this page is probably not such a smart idea. Please visit http://www.svnbook.com/ for stable versions of this book.

基于路径的授权

Apache 和 svnserve 都可以向用户授予或剥夺权限. 权限通常是针对整个仓库: 允许 (或禁止) 用户读取仓库, 以及允许 (或禁止) 用户写仓库.

实际上, 管理员还可以设置更加精细的访问权限. 例如允许一部分的用户 写 仓库中的某个特定目录, 但禁止其他用户; 或者是禁止大部分用户读取某个特定的 目录, 但允许少数用户读取. 访问权限的精细程度甚至可以细致到单个文件.

Apache 和 svnserve 使用相同的文件格式描述基于 路径的访问权限. 本节我们将介绍文件的格式, 以及如何配置 Subversion 服务 器, 以便利用规则文件管理基于路径的授权.

基于路径的访问控制

Subversion 通过模块 mod_authz_svn 向 Apache 提供基于路径的访问控制, 模块 mod_authz_svn 必须 在配置文件 httpd.conf 内, 使用配置指令 LoadModule 进行加载, 加载方式与模块 mod_dav_svn 相同. 为了让仓库能够使用这个模块, 在配置文件 httpd.conf 内用配置指令 AuthzSVNAccessFileAuthzSVNReposRelativeAccessFile 来指定访问权限配置 文件. (详细的说明见 “每个目录的访问控制”一节.)

为了给 svnserve 配置基于路径的访问控制, 只需要把 svnserve.conf 内的配置变量 authz-db 的值指定成你的访问权限配置文件.

一旦服务器知道了去哪儿查找你的访问权限配置文件, 接下来需要做的就是 定义访问权限.

Subversion 访问权限配置文件的语法与 svnserve.conf 和运行时配置文件的语法相同. 忽略以 # 开始的行, 在最简单的形式中, 文件的每一节的名字都指定了一个被版本控制的路径, 可能还指定了包含该路径的仓库. 换句话说, 除了少数几个被保留的节外, 节名只有这两种形式: 如果使用了配置指令 AuthzSVNAccessFile, 则要么是 [repos-name:path], 要么是 [path]. 如果使用了配置指令 AuthzSVNReposRelativeAccessFile 指定了每个仓库的访问权限配置文件, 则只能使用 [path] 这种形式. 已认证的用户名是每一节的选项的名字, 选项的值描述了用户对该 路径的访问权限: 只读 (r) 或读写 (rw). 如果用户名未出现在节中, 则不具有该路径的 任何访问权限.

[注意] 注意

出现在访问权限配置文件中的路径必须使用 Subversion 的 内部风格 编写, 在大部分情况下, 这意味着路径使用 UTF-8 编码, 使用斜杠 (/) 作为路径中各个分量 的分隔符 (即使是 Windows 系统, 也要使用斜杠). 还要注意这些路径 中的字符不会进行任何形式地转义—比如说路径中的空格必须与 文件中的节名 (例如 [repos-name:path with spaces]) 完全 一致.

下面是访问权限配置文件的一个例子, 文件把仓库 calc 的路径 /branches/calc/bug-142 (及其子目录) 的 读权限授予 Sally, 把读写权限授予 Harry:

[calc:/branches/calc/bug-142]
harry = rw
sally = r
[警告] 警告

Subversion 1.7 之前的版本会把仓库名和路径都转化成小写形式后, 再与访问权限配置文件进行匹配, 因此是不区分大小写的. 但从 Subversion 1.7 开始不再如此. 因此, 如果你需要从较旧的版本升级到 1.7, 你应该重新 检查访问权限配置文件中的大小写是否正确.

被授权模块检查的仓库名直接来自仓库的路径, 具体的表现受到两个服务 器选项的影响. mod_dav_svn 只使用仓库根目录 URL 的 最后一个分量 [67], 而 svnserve 则使用完整的, 相对 于服务器根目录 (由命令行选项 --root (-r) 指定) 的仓库路径.

[警告] 警告

如果同时通过 Apache 服务器与 svnserve 为 同一个仓库服务, 那么 mod_dav_svnsvnserve 决定仓库名的不同之外将会带来问题. 通常情况下, 管理员更喜欢为两种服务器配置同一个访问权限配置文件, 然而, 为了能让访问权限配置文件正常工作, 管理员必须确保访问权限配置文件里 的仓库名对于两种服务器而言都是兼容的—例如把 svnserve 的根目录配置成和 mod_dav_svn 的配置指令 SVNParentPath 相同的值, 或者为每个仓库指定一个 单独的访问权限配置文件, 这样就不用在文件里提到仓库名.

如果你使用了配置指令 SVNParentPath, 在访问权限 配置文件的节名中指定仓库名就非常重要, 因为如果省略了仓库名, 例如把节名写 成 [/home/dir], 那么该节将匹配 每个 仓库中的 /some/dir. 然而, 如果使用了配置指令 SVNPath, 那么在节名中 只提供路径也是可以的—毕竟这时候只存在一个仓库.

路径的权限继承自父目录, 这意味着我们可以为 Sally 指定一个访问 策略不同的子目录. 继续我们前面的例子, 现在我们要为 Sally 授予分支中 某个子目录的写权限, 而 Sally 原来只对分支拥有只读权限.

[calc:/branches/calc/bug-142]
harry = rw
sally = r

# give sally write access only to the 'testing' subdir
[calc:/branches/calc/bug-142/testing]
sally = rw

现在 Sally 对分支的子目录 testing 拥有写权 限, 但对于分支的其他部分仍然只具有只读权限. 而 Harry 对整个分支拥有 读写权限.

我们还可以通过规则的继承, 显式地阻止用户的权限, 方法是把用户名 设置成空:

[calc:/branches/calc/bug-142]
harry = rw
sally = r

[calc:/branches/calc/bug-142/secret]
harry =

在这个例子里, Harry 对整个 bug-142 目录具有 读写权限, 但却无法访问其中的子目录 secret.

[提示] 提示

需要记住的是最明确的路径总是最先被匹配. 服务器总 是试图匹配路径本身, 然后是路径的父路径, 然后再是父路径的父路径, 以此类推. 这样做的影响是如果我们在访问权限配置文件里添加一个特定的 路径, 那么它的权限配置就会覆盖从父目录继承而来的权限配置.

类似的, 指定了仓库名的节的权限配置将覆盖那些没有指定仓库名的节, 比如说访问权限配置文件里同时出现了 [calc:/some/path][/some/path], 那么对于仓库 calc, 将会使用第一种配置, 而忽略第二种.

在默认情况下, 任何用户对任意一个仓库都不具备访问权限, 这意味着 如果从头开始写访问权限配置文件, 你可能希望所有用户至少对仓库的根目录具有 只读权限. 可以通过把用户名设置成通配符 (*) 实现这 种配置, 此时通配符 * 表示 所有用户:

[/]
* = r

这是一种很常见的配置, 注意在节的名字中没有指定仓库的名字. 上面的 配置将把所有仓库的读权限授予给所有用户. 一旦用户对仓库具有了读权限, 接下来就可以根据具体的需要, 把特定仓库的特定目录的读写权限 (rw) 授予特定的用户.

虽然前面的例子都是针对目录的权限配置, 因为这是最常见的情况, 实际上管理员完全可以针对文件设置访问权限.

[calendar:/projects/calendar/manager.ics]
harry = rw
sally = r

用户组

访问权限配置文件还允许管理员定义用户组, 就像 Unix 里的 /etc/group. 为了定义用户组, 在访问权限配置文件里 创建一个名为 groups 的节, 然后在节内描述每一个 用户组: 变量名定义了用户组的名字, 而变量的值则是逗号分隔的, 属于该 用户组的用户名.

[groups]
calc-developers = harry, sally, joe
paint-developers = frank, sally, jane
everyone = harry, sally, joe, frank, jane

用户组的权限授予和用户名相同, 为了与用户名相区别, 在用户组的名字前 要加一个 @ 符号:

[calc:/projects/calc]
@calc-developers = rw

[paint:/projects/paint]
jane = r
@paint-developers = rw

需要特别注意的是用户组权限并不会被用户权限所覆盖, 而是会进行 叠加. 在上面的例子里, Jane 是用户组 paint-developers 的成员, 因此她对仓库 paint 具有读写权限, 再叠加上 jane = r, Jane 最终的权限仍然是可读写. 如果用户 已经是某个用户组的成员, 那就不可能再把用户的权限限制得比用户组的权限 还小.

用户组还可以包含其他的用户组:

[groups]
calc-developers = harry, sally, joe
paint-developers = frank, sally, jane
everyone = @calc-developers, @paint-developers

用户别名

某些认证系统仅支持相对较短的用户名, 例如 harry, sally, joe 这样简短的名字, 而其他一些认证系统—例如那些使用了 LDAP 和 SSL 的认证系统— 却支持更复杂的用户名, 例如在一个使用了 LDAP 的认证系统中, Harry 的 用户名可以是 CN=Harold Hacker,OU=Engineers,DC=red-bean,DC=com. 如果在访问权限配置文件里出现这些用户名, 文件将变得非常臃肿, 这些又长又 晦涩的用户名还很容易写错.

幸运的是, Subversion 1.5 为访问权限配置文件添加了对用户别名的支持. 有了用户别名, 对于复杂的用户名, 管理员只需要在赋予别名的地方写一次 就够了.

用户别名定义在访问权限配置文件的一个特殊的节— aliases, 节内的每一个变量名都定义了一个别名, 而 变量值则是真实的用户名.

[aliases]
harry = CN=Harold Hacker,OU=Engineers,DC=red-bean,DC=com
sally = CN=Sally Swatterbug,OU=Engineers,DC=red-bean,DC=com
joe = CN=Gerald I. Joseph,OU=Engineers,DC=red-bean,DC=com
…

用户别名定义完成后, 在访问权限配置文件内, 只要是能出现真实用户名的 地方都能用别名替代, 唯一的区别是要在别名前添加符号 &, 以便与真实的用户名进行区分.

[groups]
calc-developers = &harry, &sally, &joe
paint-developers = &frank, &sally, &jane
everyone = @calc-developers, @paint-developers

如果用户名经常发生变化, 那么使用别名也能带来方便. 利用别名, 当 用户名发生变化时, 只需要在定义别名的地方更新一次即可, 而不用在整个 文件内搜索并替换.

访问权限控制的高级特性

从 Subversion 1.5 开始, 访问权限配置文件还支持一些 魔力 符号, 这些符号可以帮助管理员基于用户的认证类别来 制定访问权限. 其中一个符号是 $authenticated, 用于将权限授予给所有已认证的用户. 类似的还有 $anonymous, 它表示所有 未认证 的用户.

[calendar:/projects/calendar]
$anonymous = r
$authenticated = rw

访问权限配置文件语法的另一个很有用的魔力符号是波浪号 (~), 用于排除某些用户. 在访问权限配置文件中, 如果 在用户名, 用户别名, 用户组或认证类别前加上波浪号, 就表示将访问权限 授予给与规则 不匹配 的用户. 虽然下面的配置容易 让人产生不必要的困惑, 但它和上面的例子是等效的:

[calendar:/projects/calendar]
~$authenticated = r
~$anonymous = rw

下面是一个更恰当的, 使用 ~ 的例子:

[groups]
calc-developers = &harry, &sally, &joe
calc-owners = &hewlett, &packard
calc = @calc-developers, @calc-owners

# Any calc participant has read-write access...
[calc:/projects/calc]
@calc = rw

# ...but only allow the owners to make and modify release tags.
[calc:/projects/calc/tags]
~@calc-owners = r

访问权限控制的一些陷阱

如果你使用 Apache 作为 Subversion 服务器, 而且还设置了仓库的 某些子目录对某些用户是不可读的, 那么在执行 svn checkout 时, 你需要意识到可能会出现的低效 行为.

取决于 Subversion 客户端所使用的 HTTP 函数库, 它可能会要求检出或 更新的全部载荷在一个单独的响应中递送, 当这种情况发生时, 一开始的认证 请求就是 Apache 向用户要求认证的 唯一 机会, 这会产生一些很奇怪的副作用. 比如说, 如果仓库的某个子目录只有 Sally 具有读权限, 而 Harry 检出了该目录的父目录, 他的客户端将以 Harry 的身 份完成最初的认证. 随着服务器向客户端发送大块的响应数据, 当发送 Harry 不具有读权限的那个子目录时, 服务器再也没有办法再次发送认证请求, 于是 该子目录被忽略, 而用户却无法通过重新以 Sally 的身份进行认证来避免这 种情况.

类似的道理, 如果仓库的根目录对于匿名用户是可读的, 那么检出整个仓库 时就不会向用户要求认证—同样, 服务器将直接忽略不可读的目录, 而不 会在检出到一半时向用户要求认证.[68]



[66] 这是本书的一个重要主题

[67] 通过配置指令 SVNReposName, 在 httpd.conf 里配置的任意一个人类可读懂的仓库名, 都将被授权模块忽略. 前面已经 说过, 访问权限配置文件里的节名必须引用到对服务器敏感的仓库路径.

[68] 关于这个问题的更多信息, 见 http://blogs.collab.net/subversion/2007/03/authz_and_anon_/ 的博文 Authz and Anon Authn Agony.