diff --git a/docs/config/federation.md b/docs/config/federation.md
index c32b256..38d89c9 100644
--- a/docs/config/federation.md
+++ b/docs/config/federation.md
@@ -249,6 +249,25 @@ The `extra_rp_metadata` option is used to add custom key-value pairs to the rely
another_field: another_value
```
+## `extra_fe_metadata`
+mapping / object
+optional
+
+The `extra_fe_metadata` option is used to add custom key-value pairs to the `federation_entity` metadata section in the entity configuration.
+
+This option is applied **before** any automatic copying of informational claims (controlled by [`publish_informational_claims_in_federation_entity`](#publish_informational_claims_in_federation_entity)). This means that values specified in `extra_fe_metadata` take precedence over automatically copied claims. If a field is set in `extra_fe_metadata`, it will not be overwritten by the automatic copying mechanism.
+
+When `publish_informational_claims_in_federation_entity` is set to `true` (default), OFFA will still automatically copy consistent informational claims from other metadata sections to the federation entity metadata, but only for fields that are not already defined in `extra_fe_metadata`.
+
+??? file "config.yaml"
+
+ ```yaml
+ federation:
+ extra_fe_metadata:
+ custom_federation_field: custom_value
+ another_federation_field: another_value
+ ```
+
## `extra_entity_configuration_data`
mapping / object
optional
diff --git a/internal/config/config.go b/internal/config/config.go
index 9707da8..f6f88c5 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -78,6 +78,7 @@ type federationConf struct {
OrganizationName string `yaml:"organization_name"`
OrganizationURI string `yaml:"organization_uri"`
ExtraRPMetadata map[string]any `yaml:"extra_rp_metadata"`
+ ExtraFEMetadata map[string]any `yaml:"extra_fe_metadata"`
ExtraEntityConfigurationData map[string]any `yaml:"extra_entity_configuration_data"`
ConfigurationLifetime duration.DurationOption `yaml:"configuration_lifetime"`
diff --git a/internal/server/server.go b/internal/server/server.go
index 5829642..00326b0 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -2,6 +2,7 @@ package server
import (
"fmt"
+ "reflect"
"strings"
"time"
@@ -95,6 +96,9 @@ func initFederationEntity() {
OrganizationURI: fedConfig.OrganizationURI,
},
}
+ if fedConfig.ExtraFEMetadata != nil && len(fedConfig.ExtraFEMetadata) > 0 {
+ metadata.FederationEntity = applyExtraFEMetadata(fedConfig.ExtraFEMetadata)
+ }
if metadata.RelyingParty.Extra == nil {
metadata.RelyingParty.Extra = make(map[string]any)
}
@@ -195,3 +199,47 @@ func getFullPath(path string) string {
}
return config.Get().Server.Basepath + path
}
+
+func applyExtraFEMetadata(extraFE map[string]any) *oidfed.FederationEntityMetadata {
+ fe := &oidfed.FederationEntityMetadata{}
+ v := reflect.ValueOf(fe).Elem()
+ t := v.Type()
+
+ jsonTagToField := make(map[string]string)
+ for i := 0; i < t.NumField(); i++ {
+ field := t.Field(i)
+ jsonTag := field.Tag.Get("json")
+ if jsonTag != "" && jsonTag != "-" {
+ if idx := strings.Index(jsonTag, ","); idx != -1 {
+ jsonTag = jsonTag[:idx]
+ }
+ jsonTagToField[jsonTag] = field.Name
+ }
+ }
+
+ for key, value := range extraFE {
+ if fieldName, ok := jsonTagToField[key]; ok {
+ field := v.FieldByName(fieldName)
+ if field.IsValid() && field.CanSet() {
+ switch field.Kind() {
+ case reflect.String:
+ if strVal, ok := value.(string); ok {
+ field.SetString(strVal)
+ }
+ case reflect.Slice:
+ if sliceVal, ok := value.([]interface{}); ok {
+ strSlice := make([]string, len(sliceVal))
+ for i, v := range sliceVal {
+ if str, ok := v.(string); ok {
+ strSlice[i] = str
+ }
+ }
+ field.Set(reflect.ValueOf(strSlice))
+ }
+ }
+ }
+ }
+ }
+
+ return fe
+}