Skip to content

Commit 7395a29

Browse files
committed
implement client ID for aggregator + content negotiation
1 parent aa516af commit 7395a29

6 files changed

Lines changed: 158 additions & 7 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package config
2+
3+
import (
4+
"aggregator/model"
5+
"encoding/json"
6+
"github.com/sirupsen/logrus"
7+
"net/http"
8+
)
9+
10+
type ClientIdentifierDocument struct {
11+
Context []string `json:"@context,omitempty"`
12+
ClientID string `json:"client_id"`
13+
}
14+
15+
var (
16+
clientIdentifierJSON []byte
17+
clientIdentifierJSONLD []byte
18+
)
19+
20+
func InitClientIdentifier(mux *http.ServeMux) {
21+
logrus.Info("Initializing client identifier endpoint")
22+
23+
var err error
24+
25+
// Pre-encode JSON-LD version (with context)
26+
clientDocLD := ClientIdentifierDocument{
27+
Context: []string{"https://www.w3.org/ns/solid/oidc-context.jsonld"},
28+
ClientID: model.ClientId,
29+
}
30+
clientIdentifierJSONLD, err = json.Marshal(clientDocLD)
31+
if err != nil {
32+
logrus.WithError(err).Fatal("Failed to marshal client identifier JSON-LD document")
33+
}
34+
35+
// Pre-encode JSON version (without context)
36+
clientDocJSON := ClientIdentifierDocument{
37+
ClientID: model.ClientId,
38+
}
39+
clientIdentifierJSON, err = json.Marshal(clientDocJSON)
40+
if err != nil {
41+
logrus.WithError(err).Fatal("Failed to marshal client identifier JSON document")
42+
}
43+
44+
mux.HandleFunc("/client.json", handleClientIdentifier)
45+
logrus.Info("Client identifier endpoint initialization completed")
46+
}
47+
48+
func handleClientIdentifier(w http.ResponseWriter, r *http.Request) {
49+
if r.Method != http.MethodGet && r.Method != http.MethodHead {
50+
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
51+
return
52+
}
53+
54+
accept := r.Header.Get("Accept")
55+
56+
var contentType string
57+
var body []byte
58+
59+
preferredType := negotiateContentType(accept, []string{"application/ld+json", "application/json"})
60+
61+
if preferredType == "application/json" {
62+
contentType = "application/json"
63+
body = clientIdentifierJSON
64+
} else {
65+
contentType = "application/ld+json"
66+
body = clientIdentifierJSONLD
67+
}
68+
69+
w.Header().Set("Content-Type", contentType)
70+
71+
if r.Method == http.MethodHead {
72+
return
73+
}
74+
75+
if _, err := w.Write(body); err != nil {
76+
logrus.WithError(err).Error("Failed to write client identifier document")
77+
}
78+
}

containers/aggregator-server/config/description.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func handleServerDescription(w http.ResponseWriter, r *http.Request) {
3939
"authorization_code",
4040
},
4141
Version: "1.0.0",
42-
ClientIdentifier: fmt.Sprintf("%s://%s/client.json", model.Protocol, model.ExternalHost), // Placeholder, not implemented yet
42+
ClientIdentifier: fmt.Sprintf("%s://%s/client.json", model.Protocol, model.ExternalHost),
4343
TransformationCatalog: fmt.Sprintf("%s://%s/config/transformations", model.Protocol, model.ExternalHost),
4444
}
4545

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
func negotiateContentType(accept string, supported []string) string {
9+
if accept == "" || accept == "*/*" {
10+
return supported[0]
11+
}
12+
13+
type mediaTypeWithQuality struct {
14+
mediaType string
15+
quality float64
16+
}
17+
18+
var acceptedTypes []mediaTypeWithQuality
19+
types := strings.Split(accept, ",")
20+
21+
for _, t := range types {
22+
parts := strings.Split(t, ";")
23+
mediaType := strings.TrimSpace(parts[0])
24+
quality := 1.0
25+
26+
for _, param := range parts[1:] {
27+
param = strings.TrimSpace(param)
28+
if strings.HasPrefix(param, "q=") {
29+
if q, err := parseQuality(strings.TrimPrefix(param, "q=")); err == nil {
30+
quality = q
31+
}
32+
}
33+
}
34+
35+
acceptedTypes = append(acceptedTypes, mediaTypeWithQuality{mediaType, quality})
36+
}
37+
38+
var bestMatch string
39+
var bestQuality float64 = -1
40+
41+
for _, supportedType := range supported {
42+
for _, accepted := range acceptedTypes {
43+
if accepted.mediaType == supportedType || accepted.mediaType == "*/*" {
44+
if accepted.quality > bestQuality {
45+
bestQuality = accepted.quality
46+
bestMatch = supportedType
47+
}
48+
}
49+
}
50+
}
51+
52+
return bestMatch
53+
}
54+
55+
func parseQuality(s string) (float64, error) {
56+
s = strings.TrimSpace(s)
57+
var q float64
58+
if _, err := fmt.Sscanf(s, "%f", &q); err != nil {
59+
return 0, err
60+
}
61+
if q < 0 {
62+
q = 0
63+
}
64+
if q > 1 {
65+
q = 1
66+
}
67+
return q, nil
68+
}

containers/aggregator-server/config/transformations.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,27 +46,31 @@ func (config TransformationsConfigData) HandleTransformationsEndpoint(w http.Res
4646
// getAvailableTransformations HEAD /config/transformations retrieves all available transformations
4747
func (config *TransformationsConfigData) headAvailableTransformations(w http.ResponseWriter, r *http.Request) {
4848
accept := r.Header.Get("Accept")
49-
if accept != "" && accept != "*/*" && accept != "text/turtle" {
49+
contentType := negotiateContentType(accept, []string{"text/turtle"})
50+
51+
if contentType == "" {
5052
http.Error(w, "Unsupported Media Type. Only text/turtle is supported.", http.StatusUnsupportedMediaType)
5153
return
5254
}
5355

5456
header := w.Header()
5557
header.Set("ETag", strconv.Itoa(config.etagTransformations))
56-
header.Set("Content-Type", "text/turtle")
58+
header.Set("Content-Type", contentType)
5759
}
5860

5961
// getAvailableTransformations GET /config/transformations retrieves all available transformations
6062
func (config *TransformationsConfigData) getAvailableTransformations(w http.ResponseWriter, r *http.Request) {
6163
accept := r.Header.Get("Accept")
62-
if accept != "" && accept != "*/*" && accept != "text/turtle" {
64+
contentType := negotiateContentType(accept, []string{"text/turtle"})
65+
66+
if contentType == "" {
6367
http.Error(w, "Unsupported Media Type. Only text/turtle is supported.", http.StatusUnsupportedMediaType)
6468
return
6569
}
6670

6771
header := w.Header()
6872
header.Set("ETag", strconv.Itoa(config.etagTransformations))
69-
header.Set("Content-Type", "text/turtle")
73+
header.Set("Content-Type", contentType)
7074
_, err := w.Write([]byte(config.transformations))
7175
if err != nil {
7276
http.Error(w, "error when writing body", http.StatusInternalServerError)

containers/aggregator-server/go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
2222
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
2323
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
2424
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
25-
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
26-
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
2725
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
2826
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
2927
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=

containers/aggregator-server/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ func main() {
7070
logrus.WithError(err).Warn("Failed to set up configuration endpoint (UMA might be down)")
7171
}
7272

73+
// Client Identifier endpoint
74+
config.InitClientIdentifier(serverMux)
75+
7376
// Server Description endpoint
7477
config.InitServerDescription(serverMux)
7578

0 commit comments

Comments
 (0)