Skip to content
Open
Show file tree
Hide file tree
Changes from all 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%%"`
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