[full-ci] enhancement: allow ocis to provide custom web applications (#8523)

* enhancement: allow ocis to provide custom web applications

* enhancement: add an option to disable web apps

* test: add default logger tests

* test: add app loading tests

* test: add asset server tests

* enhancement: make use of dedicated app conf file and app asset paths

* enhancement: adjust asset locations and deprecate WEB_ASSET_PATH

* enhancement: get rid of default logger and use the service level logger instead

* Apply suggestions from code review

Co-authored-by: Benedikt Kulmann <benedikt@kulmann.biz>
Co-authored-by: kobergj <juliankoberg@googlemail.com>

* enhancement: use basename as app id

* Apply suggestions from code review

Co-authored-by: Martin <github@diemattels.at>

* enhancement: use afero as fs abstraction

* enhancement: simplify logo upload

* enhancement: make use of introductionVersion field annotations

---------

Co-authored-by: Benedikt Kulmann <benedikt@kulmann.biz>
Co-authored-by: kobergj <juliankoberg@googlemail.com>
Co-authored-by: Martin <github@diemattels.at>
This commit is contained in:
Florian Schade
2024-03-05 14:11:18 +01:00
committed by GitHub
co-authored by Benedikt Kulmann kobergj Martin
parent 6ba9e4adf7
commit 6814c61506
63 changed files with 5835 additions and 130 deletions
+20 -9
View File
@@ -3,6 +3,7 @@ package assets
import (
"bytes"
"io"
"io/fs"
"mime"
"net/http"
"path"
@@ -12,32 +13,42 @@ import (
)
type fileServer struct {
root http.FileSystem
fsys http.FileSystem
}
func FileServer(root http.FileSystem) http.Handler {
return &fileServer{root}
func FileServer(fsys fs.FS) http.Handler {
return &fileServer{http.FS(fsys)}
}
func (f *fileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
upath := path.Clean(path.Join("/", r.URL.Path))
r.URL.Path = upath
uPath := path.Clean(path.Join("/", r.URL.Path))
r.URL.Path = uPath
fallbackIndex := func() {
tryIndex := func() {
r.URL.Path = "/index.html"
// not every fs contains a file named index.html,
// therefore, we need to check if the file exists and stop the recursion if it doesn't
file, err := f.fsys.Open(r.URL.Path)
if err != nil {
http.NotFound(w, r)
return
}
defer file.Close()
f.ServeHTTP(w, r)
}
asset, err := f.root.Open(upath)
asset, err := f.fsys.Open(uPath)
if err != nil {
fallbackIndex()
tryIndex()
return
}
defer asset.Close()
s, _ := asset.Stat()
if s.IsDir() {
fallbackIndex()
tryIndex()
return
}
+125
View File
@@ -0,0 +1,125 @@
package assets_test
import (
"fmt"
"io"
"io/fs"
"net/http"
"net/http/httptest"
"testing"
"testing/fstest"
"github.com/onsi/gomega"
"github.com/owncloud/ocis/v2/services/web/pkg/assets"
)
func TestFileServer(t *testing.T) {
g := gomega.NewWithT(t)
recorderStatus := func(s int) string {
return fmt.Sprintf("%03d %s", s, http.StatusText(s))
}
{
s := assets.FileServer(fstest.MapFS{})
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/foo", nil)
//defer req.Body.Close()
s.ServeHTTP(w, req)
res := w.Result()
defer res.Body.Close()
g.Expect(res.Status).To(gomega.Equal(recorderStatus(http.StatusNotFound)))
}
for _, tt := range []struct {
name string
url string
fs fstest.MapFS
expected string
}{
{
name: "not found fallback",
url: "/index.txt",
fs: fstest.MapFS{
"index.html": &fstest.MapFile{
Data: []byte("index file content"),
},
},
expected: `<html><head><base href="/"/></head><body>index file content</body></html>`,
},
{
name: "directory fallback",
url: "/some-folder",
fs: fstest.MapFS{
"some-folder": &fstest.MapFile{
Mode: fs.ModeDir,
},
"index.html": &fstest.MapFile{
Data: []byte("index file content"),
},
},
expected: `<html><head><base href="/"/></head><body>index file content</body></html>`,
},
{
name: "index.html",
url: "/index.html",
fs: fstest.MapFS{
"index.html": &fstest.MapFile{
Data: []byte("index file content"),
},
},
expected: `<html><head><base href="/"/></head><body>index file content</body></html>`,
},
{
name: "oidc-callback.html",
url: "/oidc-callback.html",
fs: fstest.MapFS{
"index.html": &fstest.MapFile{
Data: []byte("oidc-callback file content"),
},
},
expected: `<html><head><base href="/"/></head><body>oidc-callback file content</body></html>`,
},
{
name: "oidc-silent-redirect.html",
url: "/oidc-silent-redirect.html",
fs: fstest.MapFS{
"index.html": &fstest.MapFile{
Data: []byte("oidc-silent-redirect file content"),
},
},
expected: `<html><head><base href="/"/></head><body>oidc-silent-redirect file content</body></html>`,
},
{
name: "some-file.txt",
url: "/some-file.txt",
fs: fstest.MapFS{
"some-file.txt": &fstest.MapFile{
Data: []byte("some file content"),
},
},
expected: "some file content",
},
} {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", tt.url, nil)
assets.FileServer(tt.fs).ServeHTTP(w, req)
res := w.Result()
defer res.Body.Close()
g.Expect(res.Status).To(gomega.Equal(recorderStatus(http.StatusOK)))
data, err := io.ReadAll(res.Body)
g.Expect(err).ToNot(gomega.HaveOccurred())
g.Expect(string(data)).To(gomega.Equal(tt.expected))
})
}
}