feat: demo mode (#616)

This commit is contained in:
yehong 2023-08-31 23:53:25 +08:00 committed by GitHub
parent d940bab0f5
commit 43000075c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 127 additions and 0 deletions

View File

@ -7,6 +7,7 @@ driverName = mysql
dataSourceName = root:123@tcp(localhost:3306)/
dbName = casibase
redisEndpoint =
isDemoMode = false
landingFolder = casibase-landing
casdoorEndpoint = http://localhost:8000
clientId = af6b5aa958822fb9dc33

View File

@ -14,6 +14,8 @@
package controllers
import "github.com/astaxie/beego/context"
type Response struct {
Status string `json:"status"`
Msg string `json:"msg"`
@ -65,3 +67,23 @@ func (c *ApiController) RequireAdmin() bool {
return false
}
func DenyRequest(ctx *context.Context) {
responseError(ctx, "Unauthorized operation")
}
func responseError(ctx *context.Context, error string, data ...interface{}) {
resp := Response{Status: "error", Msg: error}
switch len(data) {
case 2:
resp.Data2 = data[1]
fallthrough
case 1:
resp.Data = data[0]
}
err := ctx.Output.JSON(resp, true, false)
if err != nil {
panic(err)
}
}

View File

@ -40,6 +40,7 @@ func main() {
// https://studygolang.com/articles/2303
beego.InsertFilter("/", beego.BeforeRouter, routers.TransparentStatic) // must has this for default page
beego.InsertFilter("/*", beego.BeforeRouter, routers.TransparentStatic)
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
if beego.AppConfig.String("redisEndpoint") == "" {
beego.BConfig.WebConfig.Session.SessionProvider = "file"

View File

@ -19,6 +19,9 @@ import (
"net/http"
"strings"
"github.com/casbin/casibase/conf"
"github.com/casbin/casibase/controllers"
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
"github.com/casbin/casibase/util"
@ -57,3 +60,38 @@ func TransparentStatic(ctx *context.Context) {
http.ServeFile(ctx.ResponseWriter, ctx.Request, "web/build/index.html")
}
}
func ApiFilter(ctx *context.Context) {
method := ctx.Request.Method
urlPath := getUrlPath(ctx.Request.URL.Path)
if conf.IsDemoMode() {
if !isAllowedInDemoMode(method, urlPath) {
controllers.DenyRequest(ctx)
}
}
}
func getUrlPath(urlPath string) string {
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate") || strings.HasSuffix(urlPath, "/p3/serviceValidate") || strings.HasSuffix(urlPath, "/p3/proxyValidate") || strings.HasSuffix(urlPath, "/samlValidate")) {
return "/cas"
}
if strings.HasPrefix(urlPath, "/api/login/oauth") {
return "/api/login/oauth"
}
if strings.HasPrefix(urlPath, "/api/webauthn") {
return "/api/webauthn"
}
return urlPath
}
func isAllowedInDemoMode(method string, urlPath string) bool {
if method == "POST" && !(strings.HasPrefix(urlPath, "/api/signin") || urlPath == "/api/signout") {
return false
}
return true
}

View File

@ -30,6 +30,7 @@ export const ForceLanguage = "";
export const DefaultLanguage = "en";
export const AppUrl = "";
export const IsDemoMode = false;
export const ThemeDefault = {
themeType: "default",

View File

@ -648,3 +648,7 @@ export function renderExternalLink() {
</svg>
);
}
export function isResponseDenied(data) {
return data.msg === "Unauthorized operation";
}

View File

@ -0,0 +1,59 @@
// Copyright 2023 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Modal} from "antd";
import {ExclamationCircleFilled} from "@ant-design/icons";
import i18next from "i18next";
import * as Conf from "../Conf";
import * as Setting from "../Setting";
const {confirm} = Modal;
const {fetch: originalFetch} = window;
const demoModeCallback = (res) => {
res.json().then(data => {
if (Setting.isResponseDenied(data)) {
confirm({
title: i18next.t("general:This is a read-only demo site!"),
icon: <ExclamationCircleFilled />,
content: i18next.t("general:Go to writable demo site?"),
okText: i18next.t("general:OK"),
cancelText: i18next.t("general:Cancel"),
onOk() {
Setting.openLink(`https://demo.ai.casbin.com/${location.pathname}${location.search}?username=built-in/admin&password=123`);
},
onCancel() {},
});
}
});
};
const requestFilters = [];
const responseFilters = [];
if (Conf.IsDemoMode) {
responseFilters.push(demoModeCallback);
}
window.fetch = async(url, option = {}) => {
requestFilters.forEach(filter => filter(url, option));
return new Promise((resolve, reject) => {
originalFetch(url, option).then(res => {
// eslint-disable-next-line no-console
console.log(res.clone());
responseFilters.forEach(filter => filter(res.clone()));
resolve(res);
});
});
};

View File

@ -28,6 +28,7 @@ import * as serviceWorker from "./serviceWorker";
// import 'antd/dist/antd.min.css';
import {BrowserRouter} from "react-router-dom";
import "./i18n";
import "./backend/FetchFilter";
const container = document.getElementById("root");