From 22052bd38fd56f8b144e2b199720e62314e9b2f1 Mon Sep 17 00:00:00 2001 From: Jade Lovelace Date: Sun, 12 May 2024 14:40:11 -0700 Subject: [PATCH] Add a setting to allow anonymous access to individual gists while still RequireLogin everywhere else (#229) * Add a setting to allow accessing individual gists without auth This is a middle ground between the existing setting "Require Login", which requires login to do anything at all, and having it off, which shows a public list of gists and more generally allows discovering info about the users/gists of the instance without login. The idea of this setting is that it is "require login" for everything except individual gists. Fixes #228. Co-authored-by: Thomas Miceli --- internal/auth/auth.go | 18 ++++++++++ internal/db/admin_setting.go | 27 +++++++++++--- internal/db/db.go | 9 ++--- internal/i18n/locales/cs-CZ.yml | 3 +- internal/i18n/locales/de-DE.yml | 3 +- internal/i18n/locales/en-US.yml | 2 ++ internal/i18n/locales/es-ES.yml | 3 +- internal/i18n/locales/fr-FR.yml | 3 +- internal/i18n/locales/hu-HU.yml | 3 +- internal/i18n/locales/pt-BR.yml | 3 +- internal/i18n/locales/ru-RU.yml | 3 +- internal/i18n/locales/tr-TR.yml | 3 +- internal/i18n/locales/zh-CN.yml | 3 +- internal/i18n/locales/zh-TW.yml | 3 +- internal/ssh/git_ssh.go | 12 ++++--- internal/web/auth.go | 12 +++++++ internal/web/git_http.go | 8 ++++- internal/web/server.go | 37 +++++++++++++------- internal/web/test/auth_test.go | 58 +++++++++++++++++++++++++++++++ templates/pages/admin_config.html | 11 ++++++ 20 files changed, 187 insertions(+), 37 deletions(-) create mode 100644 internal/auth/auth.go diff --git a/internal/auth/auth.go b/internal/auth/auth.go new file mode 100644 index 0000000..a5c55fa --- /dev/null +++ b/internal/auth/auth.go @@ -0,0 +1,18 @@ +package auth + +type AuthInfoProvider interface { + RequireLogin() (bool, error) + AllowGistsWithoutLogin() (bool, error) +} + +func ShouldAllowUnauthenticatedGistAccess(prov AuthInfoProvider, isSingleGistAccess bool) (bool, error) { + require, err := prov.RequireLogin() + if err != nil { + return false, err + } + allow, err := prov.AllowGistsWithoutLogin() + if err != nil { + return false, err + } + return !require || (isSingleGistAccess && allow), nil +} diff --git a/internal/db/admin_setting.go b/internal/db/admin_setting.go index 7fb8cb4..c338203 100644 --- a/internal/db/admin_setting.go +++ b/internal/db/admin_setting.go @@ -10,10 +10,11 @@ type AdminSetting struct { } const ( - SettingDisableSignup = "disable-signup" - SettingRequireLogin = "require-login" - SettingDisableLoginForm = "disable-login-form" - SettingDisableGravatar = "disable-gravatar" + SettingDisableSignup = "disable-signup" + SettingRequireLogin = "require-login" + SettingAllowGistsWithoutLogin = "allow-gists-without-login" + SettingDisableLoginForm = "disable-login-form" + SettingDisableGravatar = "disable-gravatar" ) func GetSetting(key string) (string, error) { @@ -62,3 +63,21 @@ func initAdminSettings(settings map[string]string) error { return nil } + +type DBAuthInfo struct{} + +func (auth DBAuthInfo) RequireLogin() (bool, error) { + s, err := GetSetting(SettingRequireLogin) + if err != nil { + return true, err + } + return s == "1", nil +} + +func (auth DBAuthInfo) AllowGistsWithoutLogin() (bool, error) { + s, err := GetSetting(SettingAllowGistsWithoutLogin) + if err != nil { + return false, err + } + return s == "1", nil +} diff --git a/internal/db/db.go b/internal/db/db.go index 5cb5709..b6adb48 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -52,10 +52,11 @@ func Setup(dbPath string, sharedCache bool) error { // Default admin setting values return initAdminSettings(map[string]string{ - SettingDisableSignup: "0", - SettingRequireLogin: "0", - SettingDisableLoginForm: "0", - SettingDisableGravatar: "0", + SettingDisableSignup: "0", + SettingRequireLogin: "0", + SettingAllowGistsWithoutLogin: "0", + SettingDisableLoginForm: "0", + SettingDisableGravatar: "0", }) } diff --git a/internal/i18n/locales/cs-CZ.yml b/internal/i18n/locales/cs-CZ.yml index d4d5a9e..156e6ea 100644 --- a/internal/i18n/locales/cs-CZ.yml +++ b/internal/i18n/locales/cs-CZ.yml @@ -173,7 +173,8 @@ admin.disable-login: Zakázat přihlášení admin.disable-login_help: Zakázat přihlašování pomocí formuláře pro přihlášení a vynutit používání OAuth poskytovatele. admin.disable-gravatar: Zakázat Gravatar admin.disable-gravatar_help: Zakázat použití Gravataru jako poskytovatele avatara. - +admin.allow-gists-without-login: +admin.allow-gists-without-login_help: admin.users.delete_confirm: Opravdu chcete smazat tohoto uživatele? admin.gists.title: Titulek diff --git a/internal/i18n/locales/de-DE.yml b/internal/i18n/locales/de-DE.yml index 6056052..ab4dcd4 100644 --- a/internal/i18n/locales/de-DE.yml +++ b/internal/i18n/locales/de-DE.yml @@ -185,7 +185,8 @@ admin.disable-login: 'Login-Maske deaktivieren' admin.disable-login_help: 'Login über Login-Maske verbieten und Benutzung von OAuth Providern erzwingen.' admin.disable-gravatar: 'Gravatar deaktivieren' admin.disable-gravatar_help: 'Gravatar als Avatar-Anbieter deaktivieren.' - +admin.allow-gists-without-login: +admin.allow-gists-without-login_help: admin.users.delete_confirm: 'Willst du diesen Benutzer löschen?' admin.gists.title: 'Titel' diff --git a/internal/i18n/locales/en-US.yml b/internal/i18n/locales/en-US.yml index 0ed41ca..9a34da6 100644 --- a/internal/i18n/locales/en-US.yml +++ b/internal/i18n/locales/en-US.yml @@ -203,6 +203,8 @@ admin.disable-signup: Disable signup admin.disable-signup_help: Forbid the creation of new accounts. admin.require-login: Require login admin.require-login_help: Enforce users to be logged in to see gists. +admin.allow-gists-without-login: Allow individual gists without login +admin.allow-gists-without-login_help: Allow individual gists to be viewed and downloaded without login, while requiring login for discovering gists. admin.disable-login: Disable login form admin.disable-login_help: Forbid logging in via the login form to force using OAuth providers instead. admin.disable-gravatar: Disable Gravatar diff --git a/internal/i18n/locales/es-ES.yml b/internal/i18n/locales/es-ES.yml index 1fd70b2..a9b7aef 100644 --- a/internal/i18n/locales/es-ES.yml +++ b/internal/i18n/locales/es-ES.yml @@ -165,7 +165,8 @@ admin.disable-login: Deshabilitar formulario de inicio de sesión admin.disable-login_help: Prohibir el inicio de sesión a través del formulario de inicio de sesión para forzar el uso de proveedores de OAuth en su lugar. admin.disable-gravatar: Deshabilitar Gravatar admin.disable-gravatar_help: Deshabilitar el uso de Gravatar como proveedor de avatar. - +admin.allow-gists-without-login: +admin.allow-gists-without-login_help: admin.users.delete_confirm: ¿Quieres eliminar a este usuario? admin.gists.title: Título diff --git a/internal/i18n/locales/fr-FR.yml b/internal/i18n/locales/fr-FR.yml index d6716f8..2b034d8 100644 --- a/internal/i18n/locales/fr-FR.yml +++ b/internal/i18n/locales/fr-FR.yml @@ -166,7 +166,8 @@ admin.disable-login: Désactiver le formulaire de connexion admin.disable-login_help: Interdire la connexion via le formulaire de connexion pour forcer l'utilisation des fournisseurs OAuth à la place. admin.disable-gravatar: Désactiver Gravatar admin.disable-gravatar_help: Désactiver l'utilisation de Gravatar comme fournisseur d'avatar. - +admin.allow-gists-without-login: +admin.allow-gists-without-login_help: admin.users.delete_confirm: Voulez-vous supprimer cet utilisateur ? admin.gists.title: Titre diff --git a/internal/i18n/locales/hu-HU.yml b/internal/i18n/locales/hu-HU.yml index 9c5c05a..cc00d0b 100644 --- a/internal/i18n/locales/hu-HU.yml +++ b/internal/i18n/locales/hu-HU.yml @@ -186,7 +186,8 @@ admin.disable-login: Bejelentkezés űrlap letiltása admin.disable-login_help: Letiltja a bejelentkezés űrlapon keresztüli bejelentkezéseket, OAuth szolgáltatókat ajánlva helyette. admin.disable-gravatar: Gravatar kikapcsolása admin.disable-gravatar_help: Tiltsd le a Gravatar-t mint profilkép szolgáltató. - +admin.allow-gists-without-login: +admin.allow-gists-without-login_help: admin.users.delete_confirm: Biztosan törlöd ezt a felhasználót? admin.gists.title: Cím diff --git a/internal/i18n/locales/pt-BR.yml b/internal/i18n/locales/pt-BR.yml index 4e71c5b..ecad48c 100644 --- a/internal/i18n/locales/pt-BR.yml +++ b/internal/i18n/locales/pt-BR.yml @@ -163,7 +163,8 @@ admin.disable-login: Desabilitar formulário de login admin.disable-login_help: Proibir o login através do formulário de login para forçar o uso de provedores de OAuth no lugar. admin.disable-gravatar: Desabilitar Gravatar admin.disable-gravatar_help: Desabilitar o uso do Gravatar como provedor de avatar. - +admin.allow-gists-without-login: +admin.allow-gists-without-login_help: admin.users.delete_confirm: Quer excluir este usuário? admin.gists.title: Título diff --git a/internal/i18n/locales/ru-RU.yml b/internal/i18n/locales/ru-RU.yml index 5f9b409..2588993 100644 --- a/internal/i18n/locales/ru-RU.yml +++ b/internal/i18n/locales/ru-RU.yml @@ -166,7 +166,8 @@ admin.disable-login: Запретить авторизацию по паролю admin.disable-login_help: Запретить авторизацию с вводом пароля, форсировать внешнюю авторизацию через Gitea/GitHub. admin.disable-gravatar: Запретить Gravatar admin.disable-gravatar_help: Запретить использование Gravatar как провайдера изображений профиля. - +admin.allow-gists-without-login: +admin.allow-gists-without-login_help: admin.users.delete_confirm: Вы уверены что хотите удалить этого пользователя? admin.gists.title: Название diff --git a/internal/i18n/locales/tr-TR.yml b/internal/i18n/locales/tr-TR.yml index 98883e1..781746d 100644 --- a/internal/i18n/locales/tr-TR.yml +++ b/internal/i18n/locales/tr-TR.yml @@ -207,7 +207,8 @@ admin.disable-login: Disable login form admin.disable-login_help: Forbid logging in via the login form to force using OAuth providers instead. admin.disable-gravatar: Disable Gravatar admin.disable-gravatar_help: Disable the usage of Gravatar as an avatar provider. - +admin.allow-gists-without-login: +admin.allow-gists-without-login_help: admin.users.delete_confirm: Do you want to delete this user ? admin.gists.title: Title diff --git a/internal/i18n/locales/zh-CN.yml b/internal/i18n/locales/zh-CN.yml index 8f330ee..37a8ad9 100644 --- a/internal/i18n/locales/zh-CN.yml +++ b/internal/i18n/locales/zh-CN.yml @@ -166,7 +166,8 @@ admin.disable-login: 禁用登录表单 admin.disable-login_help: 禁止使用登录表单进行登录以强制通过 OAuth 提供方登录。 admin.disable-gravatar: 禁用 Gravatar admin.disable-gravatar_help: 停止使用 Gravatar 作为头像提供方。 - +admin.allow-gists-without-login: +admin.allow-gists-without-login_help: admin.users.delete_confirm: 你想要删除此用户吗? admin.gists.title: 标题 diff --git a/internal/i18n/locales/zh-TW.yml b/internal/i18n/locales/zh-TW.yml index a59f86b..e2e5fe6 100644 --- a/internal/i18n/locales/zh-TW.yml +++ b/internal/i18n/locales/zh-TW.yml @@ -177,7 +177,8 @@ admin.disable-login: 關閉登錄頁面 admin.disable-login_help: 關閉通過登錄頁面登錄,強制使用 OAuth 提供者。 admin.disable-gravatar: 禁用 Gravatar admin.disable-gravatar_help: 禁止使用 Gravatar 作為頭像提供者。 - +admin.allow-gists-without-login: +admin.allow-gists-without-login_help: admin.users.delete_confirm: 您要刪除這個使用者嗎? admin.gists.title: 標題 diff --git a/internal/ssh/git_ssh.go b/internal/ssh/git_ssh.go index f6154c8..caf9c9f 100644 --- a/internal/ssh/git_ssh.go +++ b/internal/ssh/git_ssh.go @@ -2,14 +2,16 @@ package ssh import ( "errors" + "io" + "os/exec" + "strings" + "github.com/rs/zerolog/log" + "github.com/thomiceli/opengist/internal/auth" "github.com/thomiceli/opengist/internal/db" "github.com/thomiceli/opengist/internal/git" "golang.org/x/crypto/ssh" "gorm.io/gorm" - "io" - "os/exec" - "strings" ) func runGitCommand(ch ssh.Channel, gitCmd string, key string, ip string) error { @@ -37,7 +39,7 @@ func runGitCommand(ch ssh.Channel, gitCmd string, key string, ip string) error { return errors.New("gist not found") } - requireLogin, err := db.GetSetting(db.SettingRequireLogin) + allowUnauthenticated, err := auth.ShouldAllowUnauthenticatedGistAccess(db.DBAuthInfo{}, true) if err != nil { return errors.New("internal server error") } @@ -50,7 +52,7 @@ func runGitCommand(ch ssh.Channel, gitCmd string, key string, ip string) error { if verb == "receive-pack" || gist.Private == 2 || gist.ID == 0 || - requireLogin == "1" { + !allowUnauthenticated { pubKey, err := db.SSHKeyExistsForUser(key, gist.UserID) if err != nil { diff --git a/internal/web/auth.go b/internal/web/auth.go index f36508e..1b4ff79 100644 --- a/internal/web/auth.go +++ b/internal/web/auth.go @@ -442,3 +442,15 @@ func getAvatarUrlFromProvider(provider string, identifier string) string { } return "" } + +type ContextAuthInfo struct { + context echo.Context +} + +func (auth ContextAuthInfo) RequireLogin() (bool, error) { + return getData(auth.context, "RequireLogin") == true, nil +} + +func (auth ContextAuthInfo) AllowGistsWithoutLogin() (bool, error) { + return getData(auth.context, "AllowGistsWithoutLogin") == true, nil +} diff --git a/internal/web/git_http.go b/internal/web/git_http.go index dbce696..bf8bbf8 100644 --- a/internal/web/git_http.go +++ b/internal/web/git_http.go @@ -19,6 +19,7 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v4" "github.com/rs/zerolog/log" + "github.com/thomiceli/opengist/internal/auth" "github.com/thomiceli/opengist/internal/db" "github.com/thomiceli/opengist/internal/git" "github.com/thomiceli/opengist/internal/memdb" @@ -70,12 +71,17 @@ func gitHttp(ctx echo.Context) error { setData(ctx, "repositoryPath", repositoryPath) + allow, err := auth.ShouldAllowUnauthenticatedGistAccess(ContextAuthInfo{ctx}, true) + if err != nil { + panic("impossible") + } + // Shows basic auth if : // - user wants to push the gist // - user wants to clone/pull a private gist // - gist is not found (obfuscation) // - admin setting to require login is set to true - if isPull && gist.Private != db.PrivateVisibility && gist.ID != 0 && !getData(ctx, "RequireLogin").(bool) { + if isPull && gist.Private != db.PrivateVisibility && gist.ID != 0 && allow { return route.handler(ctx) } diff --git a/internal/web/server.go b/internal/web/server.go index 3d3ad7d..9da5848 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -27,6 +27,7 @@ import ( "github.com/labstack/echo/v4/middleware" "github.com/markbates/goth/gothic" "github.com/rs/zerolog/log" + "github.com/thomiceli/opengist/internal/auth" "github.com/thomiceli/opengist/internal/config" "github.com/thomiceli/opengist/internal/db" "github.com/thomiceli/opengist/internal/git" @@ -309,7 +310,7 @@ func NewServer(isDev bool) *Server { g3 := g1.Group("/:user/:gistname") { - g3.Use(checkRequireLogin, gistInit) + g3.Use(makeCheckRequireLogin(true), gistInit) g3.GET("", gistIndex) g3.GET("/rev/:revision", gistIndex) g3.GET("/revisions", revisions) @@ -321,9 +322,9 @@ func NewServer(isDev bool) *Server { g3.GET("/edit", edit, logged, writePermission) g3.POST("/edit", processCreate, logged, writePermission) g3.POST("/like", like, logged) - g3.GET("/likes", likes) + g3.GET("/likes", likes, checkRequireLogin) g3.POST("/fork", fork, logged) - g3.GET("/forks", forks) + g3.GET("/forks", forks, checkRequireLogin) g3.PUT("/checkbox", checkbox, logged, writePermission) } } @@ -516,21 +517,31 @@ func logged(next echo.HandlerFunc) echo.HandlerFunc { } } -func checkRequireLogin(next echo.HandlerFunc) echo.HandlerFunc { - return func(ctx echo.Context) error { - if user := getUserLogged(ctx); user != nil { +func makeCheckRequireLogin(isSingleGistAccess bool) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(ctx echo.Context) error { + if user := getUserLogged(ctx); user != nil { + return next(ctx) + } + + allow, err := auth.ShouldAllowUnauthenticatedGistAccess(ContextAuthInfo{ctx}, isSingleGistAccess) + if err != nil { + panic("impossible") + } + + if !allow { + addFlash(ctx, tr(ctx, "flash.auth.must-be-logged-in"), "error") + return redirect(ctx, "/login") + } return next(ctx) } - - require := getData(ctx, "RequireLogin") - if require == true { - addFlash(ctx, tr(ctx, "flash.auth.must-be-logged-in"), "error") - return redirect(ctx, "/login") - } - return next(ctx) } } +func checkRequireLogin(next echo.HandlerFunc) echo.HandlerFunc { + return makeCheckRequireLogin(false)(next) +} + func noRouteFound(echo.Context) error { return notFound("Page not found") } diff --git a/internal/web/test/auth_test.go b/internal/web/test/auth_test.go index 73b7b9d..f648815 100644 --- a/internal/web/test/auth_test.go +++ b/internal/web/test/auth_test.go @@ -89,3 +89,61 @@ func login(t *testing.T, s *testServer, user db.UserDTO) { err := s.request("POST", "/login", user, 302) require.NoError(t, err) } + +type settingSet struct { + key string `form:"key"` + value string `form:"value"` +} + +func TestAnonymous(t *testing.T) { + setup(t) + s, err := newTestServer() + require.NoError(t, err, "Failed to create test server") + defer teardown(t, s) + + user := db.UserDTO{Username: "thomas", Password: "azeaze"} + register(t, s, user) + + err = s.request("PUT", "/admin-panel/set-config", settingSet{"require-login", "1"}, 200) + require.NoError(t, err) + + gist1 := db.GistDTO{ + Title: "gist1", + Description: "my first gist", + VisibilityDTO: db.VisibilityDTO{ + Private: 0, + }, + Name: []string{"gist1.txt", "gist2.txt", "gist3.txt"}, + Content: []string{"yeah", "yeah\ncool", "yeah\ncool gist actually"}, + } + err = s.request("POST", "/", gist1, 302) + require.NoError(t, err) + + gist1db, err := db.GetGistByID("1") + require.NoError(t, err) + + err = s.request("GET", "/all", nil, 200) + require.NoError(t, err) + + cookie := s.sessionCookie + s.sessionCookie = "" + + err = s.request("GET", "/all", nil, 302) + require.NoError(t, err) + + // Should redirect to login if RequireLogin + err = s.request("GET", "/"+gist1db.User.Username+"/"+gist1db.Uuid, nil, 302) + require.NoError(t, err) + + s.sessionCookie = cookie + + err = s.request("PUT", "/admin-panel/set-config", settingSet{"allow-gists-without-login", "1"}, 200) + require.NoError(t, err) + + s.sessionCookie = "" + + // Should return results + err = s.request("GET", "/"+gist1db.User.Username+"/"+gist1db.Uuid, nil, 200) + require.NoError(t, err) + +} diff --git a/templates/pages/admin_config.html b/templates/pages/admin_config.html index b04d73e..d296248 100644 --- a/templates/pages/admin_config.html +++ b/templates/pages/admin_config.html @@ -93,6 +93,17 @@ +
  • +
    + + {{ .locale.Tr "admin.allow-gists-without-login" }} + {{ .locale.Tr "admin.allow-gists-without-login_help" }} + + +
    +