mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-16 10:43:35 +08:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
38b9ad1d9f | |||
5a92411006 | |||
52eaf6c822 | |||
cc84709151 | |||
22fca78be9 | |||
effd257040 |
@ -59,7 +59,15 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
}
|
||||
|
||||
bindPassword := string(r.AuthenticationSimple())
|
||||
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en")
|
||||
|
||||
enableCaptcha := false
|
||||
isSigninViaLdap := false
|
||||
isPasswordWithLdapEnabled := false
|
||||
if bindPassword != "" {
|
||||
isPasswordWithLdapEnabled = true
|
||||
}
|
||||
|
||||
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en", enableCaptcha, isSigninViaLdap, isPasswordWithLdapEnabled)
|
||||
if err != nil {
|
||||
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
|
||||
res.SetResultCode(ldap.LDAPResultInvalidCredentials)
|
||||
@ -122,6 +130,9 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
e.AddAttribute("homeDirectory", message.AttributeValue("/home/"+user.Name))
|
||||
e.AddAttribute("cn", message.AttributeValue(user.Name))
|
||||
e.AddAttribute("uid", message.AttributeValue(user.Id))
|
||||
for _, group := range user.Groups {
|
||||
e.AddAttribute(ldapMemberOfAttr, message.AttributeValue(group))
|
||||
}
|
||||
attrs := r.Attributes()
|
||||
for _, attr := range attrs {
|
||||
if string(attr) == "*" {
|
||||
|
21
ldap/util.go
21
ldap/util.go
@ -79,6 +79,8 @@ var ldapAttributesMapping = map[string]FieldRelation{
|
||||
},
|
||||
}
|
||||
|
||||
const ldapMemberOfAttr = "memberOf"
|
||||
|
||||
var AdditionalLdapAttributes []message.LDAPString
|
||||
|
||||
func init() {
|
||||
@ -180,7 +182,22 @@ func buildUserFilterCondition(filter interface{}) (builder.Cond, error) {
|
||||
}
|
||||
return builder.Not{cond}, nil
|
||||
case message.FilterEqualityMatch:
|
||||
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
||||
attr := string(f.AttributeDesc())
|
||||
|
||||
if attr == ldapMemberOfAttr {
|
||||
groupId := string(f.AssertionValue())
|
||||
users, err := object.GetGroupUsers(groupId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var names []string
|
||||
for _, user := range users {
|
||||
names = append(names, user.Name)
|
||||
}
|
||||
return builder.In("name", names), nil
|
||||
}
|
||||
|
||||
field, err := getUserFieldFromAttribute(attr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -246,7 +263,7 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
||||
return nil, code
|
||||
}
|
||||
|
||||
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
|
||||
if name == "*" { // get all users from organization 'org'
|
||||
if m.Client.IsGlobalAdmin && org == "*" {
|
||||
filteredUsers, err = object.GetGlobalUsersWithFilter(buildSafeCondition(r.Filter()))
|
||||
if err != nil {
|
||||
|
@ -78,6 +78,7 @@ func getBuiltInAccountItems() []*AccountItem {
|
||||
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "MFA accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,6 +204,7 @@ type User struct {
|
||||
SigninWrongTimes int `json:"signinWrongTimes"`
|
||||
|
||||
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
||||
MfaAccounts []MfaAccount `xorm:"mfaAccounts blob" json:"mfaAccounts"`
|
||||
NeedUpdatePassword bool `json:"needUpdatePassword"`
|
||||
}
|
||||
|
||||
@ -230,6 +231,12 @@ type ManagedAccount struct {
|
||||
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
|
||||
}
|
||||
|
||||
type MfaAccount struct {
|
||||
AccountName string `xorm:"varchar(100)" json:"accountName"`
|
||||
Issuer string `xorm:"varchar(100)" json:"issuer"`
|
||||
SecretKey string `xorm:"varchar(100)" json:"secretKey"`
|
||||
}
|
||||
|
||||
type FaceId struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
FaceIdData []float64 `json:"faceIdData"`
|
||||
@ -603,6 +610,12 @@ func GetMaskedUser(user *User, isAdminOrSelf bool, errs ...error) (*User, error)
|
||||
}
|
||||
}
|
||||
|
||||
if user.MfaAccounts != nil {
|
||||
for _, mfaAccount := range user.MfaAccounts {
|
||||
mfaAccount.SecretKey = "***"
|
||||
}
|
||||
}
|
||||
|
||||
if user.TotpSecret != "" {
|
||||
user.TotpSecret = ""
|
||||
}
|
||||
@ -675,7 +688,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
||||
columns = []string{
|
||||
"owner", "display_name", "avatar", "first_name", "last_name",
|
||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
||||
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids",
|
||||
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
|
||||
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
|
||||
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
||||
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
|
||||
|
@ -252,8 +252,8 @@ class AdapterEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("provider:DB test"), i18next.t("provider:DB test - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={2} >
|
||||
<Button type={"primary"} onClick={() => {
|
||||
AdapterBackend.getPolicies("", "", `${this.state.organizationName}/${this.state.adapterName}`)
|
||||
<Button disabled={this.state.organizationName !== this.state.adapter.owner} type={"primary"} onClick={() => {
|
||||
AdapterBackend.getPolicies("", "", `${this.state.adapter.owner}/${this.state.adapter.name}`)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("syncer:Connect successfully"));
|
||||
@ -279,13 +279,14 @@ class AdapterEditPage extends React.Component {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||
this.setState({
|
||||
organizationName: this.state.adapter.owner,
|
||||
adapterName: this.state.adapter.name,
|
||||
});
|
||||
|
||||
if (exitAfterSave) {
|
||||
this.props.history.push("/adapters");
|
||||
} else {
|
||||
this.props.history.push(`/adapters/${this.state.organizationName}/${this.state.adapter.name}`);
|
||||
this.props.history.push(`/adapters/${this.state.adapter.owner}/${this.state.adapter.name}`);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||
|
@ -88,6 +88,7 @@ class OrganizationListPage extends BaseListPage {
|
||||
{Name: "Multi-factor authentication", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
{Name: "MFA accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ import {CheckCircleOutlined, HolderOutlined, UsergroupAddOutlined} from "@ant-de
|
||||
import * as MfaBackend from "./backend/MfaBackend";
|
||||
import AccountAvatar from "./account/AccountAvatar";
|
||||
import FaceIdTable from "./table/FaceIdTable";
|
||||
import MfaAccountTable from "./table/MfaAccountTable";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
@ -1039,6 +1040,21 @@ class UserEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "MFA accounts") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:MFA accounts"), i18next.t("user:MFA accounts"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<MfaAccountTable
|
||||
title={i18next.t("user:MFA accounts")}
|
||||
table={this.state.user.mfaAccounts}
|
||||
onUpdateTable={(table) => {this.updateUserField("mfaAccounts", table);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Need update password") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
|
@ -167,6 +167,9 @@ class WebhookEditPage extends React.Component {
|
||||
["add", "update", "delete"].forEach(action => {
|
||||
res.push(`${action}-${obj}`);
|
||||
});
|
||||
if (obj === "payment") {
|
||||
res.push("invoice-payment", "notify-payment");
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
@ -108,6 +108,7 @@ class AccountTable extends React.Component {
|
||||
{name: "WebAuthn credentials", label: i18next.t("user:WebAuthn credentials")},
|
||||
{name: "Managed accounts", label: i18next.t("user:Managed accounts")},
|
||||
{name: "Face ID", label: i18next.t("user:Face ID")},
|
||||
{name: "MFA accounts", label: i18next.t("user:MFA accounts")},
|
||||
];
|
||||
};
|
||||
|
||||
|
182
web/src/table/MfaAccountTable.js
Normal file
182
web/src/table/MfaAccountTable.js
Normal file
@ -0,0 +1,182 @@
|
||||
// Copyright 2024 The Casdoor 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 React from "react";
|
||||
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
||||
import {Button, Col, Image, Input, Row, Table, Tooltip} from "antd";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
|
||||
class MfaAccountTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
mfaAccounts: this.props.table !== null ? this.props.table.map((item, index) => {
|
||||
item.key = index;
|
||||
return item;
|
||||
}) : [],
|
||||
};
|
||||
}
|
||||
|
||||
count = this.props.table?.length ?? 0;
|
||||
|
||||
updateTable(table) {
|
||||
this.setState({
|
||||
mfaAccounts: table,
|
||||
});
|
||||
|
||||
this.props.onUpdateTable([...table].map((item) => {
|
||||
const newItem = Setting.deepCopy(item);
|
||||
delete newItem.key;
|
||||
return newItem;
|
||||
}));
|
||||
}
|
||||
|
||||
updateField(table, index, key, value) {
|
||||
table[index][key] = value;
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
addRow(table) {
|
||||
const row = {key: this.count, accountName: "", issuer: "", secretKey: ""};
|
||||
if (table === undefined || table === null) {
|
||||
table = [];
|
||||
}
|
||||
|
||||
this.count += 1;
|
||||
table = Setting.addRow(table, row);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
deleteRow(table, i) {
|
||||
table = Setting.deleteRow(table, i);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
upRow(table, i) {
|
||||
table = Setting.swapRow(table, i - 1, i);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
downRow(table, i) {
|
||||
table = Setting.swapRow(table, i, i + 1);
|
||||
this.updateTable(table);
|
||||
}
|
||||
|
||||
renderTable(table) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("mfaAccount:Account Name"),
|
||||
dataIndex: "accountName",
|
||||
key: "accountName",
|
||||
width: "400px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
this.updateField(table, index, "accountName", e.target.value);
|
||||
}} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("mfaAccount:Issuer"),
|
||||
dataIndex: "issuer",
|
||||
key: "issuer",
|
||||
width: "300px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input value={text} onChange={e => {
|
||||
this.updateField(table, index, "issuer", e.target.value);
|
||||
}} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("mfaAccount:Secret Key"),
|
||||
dataIndex: "secretKey",
|
||||
key: "secretKey",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Input.Password value={text} onChange={e => {
|
||||
this.updateField(table, index, "secretKey", e.target.value);
|
||||
}} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Logo"),
|
||||
dataIndex: "issuer",
|
||||
key: "logo",
|
||||
width: "60px",
|
||||
render: (text, record, index) => (
|
||||
<Tooltip>
|
||||
{text ? (
|
||||
<Image width={36} height={36} preview={false} src={`https://cdn.casbin.org/img/social_${text.toLowerCase()}.png`}
|
||||
fallback="https://cdn.casbin.org/img/social_default.png" alt={text} />
|
||||
) : (
|
||||
<Image width={36} height={36} preview={false} src={"https://cdn.casbin.org/img/social_default.png"} alt="default" />
|
||||
)}
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
key: "action",
|
||||
width: "100px",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Tooltip placement="bottomLeft" title={i18next.t("general:Up")}>
|
||||
<Button style={{marginRight: "5px"}} disabled={index === 0} icon={<UpOutlined />} size="small" onClick={() => this.upRow(table, index)} />
|
||||
</Tooltip>
|
||||
<Tooltip placement="topLeft" title={i18next.t("general:Down")}>
|
||||
<Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />
|
||||
</Tooltip>
|
||||
<Tooltip placement="topLeft" title={i18next.t("general:Delete")}>
|
||||
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Table scroll={{x: "max-content"}} rowKey="key" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
||||
title={() => (
|
||||
<div>
|
||||
{this.props.title}
|
||||
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col span={24}>
|
||||
{
|
||||
this.renderTable(this.state.mfaAccounts)
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MfaAccountTable;
|
Reference in New Issue
Block a user