123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- /*
- * EliasDB
- *
- * Copyright 2016 Matthias Ladkau. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
- /*
- Package ac contains management code for access control.
- */
- package ac
- import (
- "fmt"
- "log"
- "net/http"
- "net/url"
- "strings"
- "devt.de/krotik/common/datautil"
- "devt.de/krotik/common/httputil/access"
- "devt.de/krotik/common/httputil/auth"
- "devt.de/krotik/common/httputil/user"
- "devt.de/krotik/eliasdb/api"
- )
- // Code and datastructures relating to access control
- // ==================================================
- /*
- PublicAccessControlEndpointMap contains endpoints which should be publically
- available when access control is used
- */
- var PublicAccessControlEndpointMap = map[string]api.RestEndpointInst{
- EndpointLogin: LoginEndpointInst,
- EndpointLogout: LogoutEndpointInst,
- EndpointWhoAmI: WhoAmIEndpointInst,
- }
- /*
- AccessManagementEndpointMap contains endpoints which can manage access rights
- */
- var AccessManagementEndpointMap = map[string]api.RestEndpointInst{
- EndpointUser: UserEndpointInst,
- }
- /*
- LogAccess is used to log access requests
- */
- var LogAccess = log.Print
- /*
- UserDB is the global user database which holds the password hashes and user
- details.
- */
- var UserDB *datautil.EnforcedUserDB
- /*
- ACL is the global AccessControlLists object which should be used to check
- user access rights.
- */
- var ACL *AccessControlLists
- /*
- AuthHandler is a wrapper object which has a HandleFunc similar to http.HandleFunc.
- The HandleFunc of this object should be used for all endpoint which should check
- for authentication and authorization.
- */
- var AuthHandler *auth.CookieAuthHandleFuncWrapper
- /*
- DefaultAccessDB is the default access table for EliasDB
- */
- var DefaultAccessDB = []byte(`
- /*
- Access control file for EliasDB. This file controls the access rights for each user.
- Rights to resources are assigned to groups. Users are assigned to groups.
- This file is monitored by the server - any changes to this file are picked up
- by the server immediately. Equally, any change on the server side is immediately
- written to this file.
- The comments in this file are for initial comprehension only. They will be
- removed as soon as the users, groups or permissions are modified from the
- server side.
- */
- {
- "groups": {
- "public": {
- // Page access
- // ===========
- "/": "-R--", // Access to the root page
- // Resource access
- // ===============
- "/css/*": "-R--", // Access to CSS rules
- "/js/*": "-R--", // Access to JavaScript files
- "/img/*": "-R--", // Access to image files
- "/vendor/*": "-R--", // Access to frontend libraries
- // REST API access
- // ===============
- "/db/*": "-R--" // Access to database (read)
- },
- "admin": {
- // REST API access
- // ===============
- "/db/*": "CRUD" // Access to database
- }
- },
- "users": {
- "elias": [ // Default EliasDB admin user
- "public",
- "admin"
- ],
- "johndoe" : [ // Default EliasDB public user
- "public"
- ]
- }
- }
- `[1:])
- /*
- InitACLs initializes the access control list object.
- */
- func InitACLs(tab access.ACLTable) {
- ACL = &AccessControlLists{tab}
- }
- // Access request types
- //
- const (
- CREATE = "create"
- READ = "read"
- UPDATE = "update"
- DELETE = "delete"
- )
- // Access request results
- //
- const (
- GRANTED = "granted"
- DENIED = "denied"
- )
- // Mapping from http request method to access request type
- //
- var httpRequestMapping = map[string]string{
- "": READ,
- "get": READ,
- "put": UPDATE,
- "post": CREATE,
- "delete": DELETE,
- }
- /*
- AccessControlLists store the access rights of groups and which users are
- member of which groups.
- */
- type AccessControlLists struct {
- access.ACLTable
- }
- /*
- CheckHTTPRequest checks the request of a given user to a resource.
- */
- func (a *AccessControlLists) CheckHTTPRequest(w http.ResponseWriter, r *http.Request, user string) bool {
- var result = DENIED
- var detail = "No rule which grants access was found"
- // Extract request details
- requestType := httpRequestMapping[strings.ToLower(r.Method)]
- requestResource := r.URL.Path
- // Build rights object
- requestRights := &access.Rights{
- Create: requestType == CREATE,
- Read: requestType == READ,
- Update: requestType == UPDATE,
- Delete: requestType == DELETE,
- }
- // Check ACLTable
- if res, resDetail, err := a.IsPermitted(user, requestResource, requestRights); res && err == nil {
- result = GRANTED
- detail = resDetail
- } else if err != nil {
- detail = err.Error()
- }
- // Log the result
- text := fmt.Sprintf("User %v requested %v access to %v - %v (%v)",
- user, requestType, requestResource, result, detail)
- if result == GRANTED {
- LogAccess(text)
- } else {
- LogAccess(text)
- http.Error(w, fmt.Sprintf("Requested %v access to %v was denied",
- requestType, requestResource),
- http.StatusForbidden)
- }
- return result == GRANTED
- }
- // Default error handlers
- /*
- CallbackSessionExpired handles requests where the session has expired.
- */
- var CallbackSessionExpired = func(w http.ResponseWriter, r *http.Request) {
- u, ok := AuthHandler.CheckAuth(r)
- // Remove all cookies
- AuthHandler.RemoveAuthCookie(w)
- user.UserSessionManager.RemoveSessionCookie(w)
- if ok {
- LogAccess("User ", u, " session expired")
- }
- origPath := r.URL.Path
- if r.URL.RawQuery != "" {
- origPath += "?" + r.URL.RawQuery
- }
- http.Redirect(w, r, fmt.Sprintf("/login.html?msg=Session+Expired&ref=%v",
- url.QueryEscape(origPath)), http.StatusFound)
- }
- /*
- CallbackUnauthorized handles requests which are unauthorized.
- */
- var CallbackUnauthorized = func(w http.ResponseWriter, r *http.Request) {
- LogAccess("Unauthorized request to ", r.URL.Path,
- " from ", r.RemoteAddr, " (", r.UserAgent(), " Cookies: ", r.Cookies(), ")")
- if strings.HasPrefix(r.URL.Path, api.APIRoot) {
- // No redirect for REST clients
- http.Error(w, "Valid credentials required", http.StatusForbidden)
- } else {
- origPath := r.URL.Path
- if r.URL.RawQuery != "" {
- origPath += "?" + r.URL.RawQuery
- }
- http.Redirect(w, r, fmt.Sprintf("/login.html?ref=%v",
- url.QueryEscape(origPath)), http.StatusFound)
- }
- }
- // Helper functions
- // ================
- /*
- checkResources check given resources for a GET request.
- */
- func checkResources(w http.ResponseWriter, resources []string, requiredMin int, requiredMax int, errorMsg string) bool {
- if len(resources) < requiredMin {
- http.Error(w, errorMsg, http.StatusBadRequest)
- return false
- } else if len(resources) > requiredMax {
- http.Error(w, "Invalid resource specification: "+strings.Join(resources[1:], "/"), http.StatusBadRequest)
- return false
- }
- return true
- }
|