implemented authorization
This commit is contained in:
@@ -11,17 +11,26 @@ import (
|
||||
|
||||
func (conn *TcpConnection) serveFileOrDir(req *http.Request) *http.Response {
|
||||
path, share, urlStatus := conn.srv.Conf.Share.MapUrl(req.Url)
|
||||
// TODO implement authorization
|
||||
_ = share
|
||||
// handle authorization
|
||||
if share != nil {
|
||||
access := checkSharePasswords(share, req)
|
||||
if access < AccessReadOnly {
|
||||
return conn.createErrorResponse(req, false, http.StatusUnauthorized,
|
||||
"authorization required").
|
||||
WithHeader("www-authenticate",
|
||||
`Basic realm="`+share.Url+`", charset="UTF-8"`)
|
||||
}
|
||||
}
|
||||
|
||||
switch urlStatus {
|
||||
case UrlFile:
|
||||
return conn.serveFile(req, path)
|
||||
case UrlDir:
|
||||
return conn.serveDirListing(req, path)
|
||||
case UrlNotFound:
|
||||
return conn.createErrorResponse(req, false, http.StatusNotFound, path)
|
||||
return conn.createErrorResponse(req, false, http.StatusNotFound, req.Url)
|
||||
case UrlForbidden:
|
||||
return conn.createErrorResponse(req, false, http.StatusForbidden, path)
|
||||
return conn.createErrorResponse(req, false, http.StatusForbidden, req.Url)
|
||||
case UrlIncorrect:
|
||||
return conn.createErrorResponse(req, false, http.StatusBadRequest,
|
||||
"invalid url '"+req.Url+"'")
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"serveg/internal/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ShareAccess uint8
|
||||
|
||||
const (
|
||||
AccessDenied ShareAccess = iota
|
||||
AccessReadOnly
|
||||
AccessReadWrite
|
||||
)
|
||||
|
||||
// writing: share allows writing and client sent write password
|
||||
// reading: share is public or client sent password (read or write)
|
||||
func checkSharePasswords(share *SharedLocation, req *http.Request,
|
||||
) ShareAccess {
|
||||
reqPass := getBasicAuthPassword(req)
|
||||
canWrite := share.WritePassword != "" &&
|
||||
(reqPass != "" && passwordMatches(reqPass, share.WritePassword))
|
||||
if canWrite {
|
||||
return AccessReadWrite
|
||||
}
|
||||
canRead := share.ReadPassword == "" ||
|
||||
(reqPass != "" && passwordMatches(reqPass, share.ReadPassword))
|
||||
if canRead {
|
||||
return AccessReadOnly
|
||||
}
|
||||
return AccessDenied
|
||||
}
|
||||
|
||||
func getBasicAuthPassword(req *http.Request) string {
|
||||
header := req.Headers["authorization"]
|
||||
scheme, b64, ok := strings.Cut(header, " ")
|
||||
if !ok || !strings.EqualFold(scheme, "Basic") {
|
||||
return ""
|
||||
}
|
||||
decoded, err := base64.StdEncoding.DecodeString(strings.TrimSpace(b64))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// try get password from username:password
|
||||
_, password, _ := strings.Cut(string(decoded), ":")
|
||||
return password
|
||||
}
|
||||
|
||||
func passwordMatches(got, want string) bool {
|
||||
// hash first: ConstantTimeCompare returns early when lengths differ,
|
||||
// which would leak the password length through timing
|
||||
g := sha256.Sum256([]byte(got))
|
||||
w := sha256.Sum256([]byte(want))
|
||||
return subtle.ConstantTimeCompare(g[:], w[:]) == 1
|
||||
}
|
||||
Reference in New Issue
Block a user