access.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. /*
  2. * EliasDB
  3. *
  4. * Copyright 2016 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the Mozilla Public
  7. * License, v. 2.0. If a copy of the MPL was not distributed with this
  8. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  9. */
  10. /*
  11. Package ac contains management code for access control.
  12. */
  13. package ac
  14. import (
  15. "fmt"
  16. "log"
  17. "net/http"
  18. "net/url"
  19. "strings"
  20. "devt.de/krotik/common/datautil"
  21. "devt.de/krotik/common/httputil/access"
  22. "devt.de/krotik/common/httputil/auth"
  23. "devt.de/krotik/common/httputil/user"
  24. "devt.de/krotik/eliasdb/api"
  25. )
  26. // Code and datastructures relating to access control
  27. // ==================================================
  28. /*
  29. PublicAccessControlEndpointMap contains endpoints which should be publically
  30. available when access control is used
  31. */
  32. var PublicAccessControlEndpointMap = map[string]api.RestEndpointInst{
  33. EndpointLogin: LoginEndpointInst,
  34. EndpointLogout: LogoutEndpointInst,
  35. EndpointWhoAmI: WhoAmIEndpointInst,
  36. }
  37. /*
  38. AccessManagementEndpointMap contains endpoints which can manage access rights
  39. */
  40. var AccessManagementEndpointMap = map[string]api.RestEndpointInst{
  41. EndpointUser: UserEndpointInst,
  42. }
  43. /*
  44. LogAccess is used to log access requests
  45. */
  46. var LogAccess = log.Print
  47. /*
  48. UserDB is the global user database which holds the password hashes and user
  49. details.
  50. */
  51. var UserDB *datautil.EnforcedUserDB
  52. /*
  53. ACL is the global AccessControlLists object which should be used to check
  54. user access rights.
  55. */
  56. var ACL *AccessControlLists
  57. /*
  58. AuthHandler is a wrapper object which has a HandleFunc similar to http.HandleFunc.
  59. The HandleFunc of this object should be used for all endpoint which should check
  60. for authentication and authorization.
  61. */
  62. var AuthHandler *auth.CookieAuthHandleFuncWrapper
  63. /*
  64. DefaultAccessDB is the default access table for EliasDB
  65. */
  66. var DefaultAccessDB = []byte(`
  67. /*
  68. Access control file for EliasDB. This file controls the access rights for each user.
  69. Rights to resources are assigned to groups. Users are assigned to groups.
  70. This file is monitored by the server - any changes to this file are picked up
  71. by the server immediately. Equally, any change on the server side is immediately
  72. written to this file.
  73. The comments in this file are for initial comprehension only. They will be
  74. removed as soon as the users, groups or permissions are modified from the
  75. server side.
  76. */
  77. {
  78. "groups": {
  79. "public": {
  80. // Page access
  81. // ===========
  82. "/": "-R--", // Access to the root page
  83. // Resource access
  84. // ===============
  85. "/css/*": "-R--", // Access to CSS rules
  86. "/js/*": "-R--", // Access to JavaScript files
  87. "/img/*": "-R--", // Access to image files
  88. "/vendor/*": "-R--", // Access to frontend libraries
  89. // REST API access
  90. // ===============
  91. "/db/*": "-R--" // Access to database (read)
  92. },
  93. "admin": {
  94. // REST API access
  95. // ===============
  96. "/db/*": "CRUD" // Access to database
  97. }
  98. },
  99. "users": {
  100. "elias": [ // Default EliasDB admin user
  101. "public",
  102. "admin"
  103. ],
  104. "johndoe" : [ // Default EliasDB public user
  105. "public"
  106. ]
  107. }
  108. }
  109. `[1:])
  110. /*
  111. InitACLs initializes the access control list object.
  112. */
  113. func InitACLs(tab access.ACLTable) {
  114. ACL = &AccessControlLists{tab}
  115. }
  116. // Access request types
  117. //
  118. const (
  119. CREATE = "create"
  120. READ = "read"
  121. UPDATE = "update"
  122. DELETE = "delete"
  123. )
  124. // Access request results
  125. //
  126. const (
  127. GRANTED = "granted"
  128. DENIED = "denied"
  129. )
  130. // Mapping from http request method to access request type
  131. //
  132. var httpRequestMapping = map[string]string{
  133. "": READ,
  134. "get": READ,
  135. "put": UPDATE,
  136. "post": CREATE,
  137. "delete": DELETE,
  138. }
  139. /*
  140. AccessControlLists store the access rights of groups and which users are
  141. member of which groups.
  142. */
  143. type AccessControlLists struct {
  144. access.ACLTable
  145. }
  146. /*
  147. CheckHTTPRequest checks the request of a given user to a resource.
  148. */
  149. func (a *AccessControlLists) CheckHTTPRequest(w http.ResponseWriter, r *http.Request, user string) bool {
  150. var result = DENIED
  151. var detail = "No rule which grants access was found"
  152. // Extract request details
  153. requestType := httpRequestMapping[strings.ToLower(r.Method)]
  154. requestResource := r.URL.Path
  155. // Build rights object
  156. requestRights := &access.Rights{
  157. Create: requestType == CREATE,
  158. Read: requestType == READ,
  159. Update: requestType == UPDATE,
  160. Delete: requestType == DELETE,
  161. }
  162. // Check ACLTable
  163. if res, resDetail, err := a.IsPermitted(user, requestResource, requestRights); res && err == nil {
  164. result = GRANTED
  165. detail = resDetail
  166. } else if err != nil {
  167. detail = err.Error()
  168. }
  169. // Log the result
  170. text := fmt.Sprintf("User %v requested %v access to %v - %v (%v)",
  171. user, requestType, requestResource, result, detail)
  172. if result == GRANTED {
  173. LogAccess(text)
  174. } else {
  175. LogAccess(text)
  176. http.Error(w, fmt.Sprintf("Requested %v access to %v was denied",
  177. requestType, requestResource),
  178. http.StatusForbidden)
  179. }
  180. return result == GRANTED
  181. }
  182. // Default error handlers
  183. /*
  184. CallbackSessionExpired handles requests where the session has expired.
  185. */
  186. var CallbackSessionExpired = func(w http.ResponseWriter, r *http.Request) {
  187. u, ok := AuthHandler.CheckAuth(r)
  188. // Remove all cookies
  189. AuthHandler.RemoveAuthCookie(w)
  190. user.UserSessionManager.RemoveSessionCookie(w)
  191. if ok {
  192. LogAccess("User ", u, " session expired")
  193. }
  194. origPath := r.URL.Path
  195. if r.URL.RawQuery != "" {
  196. origPath += "?" + r.URL.RawQuery
  197. }
  198. http.Redirect(w, r, fmt.Sprintf("/login.html?msg=Session+Expired&ref=%v",
  199. url.QueryEscape(origPath)), http.StatusFound)
  200. }
  201. /*
  202. CallbackUnauthorized handles requests which are unauthorized.
  203. */
  204. var CallbackUnauthorized = func(w http.ResponseWriter, r *http.Request) {
  205. LogAccess("Unauthorized request to ", r.URL.Path,
  206. " from ", r.RemoteAddr, " (", r.UserAgent(), " Cookies: ", r.Cookies(), ")")
  207. if strings.HasPrefix(r.URL.Path, api.APIRoot) {
  208. // No redirect for REST clients
  209. http.Error(w, "Valid credentials required", http.StatusForbidden)
  210. } else {
  211. origPath := r.URL.Path
  212. if r.URL.RawQuery != "" {
  213. origPath += "?" + r.URL.RawQuery
  214. }
  215. http.Redirect(w, r, fmt.Sprintf("/login.html?ref=%v",
  216. url.QueryEscape(origPath)), http.StatusFound)
  217. }
  218. }
  219. // Helper functions
  220. // ================
  221. /*
  222. checkResources check given resources for a GET request.
  223. */
  224. func checkResources(w http.ResponseWriter, resources []string, requiredMin int, requiredMax int, errorMsg string) bool {
  225. if len(resources) < requiredMin {
  226. http.Error(w, errorMsg, http.StatusBadRequest)
  227. return false
  228. } else if len(resources) > requiredMax {
  229. http.Error(w, "Invalid resource specification: "+strings.Join(resources[1:], "/"), http.StatusBadRequest)
  230. return false
  231. }
  232. return true
  233. }