Compare commits

...

8 Commits

Author SHA1 Message Date
Yang Luo
c64e8ab820 Update data.json 2026-02-11 00:44:55 +08:00
Yang Luo
b6d7a72695 Delete object/token_jwt_custom_test.go 2026-02-11 00:41:09 +08:00
Yang Luo
eb40d89d5f Update application.go 2026-02-11 00:40:58 +08:00
copilot-swe-agent[bot]
cadd4128db Optimize userFields array and add clarifying comments
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
2026-02-10 15:42:55 +00:00
copilot-swe-agent[bot]
2cc753681e Fix alphabetical ordering of translation entry
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
2026-02-10 15:41:36 +00:00
copilot-swe-agent[bot]
f4ebda6ac7 Add tests for dynamic token attributes with Existing Field category
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
2026-02-10 15:40:04 +00:00
copilot-swe-agent[bot]
40c9ff95e5 Add Category field to token attributes with Existing Field and Static Value options
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
2026-02-10 15:35:46 +00:00
copilot-swe-agent[bot]
3b28dbf4d9 Initial plan 2026-02-10 15:29:52 +00:00
3 changed files with 119 additions and 18 deletions

View File

@@ -61,9 +61,10 @@ type SamlItem struct {
}
type JwtItem struct {
Name string `json:"name"`
Value string `json:"value"`
Type string `json:"type"`
Name string `json:"name"`
Category string `json:"category"`
Value string `json:"value"`
Type string `json:"type"`
}
type Application struct {

View File

@@ -338,6 +338,50 @@ func getClaimsWithoutThirdIdp(claims Claims) ClaimsWithoutThirdIdp {
return res
}
// getUserFieldValue gets the value of a user field by name, handling special cases like Roles and Permissions
func getUserFieldValue(user *User, fieldName string) (interface{}, bool) {
if user == nil {
return nil, false
}
// Handle special fields that need conversion
switch fieldName {
case "Roles":
return getUserRoleNames(user), true
case "Permissions":
return getUserPermissionNames(user), true
case "permissionNames":
permissionNames := []string{}
for _, val := range user.Permissions {
permissionNames = append(permissionNames, val.Name)
}
return permissionNames, true
}
// Handle Properties fields (e.g., Properties.my_field)
if strings.HasPrefix(fieldName, "Properties.") {
parts := strings.Split(fieldName, ".")
if len(parts) == 2 {
propName := parts[1]
if user.Properties != nil {
if value, exists := user.Properties[propName]; exists {
return value, true
}
}
}
return nil, false
}
// Use reflection to get the field value
userValue := reflect.ValueOf(user).Elem()
userField := userValue.FieldByName(fieldName)
if userField.IsValid() {
return userField.Interface(), true
}
return nil, false
}
func getClaimsCustom(claims Claims, tokenField []string, tokenAttributes []*JwtItem) jwt.MapClaims {
res := make(jwt.MapClaims)
@@ -414,16 +458,30 @@ func getClaimsCustom(claims Claims, tokenField []string, tokenAttributes []*JwtI
}
for _, item := range tokenAttributes {
valueList := replaceAttributeValue(claims.User, item.Value)
if len(valueList) == 0 {
continue
var value interface{}
// If Category is "Existing Field", get the actual field value from the user
if item.Category == "Existing Field" {
fieldValue, found := getUserFieldValue(claims.User, item.Value)
if !found {
continue
}
value = fieldValue
} else {
// Default behavior: use replaceAttributeValue for "Static Value" or empty category
valueList := replaceAttributeValue(claims.User, item.Value)
if len(valueList) == 0 {
continue
}
if item.Type == "String" {
value = valueList[0]
} else {
value = valueList
}
}
if item.Type == "String" {
res[item.Name] = valueList[0]
} else {
res[item.Name] = valueList
}
res[item.Name] = value
}
return res

View File

@@ -24,6 +24,8 @@ class TokenAttributeTable extends React.Component {
this.state = {
classes: props,
};
// List of available user fields for "Existing Field" category
this.userFields = ["Owner", "Name", "Id", "DisplayName", "Email", "Phone", "Tag", "Roles", "Permissions", "permissionNames", "Groups"];
}
updateTable(table) {
@@ -36,7 +38,8 @@ class TokenAttributeTable extends React.Component {
}
addRow(table) {
const row = {Name: "", nameFormat: "", value: ""};
// Note: Field names use lowercase to match JSON serialization from backend (json:"name", json:"value", json:"type", json:"category")
const row = {name: "", value: "", type: "Array", category: "Static Value"};
if (table === undefined || table === null) {
table = [];
}
@@ -74,24 +77,63 @@ class TokenAttributeTable extends React.Component {
);
},
},
{
title: i18next.t("general:Category"),
dataIndex: "category",
key: "category",
width: "150px",
render: (text, record, index) => {
return (
<Select virtual={false} style={{width: "100%"}}
value={text ?? "Static Value"}
options={[
{value: "Static Value", label: i18next.t("application:Static Value")},
{value: "Existing Field", label: i18next.t("application:Existing Field")},
].map((item) =>
Setting.getOption(item.label, item.value))
}
onChange={value => {
this.updateField(table, index, "category", value);
}} >
</Select>
);
},
},
{
title: i18next.t("webhook:Value"),
dataIndex: "value",
key: "value",
width: "200px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
this.updateField(table, index, "value", e.target.value);
}} />
);
const category = record.category ?? "Static Value";
if (category === "Existing Field") {
// Show dropdown for existing fields
return (
<Select virtual={false} style={{width: "100%"}}
value={text}
options={this.userFields.map((field) =>
Setting.getOption(field, field))
}
onChange={value => {
this.updateField(table, index, "value", value);
}} >
</Select>
);
} else {
// Show text input for static values
return (
<Input value={text} onChange={e => {
this.updateField(table, index, "value", e.target.value);
}} />
);
}
},
},
{
title: i18next.t("general:Type"),
dataIndex: "type",
key: "type",
width: "200px",
width: "150px",
render: (text, record, index) => {
return (
<Select virtual={false} style={{width: "100%"}}