Compare commits

...

10 Commits

Author SHA1 Message Date
146aec9ee8 feat: skip username restriction for new users coming from OAuth providers. (#1225) 2022-10-17 18:01:01 +08:00
50a52de856 feat: support database version control (#1221)
* feat: support Database version control

* Update adapter.go

* fix review problems

* Update adapter.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-10-15 17:20:20 +08:00
8f7a8d7d4f fix: translation without reloading (#1215)
* fix: translation without reloading

* fix: language switch
2022-10-12 19:52:02 +08:00
23f3fe1e3c feat: update code format (#1214)
* feat: doc

* feat: doc

* Update model.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-10-12 11:42:14 +08:00
59ff5e02ab fix: Add support for including underscores for username (#1210)
* fix: Add support for including underscores for username

* Update check.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-10-11 19:39:19 +08:00
8d41508d6b fix: center loading in account page (#1209)
* fix: center loading in account page

* Update UserEditPage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-10-11 00:52:08 +08:00
04f70cf012 Improve renderRightDropdown() 2022-10-10 22:53:47 +08:00
83724c73f9 feat: fix pad and mobile views (#1202)
* fix figure width

* fix: pad resolution menu

* feat: drawer style mobile menu

* fix: menu button i18n
2022-10-10 22:37:25 +08:00
33e419e133 Show more items to org admin 2022-10-10 21:58:17 +08:00
b832c304ae Can get owner in getObject() 2022-10-10 20:56:55 +08:00
21 changed files with 113 additions and 56 deletions

View File

@ -203,12 +203,6 @@ func (c *ApiController) Signup() {
}
}
msg = object.CheckUsername(user.Name)
if msg != "" {
c.ResponseError(msg)
return
}
affected := object.AddUser(user)
if !affected {
c.ResponseError(fmt.Sprintf("Failed to create user, user information is invalid: %s", util.StructToJson(user)))

View File

@ -411,12 +411,6 @@ func (c *ApiController) Login() {
// sync info from 3rd-party if possible
object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
msg := object.CheckUsername(user.Name)
if msg != "" {
c.ResponseError(msg)
return
}
affected := object.AddUser(user)
if !affected {
c.ResponseError(fmt.Sprintf("Failed to create user, user information is invalid: %s", util.StructToJson(user)))

View File

@ -19,11 +19,13 @@ import (
"runtime"
"github.com/beego/beego"
xormadapter "github.com/casbin/xorm-adapter/v3"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
_ "github.com/denisenkom/go-mssqldb" // db = mssql
_ "github.com/go-sql-driver/mysql" // db = mysql
_ "github.com/lib/pq" // db = postgres
"xorm.io/xorm/migrate"
//_ "github.com/mattn/go-sqlite3" // db = sqlite3
"xorm.io/core"
"xorm.io/xorm"
@ -40,6 +42,7 @@ func InitConfig() {
beego.BConfig.WebConfig.Session.SessionOn = true
InitAdapter(true)
initMigrations()
}
func InitAdapter(createDatabase bool) {
@ -214,6 +217,11 @@ func (a *Adapter) createTable() {
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(xormadapter.CasbinRule))
if err != nil {
panic(err)
}
}
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
@ -239,3 +247,22 @@ func GetSession(owner string, offset, limit int, field, value, sortField, sortOr
}
return session
}
func initMigrations() {
migrations := []*migrate.Migration{
{
ID: "20221015CasbinRule--fill ptype field with p",
Migrate: func(tx *xorm.Engine) error {
_, err := tx.Cols("ptype").Update(&xormadapter.CasbinRule{
Ptype: "p",
})
return err
},
Rollback: func(tx *xorm.Engine) error {
return tx.DropTables(&xormadapter.CasbinRule{})
},
},
}
m := migrate.New(adapter.Engine, migrate.DefaultOptions, migrations)
m.Migrate()
}

View File

@ -59,6 +59,11 @@ func CheckUserSignup(application *Application, organization *Organization, usern
if reWhiteSpace.MatchString(username) {
return "username cannot contain white spaces"
}
msg := CheckUsername(username)
if msg != "" {
return msg
}
if HasUserByField(organization.Name, "name", username) {
return "username already exists"
}
@ -314,19 +319,17 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
return allowed, err
}
func CheckUsername(name string) string {
if name == "" {
func CheckUsername(username string) string {
if username == "" {
return "Empty username."
} else if len(name) > 39 {
} else if len(username) > 39 {
return "Username is too long (maximum is 39 characters)."
}
// https://stackoverflow.com/questions/58726546/github-username-convention-using-regex
re, _ := regexp.Compile("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$")
if !re.MatchString(name) {
return fmt.Sprintf("The name '%s' may only contain alphanumeric characters or hyphens, "+
"cannot have multiple consecutive hyphens, "+
"and cannot begin or end with a hyphen.", name)
re, _ := regexp.Compile("^[a-zA-Z0-9]+((?:-[a-zA-Z0-9]+)|(?:_[a-zA-Z0-9]+))*$")
if !re.MatchString(username) {
return "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline."
}
return ""

View File

@ -159,7 +159,7 @@ func DisableVerificationCode(dest string) {
}
}
// from Casnode/object/validateCode.go line 116
// From Casnode/object/validateCode.go line 116
var stdNums = []byte("0123456789")
func getRandomCode(length int) string {

View File

@ -63,11 +63,16 @@ func getObject(ctx *context.Context) (string, string) {
if method == http.MethodGet {
// query == "?id=built-in/admin"
id := ctx.Input.Query("id")
if id == "" {
return "", ""
if id != "" {
return util.GetOwnerAndNameFromId(id)
}
return util.GetOwnerAndNameFromId(id)
owner := ctx.Input.Query("owner")
if owner != "" {
return owner, ""
}
return "", ""
} else {
body := ctx.Input.RequestBody

View File

@ -16,8 +16,8 @@ import React, {Component} from "react";
import "./App.less";
import {Helmet} from "react-helmet";
import * as Setting from "./Setting";
import {DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Avatar, BackTop, Button, Card, Dropdown, Layout, Menu, Result} from "antd";
import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Avatar, BackTop, Button, Card, Drawer, Dropdown, Layout, Menu, Result} from "antd";
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import OrganizationListPage from "./OrganizationListPage";
import OrganizationEditPage from "./OrganizationEditPage";
@ -74,6 +74,7 @@ import ModelEditPage from "./ModelEditPage";
import SystemInfo from "./SystemInfo";
import AdapterListPage from "./AdapterListPage";
import AdapterEditPage from "./AdapterEditPage";
import {withTranslation} from "react-i18next";
const {Header, Footer} = Layout;
@ -85,6 +86,7 @@ class App extends Component {
selectedMenuKey: 0,
account: undefined,
uri: null,
menuVisible: false,
};
Setting.initServerUrl();
@ -298,12 +300,12 @@ class App extends Component {
<Menu onClick={this.handleRightDropdownClick.bind(this)}>
<Menu.Item key="/account">
<SettingOutlined />
&nbsp;
&nbsp;&nbsp;
{i18next.t("account:My Account")}
</Menu.Item>
<Menu.Item key="/logout">
<LogoutOutlined />
&nbsp;
&nbsp;&nbsp;
{i18next.t("account:Logout")}
</Menu.Item>
</Menu>
@ -388,9 +390,6 @@ class App extends Component {
</Link>
</Menu.Item>
);
}
if (Setting.isAdminUser(this.state.account)) {
res.push(
<Menu.Item key="/roles">
<Link to="/roles">
@ -598,6 +597,18 @@ class App extends Component {
);
}
onClose = () => {
this.setState({
menuVisible: false,
});
};
showMenu = () => {
this.setState({
menuVisible: true,
});
};
renderContent() {
if (!Setting.isMobile()) {
return (
@ -616,7 +627,7 @@ class App extends Component {
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: "64px", width: "78%", position: "absolute", left: "145px"}}
style={{lineHeight: "64px", position: "absolute", left: "145px", right: "200px"}}
>
{
this.renderMenu()
@ -649,22 +660,28 @@ class App extends Component {
</Link>
)
}
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: "64px"}}
>
{
this.renderMenu()
}
<div style = {{float: "right"}}>
<Drawer title={i18next.t("general:Close")} placement="left" visible={this.state.menuVisible} onClose={this.onClose}>
<Menu
// theme="dark"
mode={(Setting.isMobile()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: "64px"}}
onClick={this.onClose}
>
{
this.renderAccount()
this.renderMenu()
}
<SelectLanguageBox />
</div>
</Menu>
</Menu>
</Drawer>
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
{i18next.t("general:Menu")}
</Button>
<div style = {{float: "right"}}>
{
this.renderAccount()
}
<SelectLanguageBox />
</div>
</Header>
{
this.renderRouter()
@ -782,4 +799,4 @@ class App extends Component {
}
}
export default withRouter(App);
export default withRouter(withTranslation()(App));

View File

@ -341,7 +341,7 @@ class PermissionListPage extends BaseListPage {
this.setState({loading: true});
const getPermissions = Setting.isLocalAdminUser(this.props.account) ? PermissionBackend.getPermissions : PermissionBackend.getPermissionsBySubmitter;
getPermissions("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
getPermissions(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@ -25,7 +25,7 @@ class RoleListPage extends BaseListPage {
newRole() {
const randomName = Setting.getRandomName();
return {
owner: "built-in",
owner: this.props.account.owner,
name: `role_${randomName}`,
createdTime: moment().format(),
displayName: `New Role - ${randomName}`,
@ -211,7 +211,7 @@ class RoleListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
RoleBackend.getRoles("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
RoleBackend.getRoles(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@ -554,7 +554,7 @@ export function changeLanguage(language) {
localStorage.setItem("language", language);
changeMomentLanguage(language);
i18next.changeLanguage(language);
window.location.reload(true);
// window.location.reload(true);
}
export function changeMomentLanguage(language) {

View File

@ -661,7 +661,7 @@ class UserEditPage extends React.Component {
return (
<div>
{
this.state.loading ? <Spin size="large" /> : (
this.state.loading ? <Spin size="large" style={{marginLeft: "50%", marginTop: "10%"}} /> : (
this.state.user !== null ? this.renderUser() :
<Result
status="404"

View File

@ -29,6 +29,7 @@ import i18next from "i18next";
import CustomGithubCorner from "../CustomGithubCorner";
import {CountDownInput} from "../common/CountDownInput";
import SelectLanguageBox from "../SelectLanguageBox";
import {withTranslation} from "react-i18next";
const {TabPane} = Tabs;
@ -730,4 +731,4 @@ class LoginPage extends React.Component {
}
}
export default LoginPage;
export default withTranslation()(LoginPage);

View File

@ -44,10 +44,11 @@ class SingleCard extends React.Component {
return (
<Card.Grid style={gridStyle} onClick={() => Setting.goToLinkSoft(this, silentSigninLink)}>
<img src={logo} alt="logo" height={60} style={{marginBottom: "20px"}} />
<img src={logo} alt="logo" width={"100%"} style={{marginBottom: "20px"}} />
<Meta
title={title}
description={desc}
style={{justifyContent: "center"}}
/>
</Card.Grid>
);
@ -61,7 +62,7 @@ class SingleCard extends React.Component {
<Card
hoverable
cover={
<img alt="logo" src={logo} style={{width: "100%", height: "210px", objectFit: "scale-down"}} />
<img alt="logo" src={logo} style={{width: "100%", objectFit: "scale-down"}} />
}
onClick={() => Setting.goToLinkSoft(this, silentSigninLink)}
style={isSingle ? {width: "320px"} : {width: "100%"}}

View File

@ -23,6 +23,7 @@ import ja from "./locales/ja/data.json";
import es from "./locales/es/data.json";
import * as Conf from "./Conf";
import * as Setting from "./Setting";
import {initReactI18next} from "react-i18next";
const resources = {
en: en,
@ -80,7 +81,7 @@ function initLanguage() {
return language;
}
i18n.init({
i18n.use(initReactI18next).init({
lng: initLanguage(),
resources: resources,

View File

@ -133,6 +133,7 @@
"Certs": "Certs",
"Click to Upload": "Click to Upload",
"Client IP": "Client-IP",
"Close": "Close",
"Created time": "Erstellte Zeit",
"Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip",
@ -165,6 +166,7 @@
"Logo - Tooltip": "App's image tag",
"Master password": "Master-Passwort",
"Master password - Tooltip": "Masterpasswort - Tooltip",
"Menu": "Menu",
"Method": "Methode",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",

View File

@ -133,6 +133,7 @@
"Certs": "Certs",
"Click to Upload": "Click to Upload",
"Client IP": "Client IP",
"Close": "Close",
"Created time": "Created time",
"Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip",
@ -165,6 +166,7 @@
"Logo - Tooltip": "Logo - Tooltip",
"Master password": "Master password",
"Master password - Tooltip": "Master password - Tooltip",
"Menu": "Menu",
"Method": "Method",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",

View File

@ -133,6 +133,7 @@
"Certs": "Certes",
"Click to Upload": "Click to Upload",
"Client IP": "IP du client",
"Close": "Close",
"Created time": "Date de création",
"Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip",
@ -165,6 +166,7 @@
"Logo - Tooltip": "App's image tag",
"Master password": "Mot de passe maître",
"Master password - Tooltip": "Mot de passe maître - Infobulle",
"Menu": "Menu",
"Method": "Méthode",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",

View File

@ -133,6 +133,7 @@
"Certs": "Certs",
"Click to Upload": "Click to Upload",
"Client IP": "クライアント IP",
"Close": "Close",
"Created time": "作成日時",
"Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip",
@ -165,6 +166,7 @@
"Logo - Tooltip": "App's image tag",
"Master password": "マスターパスワード",
"Master password - Tooltip": "マスターパスワード - ツールチップ",
"Menu": "Menu",
"Method": "方法",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",

View File

@ -133,6 +133,7 @@
"Certs": "Certs",
"Click to Upload": "Click to Upload",
"Client IP": "Client IP",
"Close": "Close",
"Created time": "Created time",
"Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip",
@ -165,6 +166,7 @@
"Logo - Tooltip": "App's image tag",
"Master password": "Master password",
"Master password - Tooltip": "Master password - Tooltip",
"Menu": "Menu",
"Method": "Method",
"Model": "Model",
"Model - Tooltip": "Model - Tooltip",

View File

@ -133,6 +133,7 @@
"Certs": "Сертификаты",
"Click to Upload": "Нажмите здесь, чтобы загрузить",
"Client IP": "IP клиента",
"Close": "Close",
"Created time": "Время создания",
"Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip",
@ -165,6 +166,7 @@
"Logo - Tooltip": "App's image tag",
"Master password": "Мастер-пароль",
"Master password - Tooltip": "Мастер-пароль - Tooltip",
"Menu": "Menu",
"Method": "Метод",
"Model": "Модель",
"Model - Tooltip": "Модель - Подсказка",

View File

@ -133,6 +133,7 @@
"Certs": "证书",
"Click to Upload": "点击上传",
"Client IP": "客户端IP",
"Close": "关闭",
"Created time": "创建时间",
"Default application": "默认应用",
"Default application - Tooltip": "默认应用",
@ -165,6 +166,7 @@
"Logo - Tooltip": "应用程序向外展示的图标",
"Master password": "万能密码",
"Master password - Tooltip": "可用来登录该组织下的所有用户,方便管理员以该用户身份登录,以解决技术问题",
"Menu": "目录",
"Method": "方法",
"Model": "模型",
"Model - Tooltip": "Casbin模型",