diff options
| author | Shulhan <ms@kilabit.info> | 2022-03-30 01:49:14 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2022-03-30 01:49:14 +0700 |
| commit | 8a8de54d66fd14d1fbdb7e8598c597f871230980 (patch) | |
| tree | 1104e23c761563502a2f05be2ef1cda3e0fe12f6 | |
| parent | 0c288a5880fbc0fceb792dcfd0747d95163a90dc (diff) | |
| download | pakakeh.go-8a8de54d66fd14d1fbdb7e8598c597f871230980.tar.xz | |
lib/http: implement handler to authorized request to Server Memfs
The FSAuthHandler in the ServerOptions define a function that will
be called to each GET request to Server Memfs instance using the value
from the HTTP Request instance.
If the request is not authorized it must return false and the HTTP
response will be set to 401 Unauthorized with empty body.
| -rw-r--r-- | lib/http/fs_auth_handler.go | 16 | ||||
| -rw-r--r-- | lib/http/http_test.go | 31 | ||||
| -rw-r--r-- | lib/http/server.go | 13 | ||||
| -rw-r--r-- | lib/http/server_test.go | 73 | ||||
| -rw-r--r-- | lib/http/serveroptions.go | 4 | ||||
| -rw-r--r-- | lib/http/testdata/auth.txt | 1 | ||||
| -rw-r--r-- | lib/http/testdata/auth/index.html | 1 | ||||
| -rw-r--r-- | lib/http/testdata/auth/sub/index.html | 1 |
8 files changed, 139 insertions, 1 deletions
diff --git a/lib/http/fs_auth_handler.go b/lib/http/fs_auth_handler.go new file mode 100644 index 00000000..9a793d64 --- /dev/null +++ b/lib/http/fs_auth_handler.go @@ -0,0 +1,16 @@ +// Copyright 2022, Shulhan <ms@kilabit.info>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package http + +import "net/http" + +// +// FSAuthHandler define the function to authorized each GET request to +// Server MemFS using value from the HTTP Request instance. +// +// If request is not authorized it must return false and the HTTP response +// will be set to 401 Unauthorized with empty body. +// +type FSAuthHandler func(req *http.Request) (ok bool) diff --git a/lib/http/http_test.go b/lib/http/http_test.go index 707b383f..d5eb2c44 100644 --- a/lib/http/http_test.go +++ b/lib/http/http_test.go @@ -9,7 +9,9 @@ import ( "log" "net/http" "os" + "strings" "testing" + "time" "github.com/shuLhan/share/lib/memfs" ) @@ -56,7 +58,8 @@ func TestMain(m *testing.M) { Development: true, }, }, - Address: serverAddress, + HandleFSAuth: handleFSAuth, + Address: serverAddress, } testServerUrl = fmt.Sprintf("http://" + serverAddress) @@ -75,6 +78,8 @@ func TestMain(m *testing.M) { } }() + time.Sleep(400 * time.Millisecond) // Wait for server to be ready. + status := m.Run() err = testServer.Stop(0) @@ -89,6 +94,30 @@ var ( testDownloadBody []byte ) +// handleFSAuth authenticate the request to Memfs using cookie. +// It will return true if request path is "/auth/" and cookie name "sid" exist +// with value "authz". +func handleFSAuth(req *http.Request) bool { + var ( + lowerPath = strings.ToLower(req.URL.Path) + + cookieSid *http.Cookie + err error + ) + log.Printf("handleFSAuth: %s", lowerPath) + if !strings.HasPrefix(lowerPath, "/auth/") { + return true + } + cookieSid, err = req.Cookie("sid") + if err != nil { + return false + } + if cookieSid.Value != "authz" { + return false + } + return true +} + func registerEndpoints() { var err error diff --git a/lib/http/server.go b/lib/http/server.go index 4a9f4aa7..e6ef66b1 100644 --- a/lib/http/server.go +++ b/lib/http/server.go @@ -489,6 +489,9 @@ func (srv *Server) handleDelete(res http.ResponseWriter, req *http.Request) { // response body set to the content of file. // If the request Method is HEAD, only the header will be sent back to client. // +// If the request Path exists and Server Options FSAuthHandler is set and +// returning false, it will return 401 Unauthorized. +// // If the request Path is not exist it will return 404 Not Found. // func (srv *Server) HandleFS(res http.ResponseWriter, req *http.Request) { @@ -501,6 +504,7 @@ func (srv *Server) HandleFS(res http.ResponseWriter, req *http.Request) { body []byte size int64 err error + isAuthorized bool ) node = srv.getFSNode(req.URL.Path) @@ -509,6 +513,15 @@ func (srv *Server) HandleFS(res http.ResponseWriter, req *http.Request) { return } + if srv.Options.HandleFSAuth != nil { + req.URL.Path = node.Path + isAuthorized = srv.Options.HandleFSAuth(req) + if !isAuthorized { + res.WriteHeader(http.StatusUnauthorized) + return + } + } + res.Header().Set(HeaderContentType, node.ContentType) responseETag = strconv.FormatInt(node.ModTime().Unix(), 10) diff --git a/lib/http/server_test.go b/lib/http/server_test.go index 7364469d..a63bdb92 100644 --- a/lib/http/server_test.go +++ b/lib/http/server_test.go @@ -787,3 +787,76 @@ func TestStatusError(t *testing.T) { test.Assert(t, "Body", c.expBody, string(body)) } } + +// TestServer_HandleFS_Auth test GET on memfs with authorization. +func TestServer_HandleFS_Auth(t *testing.T) { + type testCase struct { + cookieSid *http.Cookie + desc string + reqPath string + expStatusCode int + } + + var ( + c testCase + req *http.Request + res *http.Response + err error + ) + + cases := []testCase{{ + desc: "With public path", + reqPath: "/index.html", + expStatusCode: http.StatusOK, + }, { + desc: "With /auth.txt", + reqPath: "/auth.txt", + expStatusCode: http.StatusOK, + }, { + desc: "With /auth path no cookie", + reqPath: "/auth", + expStatusCode: http.StatusUnauthorized, + }, { + desc: "With /auth path and cookie", + reqPath: "/auth", + cookieSid: &http.Cookie{ + Name: "sid", + Value: "authz", + }, + expStatusCode: http.StatusOK, + }, { + desc: "With invalid /auth path and cookie", + reqPath: "/auth/notexist", + cookieSid: &http.Cookie{ + Name: "sid", + Value: "authz", + }, + expStatusCode: http.StatusNotFound, + }, { + desc: "With /auth/sub path and cookie", + reqPath: "/auth/sub", + cookieSid: &http.Cookie{ + Name: "sid", + Value: "authz", + }, + expStatusCode: http.StatusOK, + }} + + for _, c = range cases { + req, err = http.NewRequest(http.MethodGet, testServerUrl+c.reqPath, nil) + if err != nil { + t.Fatalf("%s: %s", c.desc, err) + } + + if c.cookieSid != nil { + req.AddCookie(c.cookieSid) + } + + res, err = client.Do(req) + if err != nil { + t.Fatalf("%s: %s", c.desc, err) + } + + test.Assert(t, c.desc, c.expStatusCode, res.StatusCode) + } +} diff --git a/lib/http/serveroptions.go b/lib/http/serveroptions.go index 805f4dc7..b548d09c 100644 --- a/lib/http/serveroptions.go +++ b/lib/http/serveroptions.go @@ -26,6 +26,10 @@ type ServerOptions struct { // See https://pkg.go.dev/github.com/shuLhan/share/lib/memfs#hdr-Go_embed Memfs *memfs.MemFS + // HandleFSAuth handle authorization to GET request on Memfs. + // If null it means all request is allowed. + HandleFSAuth FSAuthHandler + // Address define listen address, using ip:port format. // This field is optional, default to ":80". Address string diff --git a/lib/http/testdata/auth.txt b/lib/http/testdata/auth.txt new file mode 100644 index 00000000..69e75143 --- /dev/null +++ b/lib/http/testdata/auth.txt @@ -0,0 +1 @@ +Hello, auth.txt! diff --git a/lib/http/testdata/auth/index.html b/lib/http/testdata/auth/index.html new file mode 100644 index 00000000..4b00ac04 --- /dev/null +++ b/lib/http/testdata/auth/index.html @@ -0,0 +1 @@ +<html><body>Hello, authorized world!</body></html> diff --git a/lib/http/testdata/auth/sub/index.html b/lib/http/testdata/auth/sub/index.html new file mode 100644 index 00000000..476b228b --- /dev/null +++ b/lib/http/testdata/auth/sub/index.html @@ -0,0 +1 @@ +<html><body>Hello, /auth/sub!</body></html> |
