Initial QSfera import
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
package service
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrNotFound = errors.New("query target not found")
|
||||
@@ -0,0 +1,39 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"github.com/qsfera/server/services/webfinger/pkg/metrics"
|
||||
"github.com/qsfera/server/services/webfinger/pkg/webfinger"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// NewInstrument returns a service that instruments metrics.
|
||||
func NewInstrument(next Service, metrics *metrics.Metrics) Service {
|
||||
return instrument{
|
||||
next: next,
|
||||
metrics: metrics,
|
||||
}
|
||||
}
|
||||
|
||||
type instrument struct {
|
||||
next Service
|
||||
metrics *metrics.Metrics
|
||||
}
|
||||
|
||||
// Webfinger implements the Service interface.
|
||||
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
|
||||
|
||||
i.metrics.Latency.WithLabelValues().Observe(us)
|
||||
i.metrics.Duration.WithLabelValues().Observe(v)
|
||||
}))
|
||||
|
||||
defer timer.ObserveDuration()
|
||||
|
||||
i.metrics.Counter.WithLabelValues().Inc()
|
||||
|
||||
return i.next.Webfinger(ctx, queryTarget, rels, platform)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/services/webfinger/pkg/webfinger"
|
||||
)
|
||||
|
||||
// NewLogging returns a service that logs messages.
|
||||
func NewLogging(next Service, logger log.Logger) Service {
|
||||
return logging{
|
||||
next: next,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
type logging struct {
|
||||
next Service
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// Webfinger implements the Service interface.
|
||||
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, platform)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/services/webfinger/pkg/config"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
Config *config.Config
|
||||
RelationProviders map[string]RelationProvider
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Logger provides a function to set the logger option.
|
||||
func Logger(val log.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = val
|
||||
}
|
||||
}
|
||||
|
||||
// Config provides a function to set the config option.
|
||||
func Config(val *config.Config) Option {
|
||||
return func(o *Options) {
|
||||
o.Config = val
|
||||
}
|
||||
}
|
||||
|
||||
func WithRelationProviders(val map[string]RelationProvider) Option {
|
||||
return func(o *Options) {
|
||||
o.RelationProviders = val
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/services/webfinger/pkg/config"
|
||||
"github.com/qsfera/server/services/webfinger/pkg/webfinger"
|
||||
)
|
||||
|
||||
// Service defines the extension handlers.
|
||||
type Service interface {
|
||||
// Webfinger is the endpoint for retrieving various href relations.
|
||||
//
|
||||
// GET /.well-known/webfinger?
|
||||
// resource=acct%3Acarol%40example.com&
|
||||
// rel=http%3A%2F%2Fwebfinger.qsfera%rel%2Fserver-instance
|
||||
// HTTP/1.1
|
||||
// Host: example.com
|
||||
//
|
||||
// The server might respond like this:
|
||||
//
|
||||
// HTTP/1.1 200 OK
|
||||
// Access-Control-Allow-Origin: *
|
||||
// Content-Type: application/jrd+json
|
||||
//
|
||||
// {
|
||||
// "subject" : "acct:carol@example.com",
|
||||
// "links" :
|
||||
// [
|
||||
// {
|
||||
// "rel" : "http://webfinger.qsfera/rel/server-instance",
|
||||
// "href" : "https://instance.example.com",
|
||||
// "titles": {
|
||||
// "en": "Readable Instance Name"
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// "rel" : "http://webfinger.qsfera/rel/server-instance",
|
||||
// "href" : "https://otherinstance.example.com",
|
||||
// "titles": {
|
||||
// "en": "Other Readable Instance Name"
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
Webfinger(ctx context.Context, queryTarget *url.URL, rels []string, platform string) (webfinger.JSONResourceDescriptor, error)
|
||||
}
|
||||
|
||||
type RelationProvider interface {
|
||||
Add(ctx context.Context, platform string, jrd *webfinger.JSONResourceDescriptor)
|
||||
}
|
||||
|
||||
// New returns a new instance of Service
|
||||
func New(opts ...Option) (Service, error) {
|
||||
options := newOptions(opts...)
|
||||
|
||||
// TODO use fallback implementations of InstanceIdLookup and InstanceLookup?
|
||||
// The InstanceIdLookup may have to happen earlier?
|
||||
|
||||
return svc{
|
||||
log: options.Logger,
|
||||
config: options.Config,
|
||||
relationProviders: options.RelationProviders,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type svc struct {
|
||||
config *config.Config
|
||||
log log.Logger
|
||||
relationProviders map[string]RelationProvider
|
||||
}
|
||||
|
||||
// TODO implement different implementations:
|
||||
// static one returning the href or a configurable domain
|
||||
// regex one returning different instances based on the regex that matches
|
||||
// claim one that reads a claim and then fetches the instance?
|
||||
// that is actually two interfaces / steps:
|
||||
// - one that determines the instances/schools id (read from claim, regex match)
|
||||
// - 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, platform string) (webfinger.JSONResourceDescriptor, error) {
|
||||
|
||||
jrd := webfinger.JSONResourceDescriptor{
|
||||
Subject: queryTarget.String(),
|
||||
}
|
||||
|
||||
if len(rel) == 0 {
|
||||
// add all configured relation providers
|
||||
for _, relation := range s.relationProviders {
|
||||
relation.Add(ctx, platform, &jrd)
|
||||
}
|
||||
} else {
|
||||
// only add requested relations
|
||||
for _, r := range rel {
|
||||
if relation, ok := s.relationProviders[r]; ok {
|
||||
relation.Add(ctx, platform, &jrd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return jrd, nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
"github.com/qsfera/server/services/webfinger/pkg/webfinger"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// NewTracing returns a service that instruments traces.
|
||||
func NewTracing(next Service, tp trace.TracerProvider) Service {
|
||||
return tracing{
|
||||
next: next,
|
||||
tp: tp,
|
||||
}
|
||||
}
|
||||
|
||||
type tracing struct {
|
||||
next Service
|
||||
tp trace.TracerProvider
|
||||
}
|
||||
|
||||
// Webfinger implements the Service interface.
|
||||
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(
|
||||
attribute.KeyValue{Key: "query_target", Value: attribute.StringValue(queryTarget.String())},
|
||||
attribute.KeyValue{Key: "rels", Value: attribute.StringSliceValue(rels)},
|
||||
),
|
||||
}
|
||||
ctx, span := t.tp.Tracer("webfinger").Start(ctx, "Webfinger", spanOpts...)
|
||||
defer span.End()
|
||||
|
||||
return t.next.Webfinger(ctx, queryTarget, rels, platform)
|
||||
}
|
||||
Reference in New Issue
Block a user