mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-22 20:42:40 +00:00
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 <tho.miceli@gmail.com>
This commit is contained in:
parent
2fd053a077
commit
22052bd38f
20 changed files with 187 additions and 37 deletions
18
internal/auth/auth.go
Normal file
18
internal/auth/auth.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: Название
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: 标题
|
||||
|
|
|
@ -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: 標題
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
11
templates/pages/admin_config.html
vendored
11
templates/pages/admin_config.html
vendored
|
@ -93,6 +93,17 @@
|
|||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-none gap-x-4 py-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="flex flex-grow flex-col">
|
||||
<span class="text-sm font-medium leading-6 text-slate-700 dark:text-slate-300">{{ .locale.Tr "admin.allow-gists-without-login" }}</span>
|
||||
<span class="text-sm text-gray-400 dark:text-gray-400">{{ .locale.Tr "admin.allow-gists-without-login_help" }}</span>
|
||||
</span>
|
||||
<button type="button" id="allow-gists-without-login" data-bool="{{ .AllowGistsWithoutLogin }}" class="toggle-button {{ if .AllowGistsWithoutLogin }}bg-primary-600{{else}}bg-gray-300 dark:bg-gray-400{{end}} relative inline-flex h-6 w-11 ml-4 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-600 focus:ring-offset-2" role="switch" aria-checked="false" aria-labelledby="availability-label" aria-describedby="availability-description">
|
||||
<span aria-hidden="true" class="{{ if .AllowGistsWithoutLogin }}translate-x-5{{else}}translate-x-0{{end}} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-none gap-x-4 py-5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="flex flex-grow flex-col">
|
||||
|
|
Loading…
Reference in a new issue