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

fix: enable redirect after login with OAuth2 login,

provide default for HOSTNAME, allow Let's Encrypt disabling
parent 3186a01b
Pipeline #13535 passed with stages
in 2 minutes and 35 seconds
......@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v1
uses: actions/setup-go@v2
with:
go-version: 1.x.x
- name: Code Checkout
......
......@@ -66,27 +66,28 @@ The mock ip geodatabase should be replaced with a real one from maxmind for real
Configuration is done through environment variables. The meaning of the different environment variables is detailed here :
| Environment variable | Usage | Default |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------ | --------------------------- |
| HOSTNAME | Vestibule main hostname : needed to know when to respond with the main GUI instead of an application on a webdav service | |
| APPS_FILE | Apps configuration file path | "./configs/davs.json" |
| DAVS_FILE | Davs configuration file path | "./configs/davs.json" |
| LETS_CACHE_DIR | Let's Encrypt cache directory | "./letsencrypt_cache" |
| LOG_FILE | Optional file to log to | defaults to no file logging |
| HTTPS_PORT | HTTPS port to serve on | 443 |
| HTTP_PORT | HTTP port to serve on, only used for Let's Encrypt HTTP Challenge | 80 |
| DEBUG_MODE | Debug mode, disable Let's Encrypt, enable CORS and more logging | false |
| ADMIN_ROLE | Admin role | ADMINS |
| REDIRECT_URL | Redirect url used by the idp to handle the callback | |
| CLIENT_ID | Client id to authenticate with the IdP for OAuth2 authentication | |
| CLIENT_SECRET | Client id to authenticate with the IdP for OAuth2 authentication | |
| AUTH_URL | IdP's authentication URL | |
| TOKEN_URL | IdP's token URL | |
| USERINFO_URL | IdP's userinfo URL | |
| 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 | |
| INMEMORY_TOKEN_LIFE_DAYS | Lifetime of authentication tokens for local users | 1 |
| Environment variable | Usage | Default |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| HOSTNAME | Vestibule main hostname : needed to know when to respond with the main GUI instead of an application on a webdav service | vestibule.127.0.0.1.nip.io |
| APPS_FILE | Apps configuration file path | "./configs/davs.json" |
| DAVS_FILE | Davs configuration file path | "./configs/davs.json" |
| LETS_CACHE_DIR | Let's Encrypt cache directory | "./letsencrypt_cache" |
| LOG_FILE | Optional file to log to | defaults to no file logging |
| HTTPS_PORT | HTTPS port to serve on | 443 |
| HTTP_PORT | HTTP port to serve on, only used for Let's Encrypt HTTP Challenge | 80 |
| DEBUG_MODE | Debug mode, disable Let's Encrypt, enable CORS and more logging | false |
| ADMIN_ROLE | Admin role | ADMINS |
| REDIRECT_URL | Redirect url used by the idp to handle the callback | |
| CLIENT_ID | Client id to authenticate with the IdP for OAuth2 authentication | |
| CLIENT_SECRET | Client id to authenticate with the IdP for OAuth2 authentication | |
| AUTH_URL | IdP's authentication URL | |
| TOKEN_URL | IdP's token URL | |
| USERINFO_URL | IdP's userinfo URL | |
| 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 | |
| INMEMORY_TOKEN_LIFE_DAYS | Lifetime of authentication tokens for local users | 1 |
| DISABLE_LETSENCRYPT | Disable Let's Encrypt certificates (in normal mode) and use development certificates (./dev_certificates/localhost.crt and .key) instead | false (true if HOSTNAME is not set) |
### OIDC/OAuth2 configuration
......
module github.com/nicolaspernoud/vestibule
go 1.15
go 1.16
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.4.3 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/oschwald/maxminddb-golang v1.8.0
github.com/secure-io/sio-go v0.3.1
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073
golang.org/x/text v0.3.5 // indirect
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc
golang.org/x/net v0.0.0-20210420210106-798c2154c571
golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
)
......@@ -76,6 +76,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
......@@ -86,6 +89,7 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
......@@ -139,6 +143,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc h1:+q90ECDSAQirdykUN6sPEiBXBsp8Csjcca8Oy7bgLTA=
golang.org/x/crypto v0.0.0-20210415154028-4f45737414dc/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
......@@ -197,6 +203,11 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 h1:OgUuv8lsRpBibGNbSizVwKWlysjaNzmC9gYMhPVfqFM=
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d h1:BgJvlyh+UqCUaPlscHJ+PN8GcpfrFdr7NHjd1JL0+Gs=
golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210420210106-798c2154c571 h1:Q6Bg8xzKzpFPU4Oi1sBnBTHBwlMsLeEXpu4hYBY8rAg=
golang.org/x/net v0.0.0-20210420210106-798c2154c571/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
......@@ -204,6 +215,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93 h1:alLDrZkL34Y2bnGHfvC1CYBRBXCXgx8AC2vY4MRtYX4=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78 h1:rPRtHfUb0UKZeZ6GH4K4Nt4YRbE9V1u+QZX5upZXqJQ=
golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
......@@ -242,6 +255,12 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 h1:8qxJSnu+7dRq6upnbntrmriWByIakBuct5OM/MdQC1M=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c h1:6L+uOeS3OQt/f4eFHXZcTxeZrGCuz+CLElgEBjbcTA4=
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988 h1:EjgCl+fVlIaPJSori0ikSz3uV0DOHKWOJFpv1sAAhBM=
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
......@@ -251,6 +270,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
......@@ -375,6 +396,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
......
......@@ -4,13 +4,13 @@ package mocks
import (
"fmt"
"net/http"
"os"
"github.com/nicolaspernoud/vestibule/pkg/common"
"github.com/nicolaspernoud/vestibule/pkg/middlewares"
)
var (
hostname = os.Getenv("HOSTNAME")
hostname = common.StringValueFromEnv("HOSTNAME", "vestibule.127.0.0.1.nip.io")
port int
)
......
......@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"net/http"
"os"
"strings"
"github.com/nicolaspernoud/vestibule/pkg/appserver"
......@@ -27,8 +26,7 @@ type RootMux struct {
}
// CreateRootMux creates a RootMux
func CreateRootMux(port int, appsFile string, davsFile string, staticDir string) RootMux {
hostname := os.Getenv("HOSTNAME")
func CreateRootMux(hostname string, port int, appsFile string, davsFile string, staticDir string) RootMux {
fullHostname := middlewares.GetFullHostname(hostname, port)
// Create the app handler
appServer, err := appserver.NewServer(appsFile, port, hostname, auth.ValidateAuthMiddleware)
......
......@@ -9,6 +9,7 @@ import (
"net/url"
"os"
"regexp"
"runtime"
"strings"
"sync"
"testing"
......@@ -288,7 +289,11 @@ func createAdminTests(t *testing.T) func(wg *sync.WaitGroup) {
// Try to delete a resource on an authorized dav (must pass)
do("DELETE", "admindav.vestibule.io/mydata/test2.txt", xsrfHeader, "", http.StatusNoContent, "")
// Try to get the system information (must pass)
do("GET", "/api/admin/sysinfo/", xsrfHeader, "", http.StatusOK, `{"uptime"`)
if runtime.GOOS == "windows" {
do("GET", "/api/admin/sysinfo/", xsrfHeader, "", http.StatusOK, `{"usedgb"`)
} else {
do("GET", "/api/admin/sysinfo/", xsrfHeader, "", http.StatusOK, `{"uptime"`)
}
}
// Try to login (must pass)
do("GET", "/OAuth2Login", noH, "", http.StatusOK, "<!DOCTYPE html>")
......@@ -349,7 +354,7 @@ func createDirectWebdavTests(t *testing.T) func(wg *sync.WaitGroup) {
func createTester(t *testing.T) (*httptest.Server, func(method string, url string, headers map[string]string, payload string, expectedStatus int, expectedBody string) string, func(method string, url string, headers map[string]string, payload string, expectedStatus int, expectedBody string) string) {
// Create the server
mux := CreateRootMux(1443, "./testdata/apps.json", "./testdata/davs.json", "../../web")
mux := CreateRootMux(os.Getenv("HOSTNAME"), 1443, "./testdata/apps.json", "./testdata/davs.json", "../../web")
ts := httptest.NewServer(mux.Mux)
url, _ := url.Parse(ts.URL)
port := url.Port()
......
......@@ -22,27 +22,24 @@ import (
)
var (
appsFile, davsFile, letsCacheDir, logFile string
httpsPort, httpPort int
debugMode bool
hostname string
appsFile = common.StringValueFromEnv("APPS_FILE", "./configs/apps.json") // Apps configuration file path
davsFile = common.StringValueFromEnv("DAVS_FILE", "./configs/davs.json") // Davs configuration file path
letsCacheDir = common.StringValueFromEnv("LETS_CACHE_DIR", "./letsencrypt_cache") // Let's Encrypt cache directory
logFile = common.StringValueFromEnv("LOG_FILE", "") // Optional file to log to
httpsPort = common.IntValueFromEnv("HTTPS_PORT", 443) // HTTPS port to serve on
httpPort = common.IntValueFromEnv("HTTP_PORT", 80) // HTTP port to serve on, only used for Let's Encrypt HTTP Challenge
debugMode = common.BoolValueFromEnv("DEBUG_MODE", false) // Debug mode, disable Let's Encrypt, enable CORS and more logging
disableLetsEncrypt = common.BoolValueFromEnv("DISABLE_LETSENCRYPT", false) // Disable Let's Encrypt certificates (in normal mode) and use development certificates (./dev_certificates/localhost.crt and .key) instead
)
func init() {
var err error
appsFile, err = common.StringValueFromEnv("APPS_FILE", "./configs/apps.json") // Apps configuration file path
common.CheckErrorFatal(err)
davsFile, err = common.StringValueFromEnv("DAVS_FILE", "./configs/davs.json") // Davs configuration file path
common.CheckErrorFatal(err)
letsCacheDir, err = common.StringValueFromEnv("LETS_CACHE_DIR", "./letsencrypt_cache") // Let's Encrypt cache directory
common.CheckErrorFatal(err)
logFile, err = common.StringValueFromEnv("LOG_FILE", "") // Optional file to log to
common.CheckErrorFatal(err)
httpsPort, err = common.IntValueFromEnv("HTTPS_PORT", 443) // HTTPS port to serve on
common.CheckErrorFatal(err)
httpPort, err = common.IntValueFromEnv("HTTP_PORT", 80) // HTTP port to serve on, only used for Let's Encrypt HTTP Challenge
common.CheckErrorFatal(err)
debugMode, err = common.BoolValueFromEnv("DEBUG_MODE", false) // Debug mode, disable Let's Encrypt, enable CORS and more logging
common.CheckErrorFatal(err)
defaultHostname := "vestibule.127.0.0.1.nip.io"
hostname = common.StringValueFromEnv("HOSTNAME", defaultHostname)
if hostname == defaultHostname {
disableLetsEncrypt = true
}
}
func main() {
......@@ -60,14 +57,14 @@ func main() {
}()
}
log.Logger.Println("--- Server is starting ---")
fullHostname := middlewares.GetFullHostname(os.Getenv("HOSTNAME"), httpsPort)
fullHostname := middlewares.GetFullHostname(hostname, httpsPort)
log.Logger.Println("Main hostname is ", fullHostname)
// Initializations
tokens.Init("./configs/tokenskey.json", debugMode)
// Create the server
rootMux := rootmux.CreateRootMux(httpsPort, appsFile, davsFile, "web")
rootMux := rootmux.CreateRootMux(hostname, httpsPort, appsFile, davsFile, "web")
// Serve locally with https on debug mode or with let's encrypt on production mode
if debugMode {
......@@ -83,6 +80,8 @@ func main() {
fmt.Println("Mock API server Listening on: http://localhost" + mockAPIPort)
log.Logger.Fatal(http.ListenAndServeTLS(":"+strconv.Itoa(httpsPort), "./dev_certificates/localhost.crt", "./dev_certificates/localhost.key", log.Middleware(rootMux.Mux)))
} else if disableLetsEncrypt {
log.Logger.Fatal(http.ListenAndServeTLS(":"+strconv.Itoa(httpsPort), "./dev_certificates/localhost.crt", "./dev_certificates/localhost.key", rootMux.Mux))
} else {
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
......
......@@ -26,15 +26,10 @@ const (
var (
// AdminRole represents the role reserved for admins
AdminRole string
AdminRole = common.StringValueFromEnv("ADMIN_ROLE", "ADMINS")
hostname = common.StringValueFromEnv("HOSTNAME", "vestibule.127.0.0.1.nip.io")
)
func init() {
var err error
AdminRole, err = common.StringValueFromEnv("ADMIN_ROLE", "ADMINS")
common.CheckErrorFatal(err)
}
// User represents a logged in user
type User struct {
ID string `json:"id,omitempty"`
......@@ -79,15 +74,20 @@ func ValidateAuthMiddleware(next http.Handler, allowedRoles []string, checkXSRF
return
}
// Default to redirect to authentication
redirectTo := os.Getenv("HOSTNAME")
redirectTo := hostname
_, port, perr := net.SplitHostPort(r.Host)
if perr == nil {
redirectTo += ":" + port
}
// Write the requested url in a cookie
if r.Host != redirectTo {
cookie := http.Cookie{Name: "redirectAfterLogin", Domain: hostname, Value: r.Host + r.URL.Path, MaxAge: 10, Secure: true, HttpOnly: false, SameSite: http.SameSiteLaxMode}
http.SetCookie(w, &cookie)
}
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusUnauthorized)
responseContent := fmt.Sprintf("error extracting token: %v<meta http-equiv=\"Refresh\" content=\"0; url=https://%v?redirectAfterLogin=%v#login\"/>", err.Error(), redirectTo, r.Host+r.URL.Path)
fmt.Fprintf(w, responseContent)
responseContent := fmt.Sprintf("error extracting token: %v<meta http-equiv=\"Refresh\" content=\"0; url=https://%v#login\"/>", err.Error(), redirectTo)
fmt.Fprint(w, responseContent)
return
}
......@@ -200,7 +200,7 @@ func GetShareToken(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fmt.Fprintf(w, token)
fmt.Fprint(w, token)
}
// GetTokenData gets an user from a request
......
package auth
import (
"context"
"encoding/json"
"fmt"
"net/http"
......@@ -37,7 +38,7 @@ func NewManager() Manager {
TokenURL: os.Getenv("TOKEN_URL"),
},
},
Hostname: os.Getenv("HOSTNAME"),
Hostname: common.StringValueFromEnv("HOSTNAME", "vestibule.127.0.0.1.nip.io"),
UserInfoURL: os.Getenv("USERINFO_URL"),
}
}
......@@ -81,7 +82,7 @@ func (m Manager) HandleOAuth2Callback() http.Handler {
http.SetCookie(w, &c)
// Perform code exchange
code := r.FormValue("code")
token, err := m.Config.Exchange(oauth2.NoContext, code)
token, err := m.Config.Exchange(context.Background(), code)
if err != nil {
fmt.Printf("Code exchange failed with '%s'\n", err)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
......@@ -103,6 +104,18 @@ func (m Manager) HandleOAuth2Callback() http.Handler {
http.Error(w, "no response body", http.StatusBadRequest)
return
}
////////////////////////////////////////////////
// UNCOMMENT THIS TO DEBUG USERINFO RESPONSE //
/*readBody, err := ioutil.ReadAll(response.Body)
if err != nil {
panic(err)
}
newBody := ioutil.NopCloser(bytes.NewBuffer(readBody))
response.Body = newBody
if string(readBody) != "" {
fmt.Printf("BODY : %q", readBody)
}*/
////////////////////////////////////////////////
err = json.NewDecoder(response.Body).Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
......
......@@ -15,8 +15,10 @@ import (
"github.com/nicolaspernoud/vestibule/pkg/log"
)
// Mutex used to lock file writing
var lock sync.Mutex
var (
disableLogFatal = false
lock sync.Mutex // Mutex used to lock file writing
)
// Save saves a representation of v to the file at path.
func Save(path string, v interface{}) error {
......@@ -112,35 +114,36 @@ func Contains(a []string, x string) bool {
}
// StringValueFromEnv set a value into an *interface from an environment variable or default
func StringValueFromEnv(ev string, def string) (string, error) {
func StringValueFromEnv(ev string, def string) string {
val := os.Getenv(ev)
if val == "" {
return def, nil
return def
}
return val, nil
return val
}
// IntValueFromEnv set a value into an *interface from an environment variable or default
func IntValueFromEnv(ev string, def int) (int, error) {
func IntValueFromEnv(ev string, def int) int {
val := os.Getenv(ev)
if val == "" {
return def, nil
return def
}
v, err := strconv.Atoi(val)
if err != nil && !disableLogFatal {
log.Logger.Fatalf("Error : could not get integer value from environment variable %v=%v\n", ev, val)
}
return strconv.Atoi(val)
return v
}
// BoolValueFromEnv set a value into an *interface from an environment variable or default
func BoolValueFromEnv(ev string, def bool) (bool, error) {
func BoolValueFromEnv(ev string, def bool) bool {
val := os.Getenv(ev)
if val == "" {
return def, nil
return def
}
return strconv.ParseBool(val)
}
// CheckErrorFatal logs a fatal error
func CheckErrorFatal(err error) {
if err != nil {
log.Logger.Fatalf("Error : %v\n", err)
v, err := strconv.ParseBool(val)
if err != nil && !disableLogFatal {
log.Logger.Fatalf("Error : could not get boolean value from environment variable %v=%v\n", ev, val)
}
return v
}
......@@ -5,10 +5,13 @@ import (
"testing"
)
func init() {
disableLogFatal = true
}
func TestStringValueFromEnv(t *testing.T) {
os.Setenv("MY_EV", "from_env")
var rv string
var err error
type args struct {
ev string
def string
......@@ -17,16 +20,13 @@ func TestStringValueFromEnv(t *testing.T) {
name string
args args
expected string
wantErr bool
}{
{"string_value_from_env", args{"MY_EV", "test"}, "from_env", false},
{"string_value_from_def", args{"MY_DEF", "test"}, "test", false},
{"string_value_from_env", args{"MY_EV", "test"}, "from_env"},
{"string_value_from_def", args{"MY_DEF", "test"}, "test"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if rv, err = StringValueFromEnv(tt.args.ev, tt.args.def); (err != nil) != tt.wantErr {
t.Errorf("StringValueFromEnv() error = %v, wantErr %v", err, tt.wantErr)
}
rv = StringValueFromEnv(tt.args.ev, tt.args.def)
if rv != tt.expected {
t.Errorf("StringValueFromEnv() error ; got %v, expected %v", rv, tt.expected)
}
......@@ -37,7 +37,6 @@ func TestStringValueFromEnv(t *testing.T) {
func TestIntValueFromEnv(t *testing.T) {
os.Setenv("MY_EV", "from_env")
var rv int
var err error
type args struct {
ev string
def int
......@@ -46,16 +45,13 @@ func TestIntValueFromEnv(t *testing.T) {
name string
args args
expected int
wantErr bool
}{
{"int_value_from_def", args{"MY_DEF", 1}, 1, false},
{"string_on_int_from_env", args{"MY_EV", 1}, 0, true},
{"int_value_from_def", args{"MY_DEF", 1}, 1},
{"string_on_int_from_env", args{"MY_EV", 1}, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if rv, err = IntValueFromEnv(tt.args.ev, tt.args.def); (err != nil) != tt.wantErr {
t.Errorf("IntValueFromEnv() error = %v, wantErr %v", err, tt.wantErr)
}
rv = IntValueFromEnv(tt.args.ev, tt.args.def)
if rv != tt.expected {
t.Errorf("IntValueFromEnv() error ; got %v, expected %v", rv, tt.expected)
}
......@@ -66,7 +62,6 @@ func TestIntValueFromEnv(t *testing.T) {
func TestBoolValueFromEnv(t *testing.T) {
os.Setenv("MY_EV", "from_env")
var rv bool
var err error
type args struct {
ev string
def bool
......@@ -75,17 +70,14 @@ func TestBoolValueFromEnv(t *testing.T) {
name string
args args
expected bool
wantErr bool
}{
{"bool_value_from_def", args{"MY_DEF", true}, true, false},
{"string_on_bool_from_def", args{"MY_EV", true}, false, true},
{"bool_value_from_def", args{"MY_DEF", true}, true},
{"string_on_bool_from_def", args{"MY_EV", true}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if rv, err = BoolValueFromEnv(tt.args.ev, tt.args.def); (err != nil) != tt.wantErr {
t.Errorf("BoolValueFromEnv() error = %v, wantErr %v", err, tt.wantErr)
}
rv = BoolValueFromEnv(tt.args.ev, tt.args.def)
if rv != tt.expected {
t.Errorf("BoolValueFromEnv() error ; got %v, expected %v", rv, tt.expected)
}
......
......@@ -19,7 +19,7 @@ func HandleOpen(fullHostname string) func(w http.ResponseWriter, req *http.Reque
http.Error(w, "could not open onlyoffice template: "+err.Error(), http.StatusInternalServerError)
return
}
title, _ := common.StringValueFromEnv("ONLYOFFICE_TITLE", "VestibuleOffice")
title := common.StringValueFromEnv("ONLYOFFICE_TITLE", "VestibuleOffice")
p := struct {
Title string
OnlyOfficeServer string
......@@ -80,6 +80,10 @@ func HandleSaveCallback(w http.ResponseWriter, req *http.Request) {
// PUT the content on the ressource gotten from the query
ressource := req.URL.Query().Get("file") + "?token=" + url.QueryEscape(req.URL.Query().Get("token"))
req, err := http.NewRequest("PUT", ressource, resp.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
client := &http.Client{}
_, err = client.Do(req)
if err != nil {
......
......@@ -2,6 +2,7 @@ package sysinfo
import (
"net/http"
"runtime"
"testing"
"github.com/nicolaspernoud/vestibule/pkg/tester"
......@@ -9,5 +10,9 @@ import (
func TestGetInfo(t *testing.T) {
handler := http.HandlerFunc(GetInfo)
tester.DoRequestOnHandler(t, handler, "GET", "/", nil, "", http.StatusOK, `{"uptime"`)
if runtime.GOOS == "windows" {
tester.DoRequestOnHandler(t, handler, "GET", "/", nil, "", http.StatusOK, `{"usedgb"`)
} else {
tester.DoRequestOnHandler(t, handler, "GET", "/", nil, "", http.StatusOK, `{"uptime"`)
}
}
This diff is collapsed.
This diff is collapsed.
......@@ -95,12 +95,6 @@ async function doLogin() {
throw new Error(`Login error (status ${response.status})`);
}
await Auth.GetUser();
// Redirect to original subdomain if login was displayed after an authentication error on the original subdomain
const urlParams = new URLSearchParams(window.location.search);
const redirectAfterLogin = urlParams.get("redirectAfterLogin");
if (redirectAfterLogin != "" && redirectAfterLogin != null) {
window.location.replace("https://" + redirectAfterLogin);
}