Skip to content

Commit 4300007

Browse files
authored
feat: demo mode (#616)
1 parent d940bab commit 4300007

8 files changed

Lines changed: 127 additions & 0 deletions

File tree

conf/app.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ driverName = mysql
77
dataSourceName = root:123@tcp(localhost:3306)/
88
dbName = casibase
99
redisEndpoint =
10+
isDemoMode = false
1011
landingFolder = casibase-landing
1112
casdoorEndpoint = http://localhost:8000
1213
clientId = af6b5aa958822fb9dc33

controllers/util.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package controllers
1616

17+
import "github.com/astaxie/beego/context"
18+
1719
type Response struct {
1820
Status string `json:"status"`
1921
Msg string `json:"msg"`
@@ -65,3 +67,23 @@ func (c *ApiController) RequireAdmin() bool {
6567

6668
return false
6769
}
70+
71+
func DenyRequest(ctx *context.Context) {
72+
responseError(ctx, "Unauthorized operation")
73+
}
74+
75+
func responseError(ctx *context.Context, error string, data ...interface{}) {
76+
resp := Response{Status: "error", Msg: error}
77+
switch len(data) {
78+
case 2:
79+
resp.Data2 = data[1]
80+
fallthrough
81+
case 1:
82+
resp.Data = data[0]
83+
}
84+
85+
err := ctx.Output.JSON(resp, true, false)
86+
if err != nil {
87+
panic(err)
88+
}
89+
}

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func main() {
4040
// https://studygolang.com/articles/2303
4141
beego.InsertFilter("/", beego.BeforeRouter, routers.TransparentStatic) // must has this for default page
4242
beego.InsertFilter("/*", beego.BeforeRouter, routers.TransparentStatic)
43+
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
4344

4445
if beego.AppConfig.String("redisEndpoint") == "" {
4546
beego.BConfig.WebConfig.Session.SessionProvider = "file"

routers/filter.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ import (
1919
"net/http"
2020
"strings"
2121

22+
"github.com/casbin/casibase/conf"
23+
"github.com/casbin/casibase/controllers"
24+
2225
"github.com/astaxie/beego"
2326
"github.com/astaxie/beego/context"
2427
"github.com/casbin/casibase/util"
@@ -57,3 +60,38 @@ func TransparentStatic(ctx *context.Context) {
5760
http.ServeFile(ctx.ResponseWriter, ctx.Request, "web/build/index.html")
5861
}
5962
}
63+
64+
func ApiFilter(ctx *context.Context) {
65+
method := ctx.Request.Method
66+
urlPath := getUrlPath(ctx.Request.URL.Path)
67+
68+
if conf.IsDemoMode() {
69+
if !isAllowedInDemoMode(method, urlPath) {
70+
controllers.DenyRequest(ctx)
71+
}
72+
}
73+
}
74+
75+
func getUrlPath(urlPath string) string {
76+
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")) {
77+
return "/cas"
78+
}
79+
80+
if strings.HasPrefix(urlPath, "/api/login/oauth") {
81+
return "/api/login/oauth"
82+
}
83+
84+
if strings.HasPrefix(urlPath, "/api/webauthn") {
85+
return "/api/webauthn"
86+
}
87+
88+
return urlPath
89+
}
90+
91+
func isAllowedInDemoMode(method string, urlPath string) bool {
92+
if method == "POST" && !(strings.HasPrefix(urlPath, "/api/signin") || urlPath == "/api/signout") {
93+
return false
94+
}
95+
96+
return true
97+
}

web/src/Conf.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const ForceLanguage = "";
3030
export const DefaultLanguage = "en";
3131

3232
export const AppUrl = "";
33+
export const IsDemoMode = false;
3334

3435
export const ThemeDefault = {
3536
themeType: "default",

web/src/Setting.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,3 +648,7 @@ export function renderExternalLink() {
648648
</svg>
649649
);
650650
}
651+
652+
export function isResponseDenied(data) {
653+
return data.msg === "Unauthorized operation";
654+
}

web/src/backend/FetchFilter.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2023 The casbin Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {Modal} from "antd";
16+
import {ExclamationCircleFilled} from "@ant-design/icons";
17+
import i18next from "i18next";
18+
import * as Conf from "../Conf";
19+
import * as Setting from "../Setting";
20+
21+
const {confirm} = Modal;
22+
const {fetch: originalFetch} = window;
23+
24+
const demoModeCallback = (res) => {
25+
res.json().then(data => {
26+
if (Setting.isResponseDenied(data)) {
27+
confirm({
28+
title: i18next.t("general:This is a read-only demo site!"),
29+
icon: <ExclamationCircleFilled />,
30+
content: i18next.t("general:Go to writable demo site?"),
31+
okText: i18next.t("general:OK"),
32+
cancelText: i18next.t("general:Cancel"),
33+
onOk() {
34+
Setting.openLink(`https://demo.ai.casbin.com/${location.pathname}${location.search}?username=built-in/admin&password=123`);
35+
},
36+
onCancel() {},
37+
});
38+
}
39+
});
40+
};
41+
42+
const requestFilters = [];
43+
const responseFilters = [];
44+
45+
if (Conf.IsDemoMode) {
46+
responseFilters.push(demoModeCallback);
47+
}
48+
49+
window.fetch = async(url, option = {}) => {
50+
requestFilters.forEach(filter => filter(url, option));
51+
return new Promise((resolve, reject) => {
52+
originalFetch(url, option).then(res => {
53+
// eslint-disable-next-line no-console
54+
console.log(res.clone());
55+
responseFilters.forEach(filter => filter(res.clone()));
56+
resolve(res);
57+
});
58+
});
59+
};

web/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import * as serviceWorker from "./serviceWorker";
2828
// import 'antd/dist/antd.min.css';
2929
import {BrowserRouter} from "react-router-dom";
3030
import "./i18n";
31+
import "./backend/FetchFilter";
3132

3233
const container = document.getElementById("root");
3334

0 commit comments

Comments
 (0)