client/web: invert auth logic for synology and qnap

Add separate server methods for synology and qnap, and enforce
authentication and authorization checks before calling into the actual
serving handlers. This allows us to remove all of the auth logic from
those handlers, since all requests will already be authenticated by that
point.

Also simplify the Synology token redirect handler by using fetch.

Remove the SynologyUser from nodeData, since it was never used in the
frontend anyway.

Updates tailscale/corp#13775

Signed-off-by: Will Norris <will@tailscale.com>
This commit is contained in:
Will Norris
2023-08-24 12:46:51 -07:00
committed by Will Norris
parent 05486f0f8e
commit 0c3d343ea3
4 changed files with 90 additions and 106 deletions

View File

@ -15,6 +15,37 @@ import (
"tailscale.com/util/groupmember"
)
// authorizeSynology authenticates the logged-in Synology user and verifies
// that they are authorized to use the web client. It returns true if the
// request was handled and no further processing is required.
func authorizeSynology(w http.ResponseWriter, r *http.Request) (handled bool) {
if synoTokenRedirect(w, r) {
return true
}
// authenticate the Synology user
cmd := exec.Command("/usr/syno/synoman/webman/modules/authenticate.cgi")
out, err := cmd.CombinedOutput()
if err != nil {
http.Error(w, fmt.Sprintf("auth: %v: %s", err, out), http.StatusUnauthorized)
return true
}
user := strings.TrimSpace(string(out))
// check if the user is in the administrators group
isAdmin, err := groupmember.IsMemberOfGroup("administrators", user)
if err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return true
}
if !isAdmin {
http.Error(w, "not a member of administrators group", http.StatusForbidden)
return true
}
return false
}
func synoTokenRedirect(w http.ResponseWriter, r *http.Request) bool {
if r.Header.Get("X-Syno-Token") != "" {
return false
@ -31,41 +62,15 @@ func synoTokenRedirect(w http.ResponseWriter, r *http.Request) bool {
return true
}
const synoTokenRedirectHTML = `<html><body>
const synoTokenRedirectHTML = `<html>
Redirecting with session token...
<script>
var serverURL = window.location.protocol + "//" + window.location.host;
var req = new XMLHttpRequest();
req.overrideMimeType("application/json");
req.open("GET", serverURL + "/webman/login.cgi", true);
req.onload = function() {
var jsonResponse = JSON.parse(req.responseText);
var token = jsonResponse["SynoToken"];
document.location.href = serverURL + "/webman/3rdparty/Tailscale/?SynoToken=" + token;
};
req.send(null);
fetch("/webman/login.cgi")
.then(r => r.json())
.then(data => {
u = new URL(window.location)
u.searchParams.set("SynoToken", data.SynoToken)
document.location = u
})
</script>
</body></html>
`
func synoAuthn() (string, error) {
cmd := exec.Command("/usr/syno/synoman/webman/modules/authenticate.cgi")
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("auth: %v: %s", err, out)
}
return strings.TrimSpace(string(out)), nil
}
// authorizeSynology checks whether the provided user has access to the web UI
// by consulting the membership of the "administrators" group.
func authorizeSynology(name string) error {
yes, err := groupmember.IsMemberOfGroup("administrators", name)
if err != nil {
return err
}
if !yes {
return fmt.Errorf("not a member of administrators group")
}
return nil
}