| 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 acimport (	"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 publicallyavailable 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 userdetails.*/var UserDB *datautil.EnforcedUserDB/*ACL is the global AccessControlLists object which should be used to checkuser 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 checkfor 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 upby the server immediately. Equally, any change on the server side is immediatelywritten to this file.The comments in this file are for initial comprehension only. They will beremoved as soon as the users, groups or permissions are modified from theserver 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 aremember 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}
 |