Commit a10f12d2 authored by Nicolas Pernoud's avatar Nicolas Pernoud
Browse files

feat: added new methods for vestimini

fix: added OnlyOffice Documents as recognized webdav client and improved caching
parent 8e07f910
......@@ -83,6 +83,7 @@ Configuration is done through environment variables. The meaning of the differen
| AUTH_URL | IdP's authentication URL | |
| TOKEN_URL | IdP's token URL | |
| USERINFO_URL | IdP's userinfo URL | |
| ISSUER | IdP's issuer for autoconfiguration of AUTH_URL, TOKEN_URL, USERINFO_URL if they are not already set |
| LOGOUT_URL | IdP's logout URL | |
| ONLYOFFICE_TITLE | Title used on the OnlyOffice document editor window | VestibuleOffice |
| ONLYOFFICE_SERVER | Url of the OnlyOffice document server used to edit documents | |
......@@ -109,7 +110,7 @@ Every branding asset is in `web/assets/brand` directory. They can be altered acc
### Update dependencies
```bash
go get -u
go get -u -t ./...
go mod tidy
```
......
......@@ -23,13 +23,44 @@ func Init(portFromMain int) {
func CreateMockOAuth2() *http.ServeMux {
mux := http.NewServeMux()
// Returns authorization code back to the user
mux.HandleFunc("/auth", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`
{
"response_types_supported": [
"code",
"id_token",
"token",
"code id_token",
"code token",
"token id_token",
"code id_token token"
],
"request_parameter_supported": true,
"request_uri_parameter_supported": false,
"jwks_uri": "http://127.0.0.1:8090/jwk",
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS512"
],
"registration_endpoint": "http://127.0.0.1:8090/register",
"issuer": "http://127.0.0.1:8090",
"authorization_endpoint": "http://127.0.0.1:8090/authorize",
"token_endpoint": "http://127.0.0.1:8090/token",
"userinfo_endpoint": "http://127.0.0.1:8090/userinfo"
}
`))
})
// Returns authorization code back to the user
mux.HandleFunc("/authorize", func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
redir := query.Get("redirect_uri") + "?state=" + query.Get("state") + "&code=mock_code"
http.Redirect(w, r, redir, http.StatusFound)
})
// Returns authorization code back to the user, but without the provided state
mux.HandleFunc("/auth-wrong-state", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("/authorize-wrong-state", func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
redir := query.Get("redirect_uri") + "?state=" + "a-random-state" + "&code=mock_code"
http.Redirect(w, r, redir, http.StatusFound)
......@@ -83,10 +114,7 @@ func CreateMockAPI() *http.ServeMux {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-XSS-Protection", "1; mode=block")
w.Header().Set("Content-Security-Policy", "default-src 'self'; frame-ancestors http://www.example.com")
w.Write([]byte(`{
"foo": "bar",
"bar": "foo"
}`))
w.Write([]byte(`{"foo": "bar","bar": "foo"}`))
})
}(), hostname, port))
return mux
......
......@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"os"
"strings"
"github.com/nicolaspernoud/vestibule/pkg/appserver"
......@@ -45,6 +46,9 @@ func CreateRootMux(hostname string, port int, appsFile string, davsFile string,
mainMux := http.NewServeMux()
// ALL USERS API ENDPOINTS
// Authentication endpoints
if i, e := os.LookupEnv("ISSUER"); e {
auth.InitIdPEnv(i)
}
m := auth.NewManager()
mainMux.HandleFunc("/OAuth2Login", m.HandleOAuth2Login)
mainMux.Handle("/OAuth2Callback", m.HandleOAuth2Callback())
......@@ -78,8 +82,11 @@ func CreateRootMux(hostname string, port int, appsFile string, davsFile string,
adminMux.HandleFunc("/users/", auth.ProcessUsers)
adminMux.HandleFunc("/sysinfo/", sysinfo.GetInfo)
mainMux.Handle("/api/admin/", http.StripPrefix("/api/admin", auth.ValidateAuthMiddleware(adminMux, []string{auth.AdminRole}, true)))
// Serve static files falling back to serving index.html
mainMux.Handle("/", middlewares.NoCache(http.FileServer(&common.FallBackWrapper{Assets: http.Dir(staticDir)})))
// Serve assets with cache enabled
static := http.FileServer(&common.FallBackWrapper{Assets: http.Dir(staticDir)})
mainMux.Handle("/assets/", static)
// Serve static files falling back to serving index.html with cache disabled
mainMux.Handle("/", middlewares.NoCache(static))
// Put it together into the main handler
mux := http.NewServeMux()
mux.Handle(hostname+"/", middlewares.WebSecurity(mainMux, "*."+hostname+":*", false))
......
......@@ -49,9 +49,9 @@ func TestAll(t *testing.T) {
os.Setenv("USERINFO_URL", oAuth2Server.URL+"/userinfo")
os.Setenv("LOGOUT_URL", oAuth2Server.URL+"/logout")
// Set up testers
os.Setenv("AUTH_URL", oAuth2Server.URL+"/auth-wrong-state") // Set the server to access failing OAuth2 endpoints
os.Setenv("AUTH_URL", oAuth2Server.URL+"/authorize-wrong-state") // Set the server to access failing OAuth2 endpoints
oauth2Tests := createOauth2Tests(t)
os.Setenv("AUTH_URL", oAuth2Server.URL+"/auth") // Set the server to access the correct OAuth2Endpoint
os.Setenv("AUTH_URL", oAuth2Server.URL+"/authorize") // Set the server to access the correct OAuth2Endpoint
unloggedTests := createUnLoggedTests(t)
userTests := createUserTests(t)
os.Setenv("USERINFO_URL", oAuth2Server.URL+"/admininfo")
......@@ -355,5 +355,5 @@ func createTester(t *testing.T) (*httptest.Server, tester.DoFn, tester.DoFn) {
// Create the cookie jar
jar, _ := cookiejar.New(nil)
// wrap the testing function
return ts, tester.CreateServerTester(t, port, os.Getenv("HOSTNAME"), jar), tester.CreateServerTester(t, port, os.Getenv("HOSTNAME"), nil)
return ts, tester.CreateServerTester(t, port, os.Getenv("HOSTNAME"), jar, true), tester.CreateServerTester(t, port, os.Getenv("HOSTNAME"), nil, true)
}
......@@ -13,6 +13,7 @@ import (
"time"
"github.com/nicolaspernoud/vestibule/pkg/common"
"github.com/nicolaspernoud/vestibule/pkg/log"
"github.com/nicolaspernoud/vestibule/pkg/tokens"
)
......@@ -24,6 +25,43 @@ const (
ContextData key = 0
)
type OpenIDConfiguration struct {
ResponseTypesSupported []string `json:"response_types_supported"`
RequestParameterSupported bool `json:"request_parameter_supported"`
RequestURIParameterSupported bool `json:"request_uri_parameter_supported"`
JwksURI string `json:"jwks_uri"`
SubjectTypesSupported []string `json:"subject_types_supported"`
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
RegistrationEndpoint string `json:"registration_endpoint"`
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
UserinfoEndpoint string `json:"userinfo_endpoint"`
}
// InitEnv will initialize environment variables from the issuer environment variable
func InitIdPEnv(idpUrl string) {
// Perform a request on the .well-known/oidc-configuration endpoint
r, err := http.Get(idpUrl + "/.well-known/openid-configuration")
if err != nil {
log.Logger.Fatalf("Could not read IdP configuration, exiting...")
}
defer r.Body.Close()
// Unmarshall the response into a struct
var o = OpenIDConfiguration{}
json.NewDecoder(r.Body).Decode(&o)
// Init the other env variables from this struct
if _, e := os.LookupEnv("AUTH_URL"); !e {
os.Setenv("AUTH_URL", o.AuthorizationEndpoint)
}
if _, e := os.LookupEnv("TOKEN_URL"); !e {
os.Setenv("TOKEN_URL", o.TokenEndpoint)
}
if _, e := os.LookupEnv("USERINFO_URL"); !e {
os.Setenv("USERINFO_URL", o.UserinfoEndpoint)
}
}
var (
// AdminRole represents the role reserved for admins
AdminRole = common.StringValueFromEnv("ADMIN_ROLE", "ADMINS")
......@@ -214,7 +252,7 @@ func GetTokenData(r *http.Request) (TokenData, error) {
// isWebdav works out if an user agent is a webdav user agent
func isWebdav(ua string) bool {
for _, a := range []string{"vfs", "Microsoft-WebDAV", "Konqueror", "LibreOffice", "Rei.Fs.WebDAV"} {
for _, a := range []string{"vfs", "Microsoft-WebDAV", "Konqueror", "LibreOffice", "Rei.Fs.WebDAV", "Documents"} {
if strings.Contains(ua, a) {
return true
}
......
......@@ -19,7 +19,7 @@ func TestEncryption(t *testing.T) {
url, _ := url.Parse(ts.URL)
port := url.Port()
// wrap the testing function
do := tester.CreateServerTester(t, port, "vestibule.io", nil)
do := tester.CreateServerTester(t, port, "vestibule.io", nil, true)
var noH map[string]string
// Try to access a crypted file on a encrypted unsecured dav (must pass)
do("PUT", "/test-ciphered.txt", noH, "content is encrypted !", 201, "")
......@@ -74,7 +74,7 @@ func TestSetModTime(t *testing.T) {
ts := httptest.NewServer(&davAug)
url, _ := url.Parse(ts.URL)
port := url.Port()
doPlain := tester.CreateServerTester(t, port, "vestibule.io", nil)
doPlain := tester.CreateServerTester(t, port, "vestibule.io", nil, true)
test(doPlain)
// Test with encrypted webdav
......@@ -82,6 +82,6 @@ func TestSetModTime(t *testing.T) {
tsEnc := httptest.NewServer(&davAugEnc)
urlEnc, _ := url.Parse(tsEnc.URL)
portEnc := urlEnc.Port()
doEnc := tester.CreateServerTester(t, portEnc, "vestibule.io", nil)
doEnc := tester.CreateServerTester(t, portEnc, "vestibule.io", nil, true)
test(doEnc)
}
......@@ -36,7 +36,7 @@ func DoRequestOnHandler(t *testing.T, router http.Handler, method string, route
}
// DoRequestOnServer does a request on listening server
func DoRequestOnServer(t *testing.T, hostname string, port string, jar *cookiejar.Jar, method string, testURL string, headers map[string]string, payload string, expectedStatus int, expectedBody string) string {
func DoRequestOnServer(t *testing.T, hostname string, port string, jar *cookiejar.Jar, method string, testURL string, headers map[string]string, payload string, expectedStatus int, expectedBody string, followRedirects bool) string {
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
......@@ -63,11 +63,14 @@ func DoRequestOnServer(t *testing.T, hostname string, port string, jar *cookieja
for i, v := range headers {
req.Header.Set(i, v)
}
var client *http.Client
client := &http.Client{}
if !followRedirects {
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}
}
if jar != nil {
client = &http.Client{Jar: jar}
} else {
client = &http.Client{}
client.Jar = jar
}
res, err := client.Do(req)
if err != nil {
......@@ -85,8 +88,8 @@ func DoRequestOnServer(t *testing.T, hostname string, port string, jar *cookieja
}
// CreateServerTester wraps DoRequestOnServer to factorize t, port and jar
func CreateServerTester(t *testing.T, hostname string, port string, jar *cookiejar.Jar) DoFn {
func CreateServerTester(t *testing.T, hostname string, port string, jar *cookiejar.Jar, followRedirects bool) DoFn {
return func(method string, url string, headers map[string]string, payload string, expectedStatus int, expectedBody string) string {
return DoRequestOnServer(t, port, hostname, jar, method, url, headers, payload, expectedStatus, expectedBody)
return DoRequestOnServer(t, port, hostname, jar, method, url, headers, payload, expectedStatus, expectedBody, followRedirects)
}
}
......@@ -37,6 +37,11 @@ func Init(keyfile string, debug bool) {
m = newManager(keyfile, debug)
}
// InitFromString inits the main token manager from a given string
func InitFromString(key string, debug bool) {
m = manager{key: []byte(key), debugMode: debug}
}
// newManager creates a manager
func newManager(keyfile string, debug bool) manager {
var keyConfig struct {
......
......@@ -32,7 +32,7 @@
<div class="navbar-brand">
<div class="navbar-item">
<a class="button is-primary is-rounded is-outlined" href="https://www.github.com/nicolaspernoud/Vestibule" target="_blank" rel="noopener noreferrer">
<span>4.5.18</span>
<span>4.5.19</span>
<span class="icon">
<svg
class="svg-inline--fa fa-github fa-w-16"
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment