Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve kernel API authentication #9702

Merged
merged 5 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions app/src/protyle/util/compatibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,13 @@ export const getLocalStorage = (cb: () => void) => {
});
cb();

// 数据兼容,移除历史数据,3.8.4 移除
fetchPost("/api/storage/removeLocalStorageVals", {
app: Constants.SIYUAN_APPID,
keys: ["leftColumn", "local-searchkey", "local-searchedata", "local-searchekeys", "local-searchetabdata", "rightColumn", "topBar"]
});
if (!window.siyuan.config.readonly) {
// 数据兼容,移除历史数据,3.8.4 移除
fetchPost("/api/storage/removeLocalStorageVals", {
app: Constants.SIYUAN_APPID,
keys: ["leftColumn", "local-searchkey", "local-searchedata", "local-searchekeys", "local-searchetabdata", "rightColumn", "topBar"]
});
}
});
};

Expand Down
65 changes: 65 additions & 0 deletions kernel/api/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,71 @@ import (
"github.com/siyuan-note/siyuan/kernel/util"
)

func echo(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)

password, passwordSet := c.Request.URL.User.Password()

var rawData any
if data, err := c.GetRawData(); nil == err {
rawData = base64.StdEncoding.EncodeToString(data)
} else {
rawData = nil
}

ret.Data = map[string]interface{}{
"Context": map[string]interface{}{
"Params": c.Params,
"HandlerNames": c.HandlerNames(),
"FullPath": c.FullPath(),
"ClientIP": c.ClientIP(),
"RemoteIP": c.RemoteIP(),
"ContentType": c.ContentType(),
"IsWebsocket": c.IsWebsocket(),
"RawData": rawData,
},
"Request": map[string]interface{}{
"Method": c.Request.Method,
"URL": c.Request.URL,
"Proto": c.Request.Proto,
"ProtoMajor": c.Request.ProtoMajor,
"ProtoMinor": c.Request.ProtoMinor,
"Header": c.Request.Header,
"ContentLength": c.Request.ContentLength,
"TransferEncoding": c.Request.TransferEncoding,
"Close": c.Request.Close,
"Host": c.Request.Host,
"Form": c.Request.Form,
"PostForm": c.Request.PostForm,
"MultipartForm": c.Request.MultipartForm,
"Trailer": c.Request.Trailer,
"RemoteAddr": c.Request.RemoteAddr,
"TLS": c.Request.TLS,
"UserAgent": c.Request.UserAgent(),
"Cookies": c.Request.Cookies(),
"Referer": c.Request.Referer(),
},
"URL": map[string]interface{}{
"EscapedPath": c.Request.URL.EscapedPath(),
"EscapedFragment": c.Request.URL.EscapedFragment(),
"String": c.Request.URL.String(),
"Redacted": c.Request.URL.Redacted(),
"IsAbs": c.Request.URL.IsAbs(),
"Query": c.Request.URL.Query(),
"RequestURI": c.Request.URL.RequestURI(),
"Hostname": c.Request.URL.Hostname(),
"Port": c.Request.URL.Port(),
},
"User": map[string]interface{}{
"Username": c.Request.URL.User.Username(),
"Password": password,
"PasswordSet": passwordSet,
"String": c.Request.URL.User.String(),
},
}
}

func forwardProxy(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
Expand Down
56 changes: 29 additions & 27 deletions kernel/api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/system/loginAuth", model.LoginAuth)
ginServer.Handle("POST", "/api/system/logoutAuth", model.LogoutAuth)
ginServer.Handle("GET", "/api/system/getCaptcha", model.GetCaptcha)
ginServer.Handle("POST", "/api/system/setUILayout", setUILayout) // 这里不加鉴权 After modifying the access authentication code on the browser side, the other side does not refresh https://github.com/siyuan-note/siyuan/issues/8028

ginServer.Handle("GET", "/snippets/*filepath", serveSnippets)

// 需要鉴权

Expand All @@ -56,15 +59,14 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/system/setAppearanceMode", model.CheckAuth, setAppearanceMode)
ginServer.Handle("POST", "/api/system/getSysFonts", model.CheckAuth, getSysFonts)
ginServer.Handle("POST", "/api/system/exit", model.CheckAuth, exit)
ginServer.Handle("POST", "/api/system/setUILayout", setUILayout) // 这里不加鉴权 After modifying the access authentication code on the browser side, the other side does not refresh https://github.com/siyuan-note/siyuan/issues/8028
ginServer.Handle("POST", "/api/system/getConf", model.CheckAuth, getConf)
ginServer.Handle("POST", "/api/system/checkUpdate", model.CheckAuth, checkUpdate)
ginServer.Handle("POST", "/api/system/exportLog", model.CheckAuth, exportLog)
ginServer.Handle("POST", "/api/system/getChangelog", model.CheckAuth, getChangelog)

ginServer.Handle("POST", "/api/storage/setLocalStorage", model.CheckAuth, setLocalStorage)
ginServer.Handle("POST", "/api/storage/setLocalStorage", model.CheckAuth, model.CheckReadonly, setLocalStorage)
ginServer.Handle("POST", "/api/storage/getLocalStorage", model.CheckAuth, getLocalStorage)
ginServer.Handle("POST", "/api/storage/setLocalStorageVal", model.CheckAuth, setLocalStorageVal)
ginServer.Handle("POST", "/api/storage/setLocalStorageVal", model.CheckAuth, model.CheckReadonly, setLocalStorageVal)
ginServer.Handle("POST", "/api/storage/removeLocalStorageVals", model.CheckAuth, model.CheckReadonly, removeLocalStorageVals)
ginServer.Handle("POST", "/api/storage/setCriterion", model.CheckAuth, model.CheckReadonly, setCriterion)
ginServer.Handle("POST", "/api/storage/getCriteria", model.CheckAuth, getCriteria)
Expand All @@ -78,12 +80,12 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/account/startFreeTrial", model.CheckAuth, model.CheckReadonly, startFreeTrial)

ginServer.Handle("POST", "/api/notebook/lsNotebooks", model.CheckAuth, lsNotebooks)
ginServer.Handle("POST", "/api/notebook/openNotebook", model.CheckAuth, openNotebook)
ginServer.Handle("POST", "/api/notebook/openNotebook", model.CheckAuth, model.CheckReadonly, openNotebook)
ginServer.Handle("POST", "/api/notebook/closeNotebook", model.CheckAuth, model.CheckReadonly, closeNotebook)
ginServer.Handle("POST", "/api/notebook/getNotebookConf", model.CheckAuth, getNotebookConf)
ginServer.Handle("POST", "/api/notebook/setNotebookConf", model.CheckAuth, model.CheckReadonly, setNotebookConf)
ginServer.Handle("POST", "/api/notebook/createNotebook", model.CheckAuth, model.CheckReadonly, createNotebook)
ginServer.Handle("POST", "/api/notebook/removeNotebook", model.CheckAuth, removeNotebook)
ginServer.Handle("POST", "/api/notebook/removeNotebook", model.CheckAuth, model.CheckReadonly, removeNotebook)
ginServer.Handle("POST", "/api/notebook/renameNotebook", model.CheckAuth, model.CheckReadonly, renameNotebook)
ginServer.Handle("POST", "/api/notebook/changeSortNotebook", model.CheckAuth, model.CheckReadonly, changeSortNotebook)
ginServer.Handle("POST", "/api/notebook/setNotebookIcon", model.CheckAuth, model.CheckReadonly, setNotebookIcon)
Expand Down Expand Up @@ -122,8 +124,8 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/history/rollbackDocHistory", model.CheckAuth, model.CheckReadonly, rollbackDocHistory)
ginServer.Handle("POST", "/api/history/clearWorkspaceHistory", model.CheckAuth, model.CheckReadonly, clearWorkspaceHistory)
ginServer.Handle("POST", "/api/history/reindexHistory", model.CheckAuth, model.CheckReadonly, reindexHistory)
ginServer.Handle("POST", "/api/history/searchHistory", model.CheckAuth, model.CheckReadonly, searchHistory)
ginServer.Handle("POST", "/api/history/getHistoryItems", model.CheckAuth, model.CheckReadonly, getHistoryItems)
ginServer.Handle("POST", "/api/history/searchHistory", model.CheckAuth, searchHistory)
ginServer.Handle("POST", "/api/history/getHistoryItems", model.CheckAuth, getHistoryItems)

ginServer.Handle("POST", "/api/outline/getDocOutline", model.CheckAuth, getDocOutline)
ginServer.Handle("POST", "/api/bookmark/getBookmark", model.CheckAuth, getBookmark)
Expand All @@ -141,14 +143,14 @@ func ServeAPI(ginServer *gin.Engine) {

ginServer.Handle("POST", "/api/search/searchTag", model.CheckAuth, searchTag)
ginServer.Handle("POST", "/api/search/searchTemplate", model.CheckAuth, searchTemplate)
ginServer.Handle("POST", "/api/search/removeTemplate", model.CheckAuth, removeTemplate)
ginServer.Handle("POST", "/api/search/removeTemplate", model.CheckAuth, model.CheckReadonly, removeTemplate)
ginServer.Handle("POST", "/api/search/searchWidget", model.CheckAuth, searchWidget)
ginServer.Handle("POST", "/api/search/searchRefBlock", model.CheckAuth, searchRefBlock)
ginServer.Handle("POST", "/api/search/searchEmbedBlock", model.CheckAuth, searchEmbedBlock)
ginServer.Handle("POST", "/api/search/getEmbedBlock", model.CheckAuth, getEmbedBlock)
ginServer.Handle("POST", "/api/search/fullTextSearchBlock", model.CheckAuth, fullTextSearchBlock)
ginServer.Handle("POST", "/api/search/searchAsset", model.CheckAuth, searchAsset)
ginServer.Handle("POST", "/api/search/findReplace", model.CheckAuth, model.CheckReadonly, findReplace)
ginServer.Handle("POST", "/api/search/findReplace", model.CheckAuth, findReplace)
ginServer.Handle("POST", "/api/search/fullTextSearchAssetContent", model.CheckAuth, fullTextSearchAssetContent)
ginServer.Handle("POST", "/api/search/getAssetContent", model.CheckAuth, getAssetContent)

Expand Down Expand Up @@ -183,14 +185,14 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/block/getHeadingChildrenDOM", model.CheckAuth, getHeadingChildrenDOM)
ginServer.Handle("POST", "/api/block/swapBlockRef", model.CheckAuth, model.CheckReadonly, swapBlockRef)
ginServer.Handle("POST", "/api/block/transferBlockRef", model.CheckAuth, model.CheckReadonly, transferBlockRef)
ginServer.Handle("POST", "/api/block/getParentNextChildID", model.CheckAuth, model.CheckReadonly, getParentNextChildID)
ginServer.Handle("POST", "/api/block/getParentNextChildID", model.CheckAuth, getParentNextChildID)

ginServer.Handle("POST", "/api/file/getFile", model.CheckAuth, getFile)
ginServer.Handle("POST", "/api/file/putFile", model.CheckAuth, model.CheckReadonly, putFile)
ginServer.Handle("POST", "/api/file/copyFile", model.CheckAuth, model.CheckReadonly, copyFile)
ginServer.Handle("POST", "/api/file/removeFile", model.CheckAuth, model.CheckReadonly, removeFile)
ginServer.Handle("POST", "/api/file/renameFile", model.CheckAuth, model.CheckReadonly, renameFile)
ginServer.Handle("POST", "/api/file/readDir", model.CheckAuth, model.CheckReadonly, readDir)
ginServer.Handle("POST", "/api/file/readDir", model.CheckAuth, readDir)

ginServer.Handle("POST", "/api/ref/refreshBacklink", model.CheckAuth, refreshBacklink)
ginServer.Handle("POST", "/api/ref/getBacklink", model.CheckAuth, getBacklink)
Expand All @@ -200,7 +202,7 @@ func ServeAPI(ginServer *gin.Engine) {

ginServer.Handle("POST", "/api/attr/getBookmarkLabels", model.CheckAuth, getBookmarkLabels)
ginServer.Handle("POST", "/api/attr/resetBlockAttrs", model.CheckAuth, model.CheckReadonly, resetBlockAttrs)
ginServer.Handle("POST", "/api/attr/setBlockAttrs", model.CheckAuth, setBlockAttrs)
ginServer.Handle("POST", "/api/attr/setBlockAttrs", model.CheckAuth, model.CheckReadonly, setBlockAttrs)
ginServer.Handle("POST", "/api/attr/getBlockAttrs", model.CheckAuth, getBlockAttrs)

ginServer.Handle("POST", "/api/cloud/getCloudSpace", model.CheckAuth, getCloudSpace)
Expand All @@ -215,15 +217,15 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/sync/setCloudSyncDir", model.CheckAuth, model.CheckReadonly, setCloudSyncDir)
ginServer.Handle("POST", "/api/sync/createCloudSyncDir", model.CheckAuth, model.CheckReadonly, createCloudSyncDir)
ginServer.Handle("POST", "/api/sync/removeCloudSyncDir", model.CheckAuth, model.CheckReadonly, removeCloudSyncDir)
ginServer.Handle("POST", "/api/sync/listCloudSyncDir", model.CheckAuth, model.CheckReadonly, listCloudSyncDir)
ginServer.Handle("POST", "/api/sync/listCloudSyncDir", model.CheckAuth, listCloudSyncDir)
ginServer.Handle("POST", "/api/sync/performSync", model.CheckAuth, model.CheckReadonly, performSync)
ginServer.Handle("POST", "/api/sync/performBootSync", model.CheckAuth, model.CheckReadonly, performBootSync)
ginServer.Handle("POST", "/api/sync/getBootSync", model.CheckAuth, getBootSync)
ginServer.Handle("POST", "/api/sync/getSyncInfo", model.CheckAuth, getSyncInfo)
ginServer.Handle("POST", "/api/sync/exportSyncProviderS3", model.CheckAuth, exportSyncProviderS3)
ginServer.Handle("POST", "/api/sync/importSyncProviderS3", model.CheckAuth, importSyncProviderS3)
ginServer.Handle("POST", "/api/sync/importSyncProviderS3", model.CheckAuth, model.CheckReadonly, importSyncProviderS3)
ginServer.Handle("POST", "/api/sync/exportSyncProviderWebDAV", model.CheckAuth, exportSyncProviderWebDAV)
ginServer.Handle("POST", "/api/sync/importSyncProviderWebDAV", model.CheckAuth, importSyncProviderWebDAV)
ginServer.Handle("POST", "/api/sync/importSyncProviderWebDAV", model.CheckAuth, model.CheckReadonly, importSyncProviderWebDAV)

ginServer.Handle("POST", "/api/inbox/getShorthands", model.CheckAuth, getShorthands)
ginServer.Handle("POST", "/api/inbox/getShorthand", model.CheckAuth, getShorthand)
Expand All @@ -243,7 +245,7 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/asset/getMissingAssets", model.CheckAuth, getMissingAssets)
ginServer.Handle("POST", "/api/asset/removeUnusedAsset", model.CheckAuth, model.CheckReadonly, removeUnusedAsset)
ginServer.Handle("POST", "/api/asset/removeUnusedAssets", model.CheckAuth, model.CheckReadonly, removeUnusedAssets)
ginServer.Handle("POST", "/api/asset/getDocImageAssets", model.CheckAuth, model.CheckReadonly, getDocImageAssets)
ginServer.Handle("POST", "/api/asset/getDocImageAssets", model.CheckAuth, getDocImageAssets)
ginServer.Handle("POST", "/api/asset/renameAsset", model.CheckAuth, model.CheckReadonly, renameAsset)
ginServer.Handle("POST", "/api/asset/getImageOCRText", model.CheckAuth, model.CheckReadonly, getImageOCRText)
ginServer.Handle("POST", "/api/asset/setImageOCRText", model.CheckAuth, model.CheckReadonly, setImageOCRText)
Expand Down Expand Up @@ -284,7 +286,7 @@ func ServeAPI(ginServer *gin.Engine) {

ginServer.Handle("POST", "/api/template/render", model.CheckAuth, renderTemplate)
ginServer.Handle("POST", "/api/template/docSaveAsTemplate", model.CheckAuth, model.CheckReadonly, docSaveAsTemplate)
ginServer.Handle("POST", "/api/template/renderSprig", model.CheckAuth, model.CheckReadonly, renderSprig)
ginServer.Handle("POST", "/api/template/renderSprig", model.CheckAuth, renderSprig)

ginServer.Handle("POST", "/api/transactions", model.CheckAuth, model.CheckReadonly, performTransactions)

Expand Down Expand Up @@ -363,33 +365,33 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/riff/getRiffCards", model.CheckAuth, getRiffCards)
ginServer.Handle("POST", "/api/riff/getTreeRiffCards", model.CheckAuth, getTreeRiffCards)
ginServer.Handle("POST", "/api/riff/getNotebookRiffCards", model.CheckAuth, getNotebookRiffCards)
ginServer.Handle("POST", "/api/riff/resetRiffCards", model.CheckAuth, resetRiffCards)
ginServer.Handle("POST", "/api/riff/resetRiffCards", model.CheckAuth, model.CheckReadonly, resetRiffCards)

ginServer.Handle("POST", "/api/notification/pushMsg", model.CheckAuth, pushMsg)
ginServer.Handle("POST", "/api/notification/pushErrMsg", model.CheckAuth, pushErrMsg)

ginServer.Handle("POST", "/api/snippet/getSnippet", model.CheckAuth, getSnippet)
ginServer.Handle("POST", "/api/snippet/setSnippet", model.CheckAuth, setSnippet)
ginServer.Handle("POST", "/api/snippet/setSnippet", model.CheckAuth, model.CheckReadonly, setSnippet)
ginServer.Handle("POST", "/api/snippet/removeSnippet", model.CheckAuth, model.CheckReadonly, removeSnippet)
ginServer.Handle("GET", "/snippets/*filepath", serveSnippets)

ginServer.Handle("POST", "/api/av/renderAttributeView", model.CheckAuth, renderAttributeView)
ginServer.Handle("POST", "/api/av/getAttributeViewKeys", model.CheckAuth, getAttributeViewKeys)
ginServer.Handle("POST", "/api/av/setAttributeViewBlockAttr", model.CheckAuth, setAttributeViewBlockAttr)
ginServer.Handle("POST", "/api/av/setAttributeViewBlockAttr", model.CheckAuth, model.CheckReadonly, setAttributeViewBlockAttr)

ginServer.Handle("POST", "/api/ai/chatGPT", model.CheckAuth, model.CheckReadonly, chatGPT)
ginServer.Handle("POST", "/api/ai/chatGPTWithAction", model.CheckAuth, model.CheckReadonly, chatGPTWithAction)
ginServer.Handle("POST", "/api/ai/chatGPT", model.CheckAuth, chatGPT)
ginServer.Handle("POST", "/api/ai/chatGPTWithAction", model.CheckAuth, chatGPTWithAction)

ginServer.Handle("POST", "/api/petal/loadPetals", model.CheckAuth, loadPetals)
ginServer.Handle("POST", "/api/petal/setPetalEnabled", model.CheckAuth, model.CheckReadonly, setPetalEnabled)

ginServer.Handle("POST", "/api/network/forwardProxy", model.CheckAuth, model.CheckReadonly, forwardProxy)
ginServer.Any("/api/network/echo", model.CheckAuth, echo)
ginServer.Handle("POST", "/api/network/forwardProxy", model.CheckAuth, forwardProxy)

ginServer.Handle("GET", "/ws/broadcast", model.CheckAuth, broadcast)
ginServer.Handle("GET", "/api/broadcast/channels", model.CheckAuth, getChannels)
ginServer.Handle("POST", "/api/broadcast/postMessage", model.CheckAuth, postMessage)
ginServer.Handle("POST", "/api/broadcast/getChannels", model.CheckAuth, getChannels)
ginServer.Handle("POST", "/api/broadcast/getChannelInfo", model.CheckAuth, getChannelInfo)

ginServer.Handle("POST", "/api/archive/zip", model.CheckAuth, zip)
ginServer.Handle("POST", "/api/archive/unzip", model.CheckAuth, unzip)
ginServer.Handle("POST", "/api/archive/zip", model.CheckAuth, model.CheckReadonly, zip)
ginServer.Handle("POST", "/api/archive/unzip", model.CheckAuth, model.CheckReadonly, unzip)
}
4 changes: 3 additions & 1 deletion kernel/model/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,12 @@ func CheckAuth(c *gin.Context) {
// 未设置访问授权码
if "" == Conf.AccessAuthCode {
// Authenticate requests with the Origin header other than 127.0.0.1 https://github.com/siyuan-note/siyuan/issues/9180
clientIP := c.ClientIP()
host := c.GetHeader("Host")
origin := c.GetHeader("Origin")
forwardedHost := c.GetHeader("X-Forwarded-Host")
if !localhost ||
("" != clientIP && !util.IsLocalHostname(clientIP)) ||
("" != host && !util.IsLocalHost(host)) ||
("" != origin && !util.IsLocalOrigin(origin) && !strings.HasPrefix(origin, "chrome-extension://")) ||
("" != forwardedHost && !util.IsLocalHost(forwardedHost)) {
Expand Down Expand Up @@ -243,7 +245,7 @@ func CheckAuth(c *gin.Context) {
if workspaceSession.AccessAuthCode != Conf.AccessAuthCode {
userAgentHeader := c.GetHeader("User-Agent")
if strings.HasPrefix(userAgentHeader, "SiYuan/") || strings.HasPrefix(userAgentHeader, "Mozilla/") {
if "GET" != c.Request.Method {
if "GET" != c.Request.Method || c.IsWebsocket() {
c.JSON(http.StatusUnauthorized, map[string]interface{}{"code": -1, "msg": Conf.Language(156)})
c.Abort()
return
Expand Down
24 changes: 6 additions & 18 deletions kernel/util/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,8 @@ func ValidOptionalPort(port string) bool {
return true
}

func SplitHost(host string) (hostname, port string) {
hostname = host

colon := strings.LastIndexByte(hostname, ':')
if colon != -1 && ValidOptionalPort(hostname[colon:]) {
hostname, port = hostname[:colon], hostname[colon+1:]
}

if strings.HasPrefix(hostname, "[") && strings.HasSuffix(hostname, "]") {
hostname = hostname[1 : len(hostname)-1]
}

return
}

func IsLocalHostname(hostname string) bool {
if "localhost" == hostname {
if "localhost" == hostname || strings.HasSuffix(hostname, ".localhost") {
return true
}
if ip := net.ParseIP(hostname); nil != ip {
Expand All @@ -73,8 +58,11 @@ func IsLocalHostname(hostname string) bool {
}

func IsLocalHost(host string) bool {
hostname, _ := SplitHost(host)
return IsLocalHostname(hostname)
if hostname, _, err := net.SplitHostPort(strings.TrimSpace(host)); nil != err {
return false
} else {
return IsLocalHostname(hostname)
}
}

func IsLocalOrigin(origin string) bool {
Expand Down
Loading