Compare commits

...

5 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
5d37087685 Fix backward compatibility for getDbSyncerForUser
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
2026-02-11 07:39:53 +00:00
copilot-swe-agent[bot]
5cd1d27fa9 Add UI support for DingTalk name mapping configuration
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
2026-02-11 07:35:56 +00:00
copilot-swe-agent[bot]
6273497ec2 Fix hash calculation for API-based syncers including DingTalk
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
2026-02-11 07:34:49 +00:00
copilot-swe-agent[bot]
56362d2416 Add configurable name mapping for DingTalk syncer
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
2026-02-11 07:33:29 +00:00
copilot-swe-agent[bot]
1fb58bccab Initial plan 2026-02-11 07:28:46 +00:00
6 changed files with 71 additions and 5 deletions

View File

@@ -61,6 +61,7 @@ type Syncer struct {
SyncInterval int `json:"syncInterval"`
IsReadOnly bool `json:"isReadOnly"`
IsEnabled bool `json:"isEnabled"`
NameMapping string `xorm:"varchar(100)" json:"nameMapping"`
Ormer *Ormer `xorm:"-" json:"-"`
SshClient *ssh.Client `xorm:"-" json:"-"`

View File

@@ -408,11 +408,37 @@ func (p *DingtalkSyncerProvider) getDingtalkUsers() ([]*OriginalUser, error) {
// dingtalkUserToOriginalUser converts DingTalk user to Casdoor OriginalUser
func (p *DingtalkSyncerProvider) dingtalkUserToOriginalUser(dingtalkUser *DingtalkUser) *OriginalUser {
// Use unionid as name to be consistent with OAuth provider
// Fallback to userId if unionid is not available
userName := dingtalkUser.UserId
if dingtalkUser.UnionId != "" {
// Determine the userName based on the NameMapping configuration
// Default behavior (for backward compatibility): unionid, fallback to userId
var userName string
switch p.Syncer.NameMapping {
case "userid":
userName = dingtalkUser.UserId
case "email":
userName = dingtalkUser.Email
if userName == "" {
// Fallback to userId if email is empty
userName = dingtalkUser.UserId
}
case "mobile":
userName = dingtalkUser.Mobile
if userName == "" {
// Fallback to userId if mobile is empty
userName = dingtalkUser.UserId
}
case "unionid":
userName = dingtalkUser.UnionId
if userName == "" {
// Fallback to userId if unionid is empty
userName = dingtalkUser.UserId
}
default:
// Default behavior: prefer unionid, fallback to userId
userName = dingtalkUser.UserId
if dingtalkUser.UnionId != "" {
userName = dingtalkUser.UnionId
}
}
user := &OriginalUser{

View File

@@ -16,6 +16,22 @@ package object
import "fmt"
func getSyncerForUser(user *User) (*Syncer, error) {
syncers, err := GetSyncers("admin")
if err != nil {
return nil, err
}
for _, syncer := range syncers {
if syncer.Organization == user.Owner && syncer.IsEnabled {
return syncer, nil
}
}
return nil, nil
}
// Deprecated: Use getSyncerForUser instead. Maintained for backward compatibility.
// This function only returns database-type syncers.
func getDbSyncerForUser(user *User) (*Syncer, error) {
syncers, err := GetSyncers("admin")
if err != nil {

View File

@@ -20,7 +20,7 @@ import (
)
func calculateHash(user *User) (string, error) {
syncer, err := getDbSyncerForUser(user)
syncer, err := getSyncerForUser(user)
if err != nil {
return "", err
}

View File

@@ -977,6 +977,27 @@ class SyncerEditPage extends React.Component {
}
</Col>
</Row>
{
this.state.syncer.type === "DingTalk" ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("syncer:Name mapping"), i18next.t("syncer:Name mapping - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.syncer.nameMapping} onChange={(value => {this.updateSyncerField("nameMapping", value);})}>
{
[
{id: "unionid", name: "Union ID (default)"},
{id: "userid", name: "User ID"},
{id: "email", name: "Email"},
{id: "mobile", name: "Mobile"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
) : null
}
{
this.state.syncer.type === "WeCom" || this.state.syncer.type === "Azure AD" || this.state.syncer.type === "Google Workspace" || this.state.syncer.type === "DingTalk" || this.state.syncer.type === "Lark" || this.state.syncer.type === "Okta" || this.state.syncer.type === "SCIM" || this.state.syncer.type === "AWS IAM" ? null : (
<Row style={{marginTop: "20px"}} >

View File

@@ -1262,6 +1262,8 @@
"Is key": "Is key",
"Is read-only": "Is read-only",
"Is read-only - Tooltip": "Is read-only - Tooltip",
"Name mapping": "Name mapping",
"Name mapping - Tooltip": "Choose which DingTalk field to use as the username in Casdoor. Default is Union ID for compatibility with OAuth login.",
"New Syncer": "New Syncer",
"Paste your Google Workspace service account JSON key here": "Paste your Google Workspace service account JSON key here",
"SCIM Server URL": "SCIM Server URL",