From f5ceae901b70564e527ff9eee23d93458135bcdf Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Sun, 28 Mar 2021 16:35:59 +0800 Subject: [PATCH] Support auto-login. --- main.go | 1 + object/token_jwt.go | 2 +- routers/authz_filter.go | 14 +++----- routers/auto_login_filter.go | 62 ++++++++++++++++++++++++++++++++++ routers/util.go | 34 +++++++++++++++++++ web/src/App.js | 21 ++++++++++-- web/src/Setting.js | 2 +- web/src/auth/AuthBackend.js | 5 +-- web/src/backend/UserBackend.js | 2 +- 9 files changed, 125 insertions(+), 18 deletions(-) create mode 100644 routers/auto_login_filter.go create mode 100644 routers/util.go diff --git a/main.go b/main.go index 55fb7770..a7ec2bb3 100644 --- a/main.go +++ b/main.go @@ -43,6 +43,7 @@ func main() { beego.SetStaticPath("/static", "web/build/static") // https://studygolang.com/articles/2303 beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter) + beego.InsertFilter("*", beego.BeforeRouter, routers.AutoLoginFilter) beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter) beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id" diff --git a/object/token_jwt.go b/object/token_jwt.go index 989db7a8..340d8b0c 100644 --- a/object/token_jwt.go +++ b/object/token_jwt.go @@ -58,7 +58,7 @@ func generateJwtToken(application *Application, user *User) (string, error) { return token, err } -func parseJwtToken(token string) (*Claims, error) { +func ParseJwtToken(token string) (*Claims, error) { tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) { return jwtSecret, nil }) diff --git a/routers/authz_filter.go b/routers/authz_filter.go index 299c5d35..49fd4c39 100644 --- a/routers/authz_filter.go +++ b/routers/authz_filter.go @@ -61,18 +61,12 @@ func getObject(ctx *context.Context) (string, string) { method := ctx.Request.Method if method == http.MethodGet { query := ctx.Request.URL.RawQuery - - // query == "owner=admin" - if query == "" || strings.Contains(query, "=") { + // query == "?id=built-in/admin" + idParamValue := parseQuery(query, "id") + if idParamValue == "" { return "", "" } - - // query == "id=built-in/admin" - query = strings.TrimLeft(query, "id=") - tokens := strings.Split(query, "/") - owner := tokens[0] - name := tokens[1] - return owner, name + return parseSlash(idParamValue) } else { body := ctx.Input.RequestBody diff --git a/routers/auto_login_filter.go b/routers/auto_login_filter.go new file mode 100644 index 00000000..a43b99b9 --- /dev/null +++ b/routers/auto_login_filter.go @@ -0,0 +1,62 @@ +// Copyright 2021 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. + +package routers + +import ( + "fmt" + + "github.com/astaxie/beego/context" + "github.com/casdoor/casdoor/object" +) + +func getSessionUser(ctx *context.Context) string { + user := ctx.Input.CruSession.Get("username") + if user == nil { + return "" + } + + return user.(string) +} + +func setSessionUser(ctx *context.Context, user string) { + err := ctx.Input.CruSession.Set("username", user) + if err != nil { + panic(err) + } + + // https://github.com/beego/beego/issues/3445#issuecomment-455411915 + ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter) +} + +func AutoLoginFilter(ctx *context.Context) { + query := ctx.Request.URL.RawQuery + // query == "?access_token=123" + accessToken := parseQuery(query, "accessToken") + if accessToken == "" { + return + } + + if getSessionUser(ctx) != "" { + return + } + + claims, err := object.ParseJwtToken(accessToken) + if err != nil { + panic(err) + } + + userId := fmt.Sprintf("%s/%s", claims.Organization, claims.Username) + setSessionUser(ctx, userId) +} diff --git a/routers/util.go b/routers/util.go new file mode 100644 index 00000000..9fb79a34 --- /dev/null +++ b/routers/util.go @@ -0,0 +1,34 @@ +// Copyright 2021 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. + +package routers + +import ( + "net/url" + "strings" +) + +func parseQuery(query string, key string) string { + valueMap, err := url.ParseQuery(query) + if err != nil { + panic(err) + } + + return valueMap.Get(key) +} + +func parseSlash(s string) (string, string) { + tokens := strings.Split(s, "/") + return tokens[0], tokens[1] +} diff --git a/web/src/App.js b/web/src/App.js index 47c02be9..aa815169 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -17,7 +17,7 @@ import './App.less'; import * as Setting from "./Setting"; import {DownOutlined, LogoutOutlined, SettingOutlined} from '@ant-design/icons'; import {Avatar, BackTop, Dropdown, Layout, Menu} from 'antd'; -import {Switch, Route, withRouter, Redirect, Link} from 'react-router-dom' +import {Link, Redirect, Route, Switch, withRouter} from 'react-router-dom' import OrganizationListPage from "./OrganizationListPage"; import OrganizationEditPage from "./OrganizationEditPage"; import UserListPage from "./UserListPage"; @@ -105,11 +105,26 @@ class App extends Component { } } + getAccessTokenParam() { + // "/page?access_token=123" + const params = new URLSearchParams(this.props.location.search); + return params.get("access_token"); + } + + getUrlWithoutQuery() { + // eslint-disable-next-line no-restricted-globals + return location.toString().replace(location.search, ""); + } + getAccount() { - AuthBackend.getAccount() + const accessToken = this.getAccessTokenParam(); + if (accessToken !== null) { + window.history.replaceState({}, document.title, this.getUrlWithoutQuery()); + } + AuthBackend.getAccount(accessToken) .then((res) => { this.setState({ - account: res.data, + account: res.status === "ok" ? res.data : null, }); }); } diff --git a/web/src/Setting.js b/web/src/Setting.js index 6ae43808..4cc080e2 100644 --- a/web/src/Setting.js +++ b/web/src/Setting.js @@ -66,7 +66,7 @@ export function showMessage(type, text) { } export function isAdminUser(account) { - if (account === null) { + if (account === undefined || account === null) { return false; } return account.owner === "built-in" || account.isGlobalAdmin === true; diff --git a/web/src/auth/AuthBackend.js b/web/src/auth/AuthBackend.js index 6195855f..0c8afe49 100644 --- a/web/src/auth/AuthBackend.js +++ b/web/src/auth/AuthBackend.js @@ -14,8 +14,9 @@ import {authConfig} from "./Auth"; -export function getAccount() { - return fetch(`${authConfig.serverUrl}/api/get-account`, { +export function getAccount(accessToken) { + let param = (accessToken === null) ? "" : `?accessToken=${accessToken}`; + return fetch(`${authConfig.serverUrl}/api/get-account${param}`, { method: 'GET', credentials: 'include' }).then(res => res.json()); diff --git a/web/src/backend/UserBackend.js b/web/src/backend/UserBackend.js index 746e823c..459661f6 100644 --- a/web/src/backend/UserBackend.js +++ b/web/src/backend/UserBackend.js @@ -65,7 +65,7 @@ export function deleteUser(user) { export function uploadAvatar(avatar) { let account; - AuthBackend.getAccount().then((res) => { + AuthBackend.getAccount(null).then((res) => { account = Setting.parseJson(res.data); let formData = new FormData(); formData.append("avatarfile", avatar);