Browse Source

added token info page and revokation support

deiu 5 years ago
parent
commit
ecd32b8eaf
7 changed files with 136 additions and 57 deletions
  1. 1 1
      acl_test.go
  2. 1 1
      autoneg_test.go
  3. 7 7
      pathinfo_test.go
  4. 12 2
      server.go
  5. 59 11
      system.go
  6. 43 35
      system_test.go
  7. 13 0
      templates.go

+ 1 - 1
acl_test.go

@@ -1022,7 +1022,7 @@ func TestACLCleanUp(t *testing.T) {
 func TestACLwalkPath(t *testing.T) {
 	config.Debug = false
 	s := NewServer(config)
-	req := &httpRequest{nil, s, "", "", ""}
+	req := &httpRequest{nil, s, "", "", "", false}
 
 	path := "http://example.org/foo/bar/baz"
 	p, _ := req.pathInfo(path)

+ 1 - 1
autoneg_test.go

@@ -20,7 +20,7 @@ func mockAccept(accept string) (al AcceptList, err error) {
 	req := &http.Request{}
 	req.Header = make(http.Header)
 	req.Header["Accept"] = []string{accept}
-	myreq := &httpRequest{req, nil, "", "", ""}
+	myreq := &httpRequest{req, nil, "", "", "", false}
 	al, err = myreq.Accept()
 	return
 }

+ 7 - 7
pathinfo_test.go

@@ -8,7 +8,7 @@ import (
 
 func TestPathInfoWithoutTrailingSlash(t *testing.T) {
 	sroot := serverDefaultRoot()
-	req := &httpRequest{nil, handler, "", "", ""}
+	req := &httpRequest{nil, handler, "", "", "", false}
 	p, err := req.pathInfo(testServer.URL)
 	assert.Nil(t, err)
 	assert.Equal(t, testServer.URL+"/", p.URI)
@@ -25,7 +25,7 @@ func TestPathInfoWithoutTrailingSlash(t *testing.T) {
 
 func TestPathInfoWithTrailingSlash(t *testing.T) {
 	sroot := serverDefaultRoot()
-	req := &httpRequest{nil, handler, "", "", ""}
+	req := &httpRequest{nil, handler, "", "", "", false}
 
 	p, err := req.pathInfo(testServer.URL + "/")
 	assert.Nil(t, err)
@@ -44,7 +44,7 @@ func TestPathInfoWithTrailingSlash(t *testing.T) {
 func TestPathInfoWithPath(t *testing.T) {
 	path := testServer.URL + "/_test/"
 	sroot := serverDefaultRoot()
-	req := &httpRequest{nil, handler, "", "", ""}
+	req := &httpRequest{nil, handler, "", "", "", false}
 
 	p, err := req.pathInfo(path)
 	assert.Nil(t, err)
@@ -63,7 +63,7 @@ func TestPathInfoWithPath(t *testing.T) {
 func TestPathInfoWithPathAndChildDir(t *testing.T) {
 	path := testServer.URL + "/_test/"
 	sroot := serverDefaultRoot()
-	req := &httpRequest{nil, handler, "", "", ""}
+	req := &httpRequest{nil, handler, "", "", "", false}
 
 	p, err := req.pathInfo(path + "dir/")
 	assert.Nil(t, err)
@@ -83,7 +83,7 @@ func TestPathInfoWithPathAndChildDir(t *testing.T) {
 func TestPathInfoWithPathAndChildFile(t *testing.T) {
 	path := testServer.URL + "/_test/"
 	sroot := serverDefaultRoot()
-	req := &httpRequest{nil, handler, "", "", ""}
+	req := &httpRequest{nil, handler, "", "", "", false}
 
 	p, err := req.pathInfo(path + "abc")
 	assert.Nil(t, err)
@@ -103,7 +103,7 @@ func TestPathInfoWithPathAndChildFile(t *testing.T) {
 func TestPathInfoWithPathAndACLSuffix(t *testing.T) {
 	path := testServer.URL + "/_test/"
 	sroot := serverDefaultRoot()
-	req := &httpRequest{nil, handler, "", "", ""}
+	req := &httpRequest{nil, handler, "", "", "", false}
 
 	p, err := req.pathInfo(path + config.ACLSuffix)
 	assert.Nil(t, err)
@@ -122,7 +122,7 @@ func TestPathInfoWithPathAndACLSuffix(t *testing.T) {
 func TestPathInfoWithPathAndMetaSuffix(t *testing.T) {
 	path := testServer.URL + "/_test/"
 	sroot := serverDefaultRoot()
-	req := &httpRequest{nil, handler, "", "", ""}
+	req := &httpRequest{nil, handler, "", "", "", false}
 
 	p, err := req.pathInfo(path + config.MetaSuffix)
 	assert.Nil(t, err)

+ 12 - 2
server.go

@@ -80,6 +80,7 @@ type httpRequest struct {
 	AcceptType  string
 	ContentType string
 	User        string
+	IsOwner     bool
 }
 
 func (req httpRequest) BaseURI() string {
@@ -219,7 +220,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 	defer func() {
 		req.Body.Close()
 	}()
-	r := s.handle(w, &httpRequest{req, s, "", "", ""})
+	r := s.handle(w, &httpRequest{req, s, "", "", "", false})
 	for key := range r.headers {
 		w.Header().Set(key, r.headers.Get(key))
 	}
@@ -317,6 +318,16 @@ func (s *Server) handle(w http.ResponseWriter, req *httpRequest) (r *response) {
 	w.Header().Set("User", user)
 	acl := NewWAC(req, s, w, user, rKey)
 
+	// check if is owner
+	req.IsOwner = false
+	resource, _ := req.pathInfo(req.BaseURI())
+	if len(user) > 0 {
+		aclStatus, err := acl.AllowWrite(resource.Base)
+		if aclStatus == 200 && err == nil {
+			req.IsOwner = true
+		}
+	}
+
 	// Intercept API requests
 	if strings.Contains(req.Request.URL.Path, "/"+SystemPrefix) && req.Method != "OPTIONS" {
 		resp := HandleSystem(w, req, s)
@@ -344,7 +355,6 @@ func (s *Server) handle(w http.ResponseWriter, req *httpRequest) (r *response) {
 		return TwinqlQuery(w, req, s)
 	}
 
-	resource, _ := req.pathInfo(req.BaseURI())
 	s.debug.Println(req.RemoteAddr + " requested resource URI: " + req.URL.String())
 	s.debug.Println(req.RemoteAddr + " requested resource Path: " + resource.File)
 

+ 59 - 11
system.go

@@ -672,21 +672,42 @@ func accountStatus(w http.ResponseWriter, req *httpRequest, s *Server) SystemRet
 }
 
 func accountInfo(w http.ResponseWriter, req *httpRequest, s *Server) SystemReturn {
-	resource, _ := req.pathInfo(req.BaseURI())
-	totalSize, err := DiskUsage(resource.Root)
-	if err != nil {
-		return SystemReturn{Status: 500, Body: err.Error()}
+	if len(req.User) == 0 {
+		return SystemReturn{Status: 401, Body: "Please log in to view your account information"}
+	}
+	if !req.IsOwner {
+		return SystemReturn{Status: 403, Body: "You are not allowed to view this page"}
 	}
 
-	data := accountInformation{
-		DiskUsed:  fmt.Sprintf("%d", totalSize),
-		DiskLimit: fmt.Sprintf("%d", s.Config.DiskLimit),
+	tokensHtml := "<div>"
+
+	if len(req.FormValue("revokeAuthz")) > 0 {
+		delStatus := "<p style=\"color: green;\">Successfully revoked token!</p>"
+		err := s.deletePersistedToken("Authorization", req.Host, req.FormValue("revokeAuthz"))
+		if err != nil {
+			delStatus = "<p>Could not revoke token. Error: " + err.Error() + "</p>"
+		}
+		tokensHtml += delStatus
 	}
-	jsonData, err := json.Marshal(data)
-	if err != nil {
-		s.debug.Println("Marshal error: " + err.Error())
+
+	tokens, err := s.getTokensByType("Authorization", req.Host)
+	tokensHtml += "<h2>Authorization tokens for applications</h2>\n"
+	tokensHtml += "<div>"
+	if err == nil {
+		for token, values := range tokens {
+			tokensHtml += "<p>Token: " + string(token) + "<br>\n"
+			tokensHtml += "Application: <strong>" + values["origin"] + "</strong>"
+			tokensHtml += " <a href=\"" + req.BaseURI() + "?revokeAuthz=" + encodeQuery(token) + "\">Revoke</a></p>\n"
+		}
+		tokensHtml += "</ul>\n"
+		if len(tokens) == 0 {
+			tokensHtml += "No authorization tokens found."
+		}
 	}
-	return SystemReturn{Status: 200, Body: string(jsonData)}
+
+	tokensHtml += "</div>"
+
+	return SystemReturn{Status: 200, Body: TokensTemplate(req.User, tokensHtml)}
 }
 
 // DiskUsage returns the total size occupied by dir and contents
@@ -780,3 +801,30 @@ func (s *Server) deletePersistedToken(tokenType, host, token string) error {
 	})
 	return err
 }
+
+func (s *Server) getTokensByType(tokenType, host string) (map[string]map[string]string, error) {
+	tokens := make(map[string]map[string]string)
+	err := s.BoltDB.View(func(tx *bolt.Tx) error {
+		// Assume bucket exists and has keys
+		b := tx.Bucket([]byte(host))
+		if b == nil {
+			return errors.New("No bucket for host " + host)
+		}
+		ba := b.Bucket([]byte(tokenType))
+		if ba == nil {
+			return errors.New("No bucket for type " + tokenType)
+		}
+
+		c := ba.Cursor()
+
+		for k, _ := c.First(); k != nil; k, _ = c.Next() {
+			key := string(k)
+			token, err := s.getPersistedToken(tokenType, host, key)
+			if err == nil {
+				tokens[key] = token
+			}
+		}
+		return nil
+	})
+	return tokens, err
+}

+ 43 - 35
system_test.go

@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"crypto/tls"
 	"encoding/json"
-	"fmt"
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
@@ -294,6 +293,27 @@ func TestNewAccountWithoutSPKAC(t *testing.T) {
 	assert.NoError(t, err)
 }
 
+// func TestAccountInfo(t *testing.T) {
+// 	err := handler2.StartBolt()
+// 	assert.NoError(t, err)
+// 	defer handler2.BoltDB.Close()
+
+// 	testServer1 := httptest.NewUnstartedServer(handler2)
+// 	testServer1.TLS = new(tls.Config)
+// 	testServer1.TLS.ClientAuth = tls.RequestClientCert
+// 	testServer1.TLS.NextProtos = []string{"http/1.1"}
+// 	testServer1.StartTLS()
+
+// 	request, err := http.NewRequest("GET", testServer1.URL+"/"+SystemPrefix+"/info", nil)
+// 	assert.NoError(t, err)
+// 	response, err := httpClient.Do(request)
+// 	assert.NoError(t, err)
+// 	body, _ := ioutil.ReadAll(response.Body)
+// 	response.Body.Close()
+// 	assert.Equal(t, 200, response.StatusCode)
+// 	assert.NotEmpty(t, body)
+// }
+
 func TestAccountRecoveryForm(t *testing.T) {
 	request, err := http.NewRequest("POST", testServer.URL+"/"+SystemPrefix+"/recovery", nil)
 	assert.NoError(t, err)
@@ -319,6 +339,10 @@ func TestAccountRecovery(t *testing.T) {
 	assert.Equal(t, 403, response.StatusCode)
 	assert.Empty(t, response.Cookies())
 
+	err = handler1.StartBolt()
+	assert.NoError(t, err)
+	defer handler1.BoltDB.Close()
+
 	testServer1 := httptest.NewUnstartedServer(handler1)
 	testServer1.TLS = new(tls.Config)
 	testServer1.TLS.ClientAuth = tls.RequestClientCert
@@ -379,6 +403,24 @@ func TestAccountRecovery(t *testing.T) {
 	assert.NotEmpty(t, response.Cookies())
 	assert.Equal(t, "Session", response.Cookies()[0].Name)
 
+	cookie := response.Header.Get("Set-Cookie")
+	assert.NotNil(t, cookie)
+
+	// Server info without credentials
+	request, err = http.NewRequest("GET", testServer1.URL+"/"+SystemPrefix+"/info", nil)
+	assert.NoError(t, err)
+	response, err = httpClient.Do(request)
+	assert.NoError(t, err)
+	assert.Equal(t, 401, response.StatusCode)
+
+	// Server info without credentials
+	request, err = http.NewRequest("GET", testServer1.URL+"/"+SystemPrefix+"/info", nil)
+	assert.NoError(t, err)
+	request.Header.Add("Cookie", cookie)
+	response, err = httpClient.Do(request)
+	assert.NoError(t, err)
+	assert.Equal(t, 200, response.StatusCode)
+
 	// delete user
 	err = os.RemoveAll("_test/")
 	assert.NoError(t, err)
@@ -518,37 +560,3 @@ func TestAccountStatusWithVhosts(t *testing.T) {
 	assert.Equal(t, `{"method":"status","status":"success","formURL":"`+testServer1.URL+`/`+SystemPrefix+`/new","loginURL":"https://user.`+strings.TrimLeft(testServer1.URL, "https://")+`/`+SystemPrefix+`/login","logoutURL":"https://user.`+strings.TrimLeft(testServer1.URL, "https://")+`/`+SystemPrefix+`/logout","response":{"accountURL":"https://user.`+strings.TrimLeft(testServer1.URL, "https://")+`/","available":true}}`, string(body))
 	assert.Equal(t, 200, response.StatusCode)
 }
-
-func TestAccountInfo(t *testing.T) {
-	err := os.MkdirAll("_test/user", 0755)
-	assert.NoError(t, err)
-
-	sizeLocal, err := DiskUsage("_test/")
-	assert.NoError(t, err)
-
-	testServer1 := httptest.NewUnstartedServer(handler2)
-	testServer1.TLS = new(tls.Config)
-	testServer1.TLS.ClientAuth = tls.RequestClientCert
-	testServer1.TLS.NextProtos = []string{"http/1.1"}
-	testServer1.StartTLS()
-
-	request, err := http.NewRequest("GET", testServer1.URL+"/"+SystemPrefix+"/info", nil)
-	assert.NoError(t, err)
-	response, err := httpClient.Do(request)
-	assert.NoError(t, err)
-	body, _ := ioutil.ReadAll(response.Body)
-	response.Body.Close()
-
-	err = os.RemoveAll("_test/")
-	assert.NoError(t, err)
-
-	assert.Equal(t, 200, response.StatusCode)
-	assert.NotEmpty(t, body)
-
-	dataLocal := accountInformation{
-		DiskUsed:  fmt.Sprintf("%d", sizeLocal),
-		DiskLimit: fmt.Sprintf("%d", config2.DiskLimit),
-	}
-	jsonData, err := json.Marshal(dataLocal)
-	assert.Equal(t, body, jsonData)
-}

+ 13 - 0
templates.go

@@ -207,3 +207,16 @@ func LogoutTemplate(webid string) string {
 </html>`
 	return template
 }
+
+func TokensTemplate(webid, tokens string) string {
+	template := `<!DOCTYPE html>
+<html id="docHTML">
+<head>
+</head>
+<body>
+    <h1>You are logged in as ` + webid + `.</h1>
+    ` + tokens + `
+</body>
+</html>`
+	return template
+}