Skip to content

Commit

Permalink
Add a setting to allow anonymous access to individual gists while sti…
Browse files Browse the repository at this point in the history
…ll 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 <[email protected]>
  • Loading branch information
lf- and thomiceli committed May 12, 2024
1 parent 2fd053a commit 22052bd
Show file tree
Hide file tree
Showing 20 changed files with 187 additions and 37 deletions.
18 changes: 18 additions & 0 deletions internal/auth/auth.go
Original file line number Diff line number Diff line change
@@ -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
}
27 changes: 23 additions & 4 deletions internal/db/admin_setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
9 changes: 5 additions & 4 deletions internal/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
})
}

Expand Down
3 changes: 2 additions & 1 deletion internal/i18n/locales/cs-CZ.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion internal/i18n/locales/de-DE.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions internal/i18n/locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion internal/i18n/locales/es-ES.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion internal/i18n/locales/fr-FR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion internal/i18n/locales/hu-HU.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion internal/i18n/locales/pt-BR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion internal/i18n/locales/ru-RU.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: Название
Expand Down
3 changes: 2 additions & 1 deletion internal/i18n/locales/tr-TR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion internal/i18n/locales/zh-CN.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: 标题
Expand Down
3 changes: 2 additions & 1 deletion internal/i18n/locales/zh-TW.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: 標題
Expand Down
12 changes: 7 additions & 5 deletions internal/ssh/git_ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
}
Expand All @@ -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 {
Expand Down
12 changes: 12 additions & 0 deletions internal/web/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
8 changes: 7 additions & 1 deletion internal/web/git_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}

Expand Down
37 changes: 24 additions & 13 deletions internal/web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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 {
return next(ctx)
}
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")
}

require := getData(ctx, "RequireLogin")
if require == true {
addFlash(ctx, tr(ctx, "flash.auth.must-be-logged-in"), "error")
return redirect(ctx, "/login")
if !allow {
addFlash(ctx, tr(ctx, "flash.auth.must-be-logged-in"), "error")
return redirect(ctx, "/login")
}
return next(ctx)
}
return next(ctx)
}
}

func checkRequireLogin(next echo.HandlerFunc) echo.HandlerFunc {
return makeCheckRequireLogin(false)(next)
}

func noRouteFound(echo.Context) error {
return notFound("Page not found")
}
Expand Down
58 changes: 58 additions & 0 deletions internal/web/test/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

}

0 comments on commit 22052bd

Please sign in to comment.