Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion services/webfinger/pkg/command/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -131,5 +131,6 @@ func getRelationProviders(cfg *config.Config) (map[string]service.RelationProvid
return nil, fmt.Errorf("unknown relation '%s'", relationURI)
}
}

return rels, nil
}
25 changes: 20 additions & 5 deletions services/webfinger/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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%%"`
Comment thread
micbar marked this conversation as resolved.
Outdated
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:"-"`
}
Expand All @@ -34,3 +44,8 @@ type Instance struct {
Titles map[string]string `yaml:"titles"`
Break bool `yaml:"break"`
}

type OIDCClientConfig struct {
ClientID string
Scopes []string
}
36 changes: 34 additions & 2 deletions services/webfinger/pkg/config/defaults/defaultconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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,
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
21 changes: 17 additions & 4 deletions services/webfinger/pkg/relations/openid_discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,44 @@ 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{}
}
jrd.Links = append(jrd.Links, webfinger.Link{
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
}
}
}
31 changes: 29 additions & 2 deletions services/webfinger/pkg/relations/openid_discovery_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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"})
}
}
11 changes: 4 additions & 7 deletions services/webfinger/pkg/server/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
//
Expand Down
4 changes: 2 additions & 2 deletions services/webfinger/pkg/service/v0/instrument.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
}
4 changes: 2 additions & 2 deletions services/webfinger/pkg/service/v0/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
10 changes: 5 additions & 5 deletions services/webfinger/pkg/service/v0/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(),
Expand All @@ -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)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions services/webfinger/pkg/service/v0/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
}
2 changes: 1 addition & 1 deletion services/webfinger/pkg/webfinger/webfinger.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down