rbac.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "github.com/casbin/casbin/v2"
  6. xormadapter "github.com/casbin/xorm-adapter/v2"
  7. "github.com/iapt-platform/config"
  8. )
  9. const (
  10. IDPrefixTranslation = "it_"
  11. IDPrefixChannel = "ic_"
  12. IDPrefixArticle = "ia_"
  13. IDPrefixUser = "iu_"
  14. IDPrefixOrg = "io_"
  15. IDPrefixOrgGroup = "iog_"
  16. // 资源分组:版本风格
  17. ResChannel = "s_channel"
  18. // 资源分组:文章
  19. ResArticle = "s_article"
  20. // 资源分组:译文(版本风格 + 文章)
  21. ResTranslation = "s_translation"
  22. // 组织管理员
  23. RoleOrgAdmin = "r_admin"
  24. // 组织用户
  25. RoleOrgMember = "r_member"
  26. // 权限:角色分组
  27. GroupRole = "g"
  28. // 权限:资源分组
  29. GroupRes = "g2"
  30. // 权限:阅读权限
  31. PermRead = "p_read"
  32. // 权限:翻译权限
  33. PermTrans = "p_trans"
  34. // 权限:修改权限
  35. PermWrite = "p_write"
  36. )
  37. func CreateOrg(orgID string, userID string, e *casbin.Enforcer) {
  38. // 将该用户设置为组织机构的管理员
  39. e.AddNamedGroupingPolicy(GroupRole, IDPrefixUser+userID, RoleOrgAdmin, IDPrefixOrg+orgID)
  40. // 添加 admin 资源操作权限
  41. e.AddNamedPolicy("p", RoleOrgAdmin, IDPrefixOrg+orgID, ResArticle, ".*")
  42. e.AddNamedPolicy("p", RoleOrgAdmin, IDPrefixOrg+orgID, ResChannel, ".*")
  43. e.AddNamedPolicy("p", RoleOrgAdmin, IDPrefixOrg+orgID, ResTranslation, ".*")
  44. // 添加 member 资源操作权限
  45. e.AddNamedPolicy("p", RoleOrgMember, IDPrefixOrg+orgID, ResArticle, PermRead)
  46. e.AddNamedPolicy("p", RoleOrgMember, IDPrefixOrg+orgID, ResChannel, PermRead)
  47. e.AddNamedPolicy("p", RoleOrgMember, IDPrefixOrg+orgID, ResTranslation, PermRead)
  48. }
  49. func AddOrgMemberToGroup(orgID string, groupID string, userID string, e *casbin.Enforcer) {
  50. // g, li4, translator, org1
  51. e.AddNamedGroupingPolicy(GroupRole, IDPrefixUser+userID, IDPrefixOrgGroup+groupID, IDPrefixOrg+orgID)
  52. }
  53. /*
  54. * 「为组织添加新成员」
  55. * 加入 member 分组,拥有组织资源读取权限
  56. */
  57. func AddOrgMember(orgID string, userID string, e *casbin.Enforcer) {
  58. e.AddNamedGroupingPolicy(GroupRole, IDPrefixUser+userID, RoleOrgMember, IDPrefixOrg+orgID)
  59. }
  60. /*
  61. * 「为组织添加新管理员」
  62. * 加入 admin 分组,拥有组织资源一切权限
  63. */
  64. func AddOrgAdmin(orgID string, userID string, e *casbin.Enforcer) {
  65. e.AddNamedGroupingPolicy(GroupRole, IDPrefixUser+userID, RoleOrgAdmin, IDPrefixOrg+orgID)
  66. }
  67. func CreateChannel(channelID string, orgID string, e *casbin.Enforcer) {
  68. // 将该 Channel 资源放入本组织的 channel 分组
  69. e.AddNamedGroupingPolicy(GroupRes, IDPrefixChannel+channelID, ResChannel, IDPrefixOrg+orgID)
  70. }
  71. /*
  72. * 「为版本风格添加只读用户」,也即是「分享版本风格」-「查看者」
  73. * 操作之后,该用户可以访问此版本风格下的所有译文
  74. */
  75. func AddChannelReader(channelID string, orgID string, userID string, e *casbin.Enforcer) {
  76. e.AddNamedPolicy("p", IDPrefixUser+userID, IDPrefixOrg+orgID, IDPrefixChannel+channelID, PermRead)
  77. }
  78. func AddChannelReaderGroup(channelID string, orgID string, groupID string, e *casbin.Enforcer) {
  79. e.AddNamedPolicy("p", IDPrefixOrgGroup+groupID, IDPrefixOrg+orgID, IDPrefixChannel+channelID, PermRead)
  80. }
  81. /*
  82. * 「为版本风格添加翻译用户」,也即是「分享版本风格」-「编辑者」
  83. * 操作之后,该用户可以编辑此版本风格下的所有译文
  84. * 注意:该权限并不能编辑「版本风格」本身
  85. */
  86. func AddChannelTranslator(channelID string, orgID string, userID string, e *casbin.Enforcer) {
  87. e.AddNamedPolicy("p", IDPrefixUser+userID, IDPrefixOrg+orgID, IDPrefixChannel+channelID, PermTrans)
  88. }
  89. func AddChannelTranslatorGroup(channelID string, orgID string, groupID string, e *casbin.Enforcer) {
  90. e.AddNamedPolicy("p", IDPrefixOrgGroup+groupID, IDPrefixOrg+orgID, IDPrefixChannel+channelID, PermTrans)
  91. }
  92. /*
  93. * 「为版本风格添加编辑用户」
  94. * 操作之后,该用户可以编辑此版本风格本身,比如:
  95. * 分享此版本风格、修改版本风格的描述等等
  96. * //TODO: 此功能是否需要?
  97. */
  98. func AddChannelWriter(channelID string, orgID string, userID string, e *casbin.Enforcer) (string, error) {
  99. e.AddNamedPolicy("p", IDPrefixUser+userID, IDPrefixOrg+orgID, IDPrefixChannel+channelID, PermWrite)
  100. return "", errors.New("Do we realy need this function?")
  101. }
  102. func AddChannelWriterGroup(channelID string, orgID string, groupID string, e *casbin.Enforcer) (string, error) {
  103. e.AddNamedPolicy("p", IDPrefixOrgGroup+groupID, IDPrefixOrg+orgID, IDPrefixChannel+channelID, PermWrite)
  104. return "", errors.New("Do we realy need this function?")
  105. }
  106. func CreateArticle(articleID string, orgID string, e *casbin.Enforcer) {
  107. // 将该 Article 资源放入本组织的 article 分组
  108. e.AddNamedGroupingPolicy("g2", IDPrefixArticle+articleID, ResArticle, IDPrefixOrg+orgID)
  109. }
  110. /*
  111. * 「为文章添加只读用户」,也即是「分享文章」-「查看者」
  112. * 操作之后,该用户可以访问此文章模板,
  113. * 对于能否查看对应译文,需要由对应 channel 的权限来判定
  114. */
  115. func AddArticleReader(articleID string, orgID string, userID string, e *casbin.Enforcer) {
  116. e.AddNamedPolicy("p", IDPrefixUser+userID, IDPrefixOrg+orgID, IDPrefixArticle+articleID, PermRead)
  117. }
  118. func AddArticleReaderGroup(articleID string, orgID string, groupID string, e *casbin.Enforcer) {
  119. e.AddNamedPolicy("p", IDPrefixOrgGroup+groupID, IDPrefixOrg+orgID, IDPrefixArticle+articleID, PermRead)
  120. }
  121. /*
  122. * 「为文章添加编辑用户」,也即是「分享文章」-「编辑者」
  123. * 操作之后,该用户可以编辑此文章模板,
  124. * 对于能否查看、或修改对应译文,需要由对应 channel 的权限来判定
  125. */
  126. func AddArticleWriter(articleID string, orgID string, userID string, e *casbin.Enforcer) {
  127. e.AddNamedPolicy("p", IDPrefixUser+userID, IDPrefixOrg+orgID, IDPrefixArticle+articleID, PermWrite)
  128. }
  129. func AddArticleWriterGroup(articleID string, orgID string, groupID string, e *casbin.Enforcer) {
  130. e.AddNamedPolicy("p", IDPrefixOrgGroup+groupID, IDPrefixOrg+orgID, IDPrefixArticle+articleID, PermWrite)
  131. }
  132. /*
  133. * 创建译文 = 版本风格 + 文章模板
  134. * 所谓 “译文是由 版本风格 和 文章模板 组成”,这是一个幻象。
  135. * 当我们参与翻译的时候,最终的翻译结果是对应的经文句子编号,如:{{125-2347-2-14}},
  136. * 而不是 “翻译了这篇文章”,也就是说当前的翻译结果,可以在任何引用了该句子编号的文章里找到。
  137. *
  138. * 所以,与其说是 “创建了译文”,不如说是:
  139. * “通过在当前「文章模板」下基于某「版本风格」翻译该「句子编号」,
  140. * 导致该「文章模板」与该「版本风格」发生了「我曾经在该文章下使用该风格翻译了某句话」的关系”
  141. *
  142. * 那么,我们为什么要创建还要单独译文呢?
  143. * 1. 将「版本风格」与「文章模板」绑定,方便译者查找自己未完成的工作
  144. * 2. 单独创建的译文,可以单独分享,而不用分享整个「版本风格」加上单独分享「文章模板」
  145. * 2.1 如果单独分享了「版本风格」,那么该用户使用该风格翻译的其他「句子」也会被同时分享
  146. * 2.2 如果单独分享「文章模板」以及「版本风格」,两者并没有发生关联,
  147. * 那么被分享者也难以找到如此配对的资源
  148. *
  149. * 那么,如何判断当前访问的资源,是「文章对应的译文」还是「圣典对应的译文」呢?
  150. * 1. app/article?... 对应的资源便是「文章,以及对应的译文」
  151. * 2. app/reader?... 对应的资源便是「圣典,以及对应的译文」
  152. *
  153. * 那么,我们如何判断他们的权限呢?
  154. * 0. 如果以上两个资源都没有传递 channel 参数,则表示「不关特别联任何译文」
  155. * 则不需要做任何权限判断(可选择公开的版本风格显示译文)
  156. * 1. app/article?id=01&channel=01... article - 01 关联了 channel - 01
  157. * 1.1 首先该用户是否具有 channel - 01 和 article - 01 组成的译文的权限,如果有则授予
  158. 这样以来,用户可以单独分享「译文」,而不必分享「版本风格」+「文章模板」了,
  159. 同时也满足了「仅仅希望与其他同学一起编辑某一篇文章」的需求
  160. * 1.2 判断该用户是否拥有 article - 01 的权限,如果有,则进行 1.3 判断
  161. * 1.3 判断该用户是否具有 channel - 01 的权限,如果有,则授予,否则即没有权限
  162. * 2. app/reader?channel=01... 关联了 channel - 01
  163. * 2.1 判断该用户是否拥有 channel - 01 的权限即可
  164. *
  165. * 以上方法对于「写入译文」时的权限判断,同理可推得。推不得的话,我们讨论讨论。
  166. */
  167. func CreateTranslation(channelID string, articleID string, orgID string, e *casbin.Enforcer) {
  168. // 将该 Translation 资源放入本组织的 Translation 分组
  169. // ID 由 channelID + articleID 构成
  170. e.AddNamedGroupingPolicy("g2", IDPrefixTranslation+channelID+"+"+articleID, ResTranslation, orgID)
  171. }
  172. /*
  173. * 「为译文添加只读用户」,也即是仅分享「某版本风格」对应的「某篇文章模板」
  174. * 操作之后,该用户可以访问此译文,但
  175. * 并不能:单独访问该「文章模板」,或,访问该「版本风格」对应的其他译文,
  176. * 以上两种权限不在此处授予。
  177. */
  178. func AddTranslationReader(channelID string, articleID string, orgID string, userID string, e *casbin.Enforcer) {
  179. e.AddNamedPolicy("p", IDPrefixUser+userID, IDPrefixOrg+orgID, IDPrefixTranslation+channelID+"+"+articleID, PermRead)
  180. }
  181. func AddTranslationReaderGroup(channelID string, articleID string, orgID string, groupID string, e *casbin.Enforcer) {
  182. e.AddNamedPolicy("p", IDPrefixOrgGroup+groupID, IDPrefixOrg+orgID, IDPrefixTranslation+channelID+"+"+articleID, PermRead)
  183. }
  184. /*
  185. * 「为译文添加翻译用户」,也即是仅分享「某版本风格」对应的「某篇文章模板」,并允许翻译
  186. * 操作之后,该用户可以访问、修改此译文,但
  187. * 并不能:单独访问该「文章模板」,或,访问或修改该「版本风格」对应的其他译文,
  188. * 以上两种权限不在此处授予。
  189. */
  190. func AddTranslationTranslator(channelID string, articleID string, orgID string, userID string, e *casbin.Enforcer) {
  191. e.AddNamedPolicy("p", IDPrefixUser+userID, IDPrefixOrg+orgID, IDPrefixTranslation+channelID+"+"+articleID, PermTrans)
  192. }
  193. func AddTranslationTranslatorGroup(channelID string, articleID string, orgID string, groupID string, e *casbin.Enforcer) {
  194. e.AddNamedPolicy("p", IDPrefixOrgGroup+groupID, IDPrefixOrg+orgID, IDPrefixTranslation+channelID+"+"+articleID, PermTrans)
  195. }
  196. /*
  197. * //TODO 是否有这个需求?
  198. * 好像不需要将译文的修改权限分享出去,用户需要的是翻译。
  199. * 即便分享出去,能做什么呢?解除绑定?
  200. */
  201. func AddTranslationWriter(channelID string, articleID string, orgID string, userID string, e *casbin.Enforcer) (string, error) {
  202. return "", errors.New("Do we realy need this function?")
  203. }
  204. func AddTranslationWriterGroup(channelID string, articleID string, orgID string, groupID string, e *casbin.Enforcer) (string, error) {
  205. return "", errors.New("Do we realy need this function?")
  206. }
  207. func UserCanReadArticle(userID string, orgID string, articleID string, e *casbin.Enforcer) (bool, error) {
  208. return e.Enforce(IDPrefixUser+userID, IDPrefixOrg+orgID, IDPrefixArticle+articleID, PermRead+PermWrite)
  209. }
  210. func UserCanReadChannel(userID string, orgID string, channelID string, e *casbin.Enforcer) (bool, error) {
  211. return e.Enforce(IDPrefixUser+userID, IDPrefixOrg+orgID, IDPrefixChannel+channelID, PermRead+PermWrite+PermTrans)
  212. }
  213. func UserCanWriteChannel(userID string, orgID string, channelID string, e *casbin.Enforcer) (bool, error) {
  214. return e.Enforce(IDPrefixUser+userID, IDPrefixOrg+orgID, IDPrefixChannel+channelID, PermWrite)
  215. }
  216. func UserCanTranslateChannel(userID string, orgID string, channelID string, e *casbin.Enforcer) (bool, error) {
  217. return e.Enforce(IDPrefixUser+userID, IDPrefixOrg+orgID, IDPrefixChannel+channelID, PermTrans)
  218. }
  219. func UserCanReadTranslation(userID string, orgID string, channelID string, articleID string, e *casbin.Enforcer) (bool, error) {
  220. // 先基于译文判断
  221. r, err := e.Enforce(IDPrefixUser+userID, IDPrefixOrg+orgID, IDPrefixTranslation+channelID+"+"+articleID, PermRead+PermTrans)
  222. if !r {
  223. // 再基于 版本风格 和 文章模板联合判断
  224. r_channel, _ := UserCanReadChannel(userID, orgID, channelID, e)
  225. r_article, _ := UserCanReadArticle(userID, orgID, articleID, e)
  226. if r_channel && r_article {
  227. return true, nil
  228. }
  229. }
  230. return r, err
  231. }
  232. func UserCanTranslateTranslation(userID string, orgID string, channelID string, articleID string, e *casbin.Enforcer) (bool, error) {
  233. // 先基于译文判断
  234. r, err := e.Enforce(IDPrefixUser+userID, IDPrefixOrg+orgID, IDPrefixTranslation+channelID+"+"+articleID, PermTrans)
  235. if !r {
  236. // 再基于 版本风格 和 文章模板联合判断
  237. r_channel, _ := UserCanTranslateChannel(userID, orgID, channelID, e)
  238. r_article, _ := UserCanReadArticle(userID, orgID, articleID, e)
  239. if r_channel && r_article {
  240. return true, nil
  241. }
  242. }
  243. return r, err
  244. }
  245. // ---------- Test Code ---------------
  246. func testPermission(userID string, e *casbin.Enforcer) {
  247. fmt.Println("\n")
  248. fmt.Printf("// ---- %s 是否有权限 查看 组织 zhang3 的文章模板 article-01?", userID)
  249. r_a, _ := UserCanReadArticle(userID, "zhang3", "article-01", e)
  250. fmt.Printf(" %t\n", r_a)
  251. fmt.Printf("// ---- %s 是否有权限 查看 组织 zhang3 的版本风格 chinese-01?", userID)
  252. r_c, _ := UserCanReadChannel(userID, "zhang3", "chinese-01", e)
  253. fmt.Printf(" %t\n", r_c)
  254. fmt.Printf("// ---- %s 是否有权限 修改 组织 zhang3 的版本风格 chinese-01?", userID)
  255. r_c_w, _ := UserCanWriteChannel(userID, "zhang3", "chinese-01", e)
  256. fmt.Printf(" %t\n", r_c_w)
  257. fmt.Printf("// ---- %s 是否有权限 查看 组织 zhang3 的版本风格 chinese-02?", userID)
  258. r_c02, _ := UserCanReadChannel(userID, "zhang3", "chinese-02", e)
  259. fmt.Printf(" %t\n", r_c02)
  260. fmt.Printf("// ---- %s 是否有权限 修改 组织 zhang3 的版本风格 chinese-02?", userID)
  261. r_c_w02, _ := UserCanWriteChannel(userID, "zhang3", "chinese-02", e)
  262. fmt.Printf(" %t\n", r_c_w02)
  263. fmt.Printf("// ---- %s 是否有权限 基于 组织 zhang3 的版本风格 chinese-01 进行翻译?", userID)
  264. r_t, _ := UserCanTranslateChannel(userID, "zhang3", "chinese-01", e)
  265. fmt.Printf(" %t\n", r_t)
  266. fmt.Printf("// ---- %s 是否能查看 组织 zhang3 的译文 chinese-01+article-01?", userID)
  267. r_tt, _ := UserCanReadTranslation(userID, "zhang3", "chinese-01", "article-01", e)
  268. fmt.Printf(" %t\n", r_tt)
  269. fmt.Printf("// ---- %s 是否能翻译 组织 zhang3 的译文 chinese-01+article-01?", userID)
  270. r_tt1, _ := UserCanTranslateTranslation(userID, "zhang3", "chinese-01", "article-01", e)
  271. fmt.Printf(" %t\n", r_tt1)
  272. fmt.Printf("// ---- %s 是否能查看 组织 zhang3 的译文 chinese-02+article-01?", userID)
  273. r1_tt, _ := UserCanReadTranslation(userID, "zhang3", "chinese-02", "article-01", e)
  274. fmt.Printf(" %t\n", r1_tt)
  275. fmt.Printf("// ---- %s 是否能翻译 组织 zhang3 的译文 chinese-02+article-01?", userID)
  276. r1_tt1, _ := UserCanTranslateTranslation(userID, "zhang3", "chinese-02", "article-01", e)
  277. fmt.Printf(" %t\n", r1_tt1)
  278. fmt.Println("\n")
  279. }
  280. func main() {
  281. // 获取数据库配置
  282. dbConfig := config.GetConfig().Database
  283. dataSourceName := fmt.Sprintf("user=%s password=%s host=%s port=%s sslmode=%s",
  284. dbConfig.Username,
  285. dbConfig.Password,
  286. dbConfig.Host,
  287. dbConfig.Port,
  288. dbConfig.SSLMode)
  289. // 初始化 Casbin,默认使用数据库 `casbin`,如果不存在,则创建新数据库 `casbin`
  290. a, _ := xormadapter.NewAdapter("postgres", dataSourceName)
  291. // 加载权限模型
  292. e, _ := casbin.NewEnforcer("./rbac/rbac_model.conf", a)
  293. // e.AddNamedMatchingFunc("g", "", util.RegexMatch)
  294. // e.AddNamedDomainMatchingFunc("g", "", util.RegexMatch)
  295. // 从数据库加载已定义的权限策略
  296. e.LoadPolicy()
  297. /*
  298. * 按照业务逻辑测试验证
  299. */
  300. fmt.Println("// 创建新用户 zhang3,等于同时创建了 Org: zhang3,只是 OrgID 和 UserID 相同")
  301. CreateOrg("zhang3", "zhang3", e)
  302. fmt.Println("// 用户 zhang3 创建了 版本风格 chinese-01")
  303. CreateChannel("chinese-01", "zhang3", e)
  304. fmt.Println("// 用户 zhang3 创建了 版本风格 chinese-02")
  305. CreateChannel("chinese-02", "zhang3", e)
  306. fmt.Println("// 用户 zhang3 在组织 zhang3 下创建了 文章模板 article-01")
  307. CreateArticle("article-01", "zhang3", e)
  308. fmt.Println("// 用户 zhang3 基于 版本风格 chinese-01 和 文章 article-01 创建了 译文 chinses-01+article-01")
  309. CreateTranslation("chinese-01", "article-01", "zhang3", e)
  310. fmt.Println("// 用户 zhang3 基于 版本风格 chinese-02 和 文章 article-01 创建了 译文 chinses-02+article-01")
  311. CreateTranslation("chinese-02", "article-01", "zhang3", e)
  312. testPermission("zhang3", e)
  313. fmt.Println("// 创建新用户 li4,等于同时创建了 Org: li4,只是 OrgID 和 UserID 相同")
  314. CreateOrg("li4", "li4", e)
  315. testPermission("li4", e)
  316. fmt.Println("// 将用户 li4 加入到组织 zhang3 里")
  317. AddOrgMember("zhang3", "li4", e)
  318. testPermission("li4", e)
  319. fmt.Println("// 创建新用户 wang5,等于同时创建了 Org: wang5,只是 OrgID 和 UserID 相同")
  320. CreateOrg("wang5", "wang5", e)
  321. testPermission("wang5", e)
  322. fmt.Println("// 将 chinese-01 分享给 wang5,只读 ")
  323. AddChannelReader("chinese-01", "zhang3", "wang5", e)
  324. fmt.Println("// 将 article-01 分享给 wang5,只读 ")
  325. AddArticleReader("article-01", "zhang3", "wang5", e)
  326. testPermission("wang5", e)
  327. fmt.Println("// 将 chinese-01 分享给 wang5,可以翻译 ")
  328. AddChannelTranslator("chinese-01", "zhang3", "wang5", e)
  329. testPermission("wang5", e)
  330. fmt.Println("// 创建新用户 zhao6,等于同时创建了 Org: zhao6,只是 OrgID 和 UserID 相同")
  331. CreateOrg("zhao6", "zhao6", e)
  332. testPermission("zhao6", e)
  333. fmt.Println("// 将 译文 chinese-01+article-01 分享给 zhao6,翻译权限")
  334. AddTranslationTranslator("chinese-01", "article-01", "zhang3", "zhao6", e)
  335. fmt.Println("// 将 译文 chinese-02+article-01 分享给 zhao6,查看权限")
  336. AddTranslationReader("chinese-02", "article-01", "zhang3", "zhao6", e)
  337. testPermission("zhao6", e)
  338. // Modify the policy.
  339. // e.AddPolicy(...)
  340. // e.RemovePolicy(...)
  341. // Save the policy back to DB.
  342. e.SavePolicy()
  343. }