diff --git a/services/webfinger/pkg/command/server.go b/services/webfinger/pkg/command/server.go index fb93d0868d..49fd8fdbac 100644 --- a/services/webfinger/pkg/command/server.go +++ b/services/webfinger/pkg/command/server.go @@ -120,7 +120,7 @@ func getRelationProviders(cfg *config.Config) (map[string]service.RelationProvid for _, relationURI := range cfg.Relations { switch relationURI { case relations.OpenIDConnectRel: - rels[relationURI] = relations.OpenIDDiscovery(cfg.IDP) + rels[relationURI] = relations.OpenIDDiscovery(cfg.IDP, cfg.OIDCClientConfigs) case relations.OpenCloudInstanceRel: var err error rels[relationURI], err = relations.OpenCloudInstance(cfg.Instances, cfg.OpenCloudURL) @@ -131,5 +131,6 @@ func getRelationProviders(cfg *config.Config) (map[string]service.RelationProvid return nil, fmt.Errorf("unknown relation '%s'", relationURI) } } + return rels, nil } diff --git a/services/webfinger/pkg/config/config.go b/services/webfinger/pkg/config/config.go index 7055a97ee7..2230a7427d 100644 --- a/services/webfinger/pkg/config/config.go +++ b/services/webfinger/pkg/config/config.go @@ -17,11 +17,21 @@ type Config struct { HTTP HTTP `yaml:"http"` - Instances []Instance `yaml:"instances"` - Relations []string `yaml:"relations" env:"WEBFINGER_RELATIONS" desc:"A list of relation URIs or registered relation types to add to webfinger responses. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"` - IDP string `yaml:"idp" env:"OC_URL;OC_OIDC_ISSUER;WEBFINGER_OIDC_ISSUER" desc:"The identity provider href for the openid-discovery relation." introductionVersion:"1.0.0"` - OpenCloudURL string `yaml:"opencloud_url" env:"OC_URL;WEBFINGER_OPENCLOUD_SERVER_INSTANCE_URL" desc:"The URL for the legacy OpenCloud server instance relation (not to be confused with the product OpenCloud Server). It defaults to the OC_URL but can be overridden to support some reverse proxy corner cases. To shard the deployment, multiple instances can be configured in the configuration file." introductionVersion:"1.0.0"` - Insecure bool `yaml:"insecure" env:"OC_INSECURE;WEBFINGER_INSECURE" desc:"Allow insecure connections to the WEBFINGER service." introductionVersion:"1.0.0"` + Instances []Instance `yaml:"instances"` + Relations []string `yaml:"relations" env:"WEBFINGER_RELATIONS" desc:"A list of relation URIs or registered relation types to add to webfinger responses. See the Environment Variable Types description for more details." introductionVersion:"1.0.0"` + IDP string `yaml:"idp" env:"OC_URL;OC_OIDC_ISSUER;WEBFINGER_OIDC_ISSUER" desc:"The identity provider href for the openid-discovery relation." introductionVersion:"1.0.0"` + AndroidClientID string `yaml:"android_client_id" env:"WEBFINGER_ANDROID_OIDC_CLIENT_ID" desc:"The OIDC client ID for Android app." introductionVersion:"%%NEXT%%"` + AndroidClientScopes []string `yaml:"android_client_scopes" env:"WEBFINGER_ANDROID_OIDC_CLIENT_SCOPES" desc:"The OIDC client scopes the Android app should request." introductionVersion:"%%NEXT%%"` + DesktopClientID string `yaml:"desktop_client_id" env:"WEBFINGER_DESKTOP_OIDC_CLIENT_ID" desc:"The OIDC client ID for the OpenCloud desktop application." introductionVersion:"%%NEXT%%"` + DesktopClientScopes []string `yaml:"desktop_client_scopes" env:"WEBFINGER_DESKTOP_OIDC_CLIENT_SCOPES" desc:"The OIDC client scopes the OpenCloud desktop application should request." introductionVersion:"%%NEXT%%"` + IOSClientID string `yaml:"ios_client_id" env:"WEBFINGER_IOS_OIDC_CLIENT_ID" desc:"The OIDC client ID for the IOS app." introductionVersion:"%%NEXT%%"` + IOSClientScopes []string `yaml:"ios_client_scopes" env:"WEBFINGER_IOS_OIDC_CLIENT_SCOPES" desc:"The OIDC client scopes the IOS app should request." introductionVersion:"%%NEXT%%"` + WebClientID string `yaml:"web_client_id" env:"WEBFINGER_WEB_OIDC_CLIENT_ID" desc:"The OIDC client ID for the OpenCloud web client." introductionVersion:"%%NEXT%%"` + WebClientScopes []string `yaml:"web_client_scopes" env:"WEBFINGER_WEB_OIDC_CLIENT_SCOPES" desc:"The OIDC client scopes the OpenCloud web client should request." introductionVersion:"%%NEXT%%"` + OpenCloudURL string `yaml:"opencloud_url" env:"OC_URL;WEBFINGER_OPENCLOUD_SERVER_INSTANCE_URL" desc:"The URL for the legacy OpenCloud server instance relation (not to be confused with the product OpenCloud Server). It defaults to the OC_URL but can be overridden to support some reverse proxy corner cases. To shard the deployment, multiple instances can be configured in the configuration file." introductionVersion:"1.0.0"` + Insecure bool `yaml:"insecure" env:"OC_INSECURE;WEBFINGER_INSECURE" desc:"Allow insecure connections to the WEBFINGER service." introductionVersion:"1.0.0"` + + OIDCClientConfigs map[string]OIDCClientConfig `yaml:"-"` Context context.Context `yaml:"-"` } @@ -34,3 +44,8 @@ type Instance struct { Titles map[string]string `yaml:"titles"` Break bool `yaml:"break"` } + +type OIDCClientConfig struct { + ClientID string + Scopes []string +} diff --git a/services/webfinger/pkg/config/defaults/defaultconfig.go b/services/webfinger/pkg/config/defaults/defaultconfig.go index 3d698ce7ff..bc209ae3ca 100644 --- a/services/webfinger/pkg/config/defaults/defaultconfig.go +++ b/services/webfinger/pkg/config/defaults/defaultconfig.go @@ -7,6 +7,11 @@ import ( "github.com/opencloud-eu/opencloud/services/webfinger/pkg/relations" ) +var ( + nativeAppScopes = []string{"openid", "profile", "email", "offline_access"} + webAppScopes = []string{"openid", "profile", "email"} +) + // FullDefaultConfig returns a fully initialized default configuration func FullDefaultConfig() *config.Config { cfg := DefaultConfig() @@ -49,8 +54,16 @@ func DefaultConfig() *config.Config { }, }, }, - IDP: "https://localhost:9200", - Insecure: false, + IDP: "https://localhost:9200", + Insecure: false, + AndroidClientID: "OpenCloudAndroid", + AndroidClientScopes: nativeAppScopes, + DesktopClientID: "OpenCloudDesktop", + DesktopClientScopes: nativeAppScopes, + IOSClientID: "OpenCloudIOS", + IOSClientScopes: nativeAppScopes, + WebClientID: "web", + WebClientScopes: webAppScopes, } } @@ -86,4 +99,23 @@ func Sanitize(cfg *config.Config) { if cfg.HTTP.Root != "/" { cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") } + + cfg.OIDCClientConfigs = map[string]config.OIDCClientConfig{ + "android": { + ClientID: cfg.AndroidClientID, + Scopes: cfg.AndroidClientScopes, + }, + "desktop": { + ClientID: cfg.DesktopClientID, + Scopes: cfg.DesktopClientScopes, + }, + "ios": { + ClientID: cfg.IOSClientID, + Scopes: cfg.IOSClientScopes, + }, + "web": { + ClientID: cfg.WebClientID, + Scopes: cfg.WebClientScopes, + }, + } } diff --git a/services/webfinger/pkg/relations/owncloud_instance.go b/services/webfinger/pkg/relations/opencloud_instance.go similarity index 95% rename from services/webfinger/pkg/relations/owncloud_instance.go rename to services/webfinger/pkg/relations/opencloud_instance.go index f059748274..1a51ae9c2e 100644 --- a/services/webfinger/pkg/relations/owncloud_instance.go +++ b/services/webfinger/pkg/relations/opencloud_instance.go @@ -57,7 +57,7 @@ func OpenCloudInstance(instances []config.Instance, openCloudURL string) (servic }, nil } -func (l *openCloudInstance) Add(ctx context.Context, jrd *webfinger.JSONResourceDescriptor) { +func (l *openCloudInstance) Add(ctx context.Context, _ string, jrd *webfinger.JSONResourceDescriptor) { if jrd == nil { jrd = &webfinger.JSONResourceDescriptor{} } diff --git a/services/webfinger/pkg/relations/owncloud_instance_test.go b/services/webfinger/pkg/relations/opencloud_instance_test.go similarity index 98% rename from services/webfinger/pkg/relations/owncloud_instance_test.go rename to services/webfinger/pkg/relations/opencloud_instance_test.go index fa0aa686f5..8e04c14482 100644 --- a/services/webfinger/pkg/relations/owncloud_instance_test.go +++ b/services/webfinger/pkg/relations/opencloud_instance_test.go @@ -44,7 +44,7 @@ func TestOpenCloudInstanceAddLink(t *testing.T) { "otherclaim": "someone", }) jrd := webfinger.JSONResourceDescriptor{} - provider.Add(ctx, &jrd) + provider.Add(ctx, "", &jrd) if len(jrd.Links) != 1 { t.Errorf("provider returned wrong number of links: %v, expected 1", len(jrd.Links)) diff --git a/services/webfinger/pkg/relations/openid_discovery.go b/services/webfinger/pkg/relations/openid_discovery.go index 196357ee9c..a51945e691 100644 --- a/services/webfinger/pkg/relations/openid_discovery.go +++ b/services/webfinger/pkg/relations/openid_discovery.go @@ -3,26 +3,31 @@ package relations import ( "context" + "github.com/opencloud-eu/opencloud/services/webfinger/pkg/config" "github.com/opencloud-eu/opencloud/services/webfinger/pkg/service/v0" "github.com/opencloud-eu/opencloud/services/webfinger/pkg/webfinger" ) const ( OpenIDConnectRel = "http://openid.net/specs/connect/1.0/issuer" + clientIDProp = "http://opencloud.eu/ns/oidc/client_id" + scopesProp = "http://opencloud.eu/ns/oidc/scopes" ) type openIDDiscovery struct { - Href string + Href string + OIDCClients map[string]config.OIDCClientConfig } // OpenIDDiscovery adds the Openid Connect issuer relation -func OpenIDDiscovery(href string) service.RelationProvider { +func OpenIDDiscovery(href string, clients map[string]config.OIDCClientConfig) service.RelationProvider { return &openIDDiscovery{ - Href: href, + Href: href, + OIDCClients: clients, } } -func (l *openIDDiscovery) Add(_ context.Context, jrd *webfinger.JSONResourceDescriptor) { +func (l *openIDDiscovery) Add(_ context.Context, platform string, jrd *webfinger.JSONResourceDescriptor) { if jrd == nil { jrd = &webfinger.JSONResourceDescriptor{} } @@ -30,4 +35,12 @@ func (l *openIDDiscovery) Add(_ context.Context, jrd *webfinger.JSONResourceDesc Rel: OpenIDConnectRel, Href: l.Href, }) + + if platform != "" { + if clientConfig, ok := l.OIDCClients[platform]; ok { + jrd.Properties = make(map[string]any) + jrd.Properties[clientIDProp] = clientConfig.ClientID + jrd.Properties[scopesProp] = clientConfig.Scopes + } + } } diff --git a/services/webfinger/pkg/relations/openid_discovery_test.go b/services/webfinger/pkg/relations/openid_discovery_test.go index 7b4efabd44..097076763b 100644 --- a/services/webfinger/pkg/relations/openid_discovery_test.go +++ b/services/webfinger/pkg/relations/openid_discovery_test.go @@ -4,15 +4,27 @@ import ( "context" "testing" + "github.com/opencloud-eu/opencloud/services/webfinger/pkg/config" "github.com/opencloud-eu/opencloud/services/webfinger/pkg/webfinger" ) func TestOpenidDiscovery(t *testing.T) { - provider := OpenIDDiscovery("http://issuer.url") + clients := map[string]config.OIDCClientConfig{ + "web": { + ClientID: "web", + Scopes: []string{"openid", "profile", "email"}, + }, + "test": { + ClientID: "test", + Scopes: []string{"test"}, + }, + } + + provider := OpenIDDiscovery("http://issuer.url", clients) jrd := webfinger.JSONResourceDescriptor{} - provider.Add(context.Background(), &jrd) + provider.Add(context.Background(), "", &jrd) if len(jrd.Links) != 1 { t.Errorf("provider returned wrong number of links: %v, expected 1", len(jrd.Links)) @@ -23,4 +35,19 @@ func TestOpenidDiscovery(t *testing.T) { if jrd.Links[0].Rel != "http://openid.net/specs/connect/1.0/issuer" { t.Errorf("provider returned wrong openid connect rel: %v, expected %v", jrd.Links[0].Href, OpenIDConnectRel) } + if len(jrd.Properties) != 0 { + t.Errorf("provider returned properties for empty platform: %v, expected 0", len(jrd.Properties)) + } + + jrd = webfinger.JSONResourceDescriptor{} + provider.Add(context.Background(), "test", &jrd) + if len(jrd.Properties) != 2 { + t.Errorf("provider returned wrong number of properties for platform test: %v, expected 2", len(jrd.Properties)) + } + if jrd.Properties["http://opencloud.eu/ns/oidc/client_id"] != "test" { + t.Errorf("provider returned wrong client_id property: %v, expected %v", jrd.Properties["http://opencloud.eu/ns/oidc/client_id"], "test") + } + if scopes, ok := jrd.Properties["http://opencloud.eu/ns/oidc/scopes"].([]string); !ok || len(scopes) != 1 || scopes[0] != "test" { + t.Errorf("provider returned wrong scopes property: %v, expected %v", jrd.Properties["http://opencloud.eu/ns/oidc/scopes"], []string{"test"}) + } } diff --git a/services/webfinger/pkg/server/http/server.go b/services/webfinger/pkg/server/http/server.go index 239ca5e67a..dbe91b0ca4 100644 --- a/services/webfinger/pkg/server/http/server.go +++ b/services/webfinger/pkg/server/http/server.go @@ -125,14 +125,11 @@ func WebfingerHandler(service svc.Service) func(w http.ResponseWriter, r *http.R return } - rels := make([]string, 0) - for k, v := range r.URL.Query() { - if k == "rel" { - rels = append(rels, v...) - } - } + rels := r.URL.Query()["rel"] + + platform := r.URL.Query().Get("platform") - jrd, err := service.Webfinger(ctx, queryTarget, rels) + jrd, err := service.Webfinger(ctx, queryTarget, rels, platform) if errors.Is(err, serviceErrors.ErrNotFound) { // from https://www.rfc-editor.org/rfc/rfc7033#section-4.2 // diff --git a/services/webfinger/pkg/service/v0/instrument.go b/services/webfinger/pkg/service/v0/instrument.go index bfca78a3df..b5a34c9dbf 100644 --- a/services/webfinger/pkg/service/v0/instrument.go +++ b/services/webfinger/pkg/service/v0/instrument.go @@ -23,7 +23,7 @@ type instrument struct { } // Webfinger implements the Service interface. -func (i instrument) Webfinger(ctx context.Context, queryTarget *url.URL, rels []string) (webfinger.JSONResourceDescriptor, error) { +func (i instrument) Webfinger(ctx context.Context, queryTarget *url.URL, rels []string, platform string) (webfinger.JSONResourceDescriptor, error) { timer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) { us := v * 1000000 @@ -35,5 +35,5 @@ func (i instrument) Webfinger(ctx context.Context, queryTarget *url.URL, rels [] i.metrics.Counter.WithLabelValues().Inc() - return i.next.Webfinger(ctx, queryTarget, rels) + return i.next.Webfinger(ctx, queryTarget, rels, platform) } diff --git a/services/webfinger/pkg/service/v0/logging.go b/services/webfinger/pkg/service/v0/logging.go index 4f03b76958..270bc3abd3 100644 --- a/services/webfinger/pkg/service/v0/logging.go +++ b/services/webfinger/pkg/service/v0/logging.go @@ -22,11 +22,11 @@ type logging struct { } // Webfinger implements the Service interface. -func (l logging) Webfinger(ctx context.Context, queryTarget *url.URL, rels []string) (webfinger.JSONResourceDescriptor, error) { +func (l logging) Webfinger(ctx context.Context, queryTarget *url.URL, rels []string, platform string) (webfinger.JSONResourceDescriptor, error) { l.logger.Debug(). Str("query_target", queryTarget.String()). Strs("rel", rels). Msg("Webfinger") - return l.next.Webfinger(ctx, queryTarget, rels) + return l.next.Webfinger(ctx, queryTarget, rels, platform) } diff --git a/services/webfinger/pkg/service/v0/service.go b/services/webfinger/pkg/service/v0/service.go index 486eb1cb82..29c694d84d 100644 --- a/services/webfinger/pkg/service/v0/service.go +++ b/services/webfinger/pkg/service/v0/service.go @@ -45,11 +45,11 @@ type Service interface { // } // ] // } - Webfinger(ctx context.Context, queryTarget *url.URL, rels []string) (webfinger.JSONResourceDescriptor, error) + Webfinger(ctx context.Context, queryTarget *url.URL, rels []string, platform string) (webfinger.JSONResourceDescriptor, error) } type RelationProvider interface { - Add(ctx context.Context, jrd *webfinger.JSONResourceDescriptor) + Add(ctx context.Context, platform string, jrd *webfinger.JSONResourceDescriptor) } // New returns a new instance of Service @@ -81,7 +81,7 @@ type svc struct { // - one that looks up in instance by id (use template, read from json, read from ldap, read from graph) // Webfinger implements the service interface -func (s svc) Webfinger(ctx context.Context, queryTarget *url.URL, rel []string) (webfinger.JSONResourceDescriptor, error) { +func (s svc) Webfinger(ctx context.Context, queryTarget *url.URL, rel []string, platform string) (webfinger.JSONResourceDescriptor, error) { jrd := webfinger.JSONResourceDescriptor{ Subject: queryTarget.String(), @@ -90,13 +90,13 @@ func (s svc) Webfinger(ctx context.Context, queryTarget *url.URL, rel []string) if len(rel) == 0 { // add all configured relation providers for _, relation := range s.relationProviders { - relation.Add(ctx, &jrd) + relation.Add(ctx, platform, &jrd) } } else { // only add requested relations for _, r := range rel { if relation, ok := s.relationProviders[r]; ok { - relation.Add(ctx, &jrd) + relation.Add(ctx, platform, &jrd) } } } diff --git a/services/webfinger/pkg/service/v0/tracing.go b/services/webfinger/pkg/service/v0/tracing.go index fd41cce948..4c3557bc4d 100644 --- a/services/webfinger/pkg/service/v0/tracing.go +++ b/services/webfinger/pkg/service/v0/tracing.go @@ -23,7 +23,7 @@ type tracing struct { } // Webfinger implements the Service interface. -func (t tracing) Webfinger(ctx context.Context, queryTarget *url.URL, rels []string) (webfinger.JSONResourceDescriptor, error) { +func (t tracing) Webfinger(ctx context.Context, queryTarget *url.URL, rels []string, platform string) (webfinger.JSONResourceDescriptor, error) { spanOpts := []trace.SpanStartOption{ trace.WithSpanKind(trace.SpanKindServer), trace.WithAttributes( @@ -34,5 +34,5 @@ func (t tracing) Webfinger(ctx context.Context, queryTarget *url.URL, rels []str ctx, span := t.tp.Tracer("webfinger").Start(ctx, "Webfinger", spanOpts...) defer span.End() - return t.next.Webfinger(ctx, queryTarget, rels) + return t.next.Webfinger(ctx, queryTarget, rels, platform) } diff --git a/services/webfinger/pkg/webfinger/webfinger.go b/services/webfinger/pkg/webfinger/webfinger.go index f4674376ea..531a2523c2 100644 --- a/services/webfinger/pkg/webfinger/webfinger.go +++ b/services/webfinger/pkg/webfinger/webfinger.go @@ -56,7 +56,7 @@ type JSONResourceDescriptor struct { // values are strings or null. // // The "properties" member is OPTIONAL in the JRD. - Properties map[string]string `json:"properties,omitempty"` + Properties map[string]any `json:"properties,omitempty"` // Links is an array of objects that contain link relation information // // The "links" array is OPTIONAL in the JRD.