mirror of
https://github.com/pomerium/pomerium.git
synced 2025-06-02 19:04:14 +02:00
mcp: client registration: store to databroker
This commit is contained in:
parent
578b3bf8b3
commit
459168222d
2 changed files with 140 additions and 3 deletions
|
@ -1,11 +1,82 @@
|
||||||
package mcp
|
package mcp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/bufbuild/protovalidate-go"
|
||||||
|
"google.golang.org/protobuf/encoding/protojson"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
|
rfc7591v1 "github.com/pomerium/pomerium/internal/rfc7591"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const maxClientRegistrationPayload = 1024 * 1024 // 1MB
|
||||||
|
|
||||||
// RegisterClient handles the /register endpoint.
|
// RegisterClient handles the /register endpoint.
|
||||||
// It is used to register a new client with the MCP server.
|
// It is used to register a new client with the MCP server.
|
||||||
func (srv *Handler) RegisterClient(w http.ResponseWriter, _ *http.Request) {
|
func (srv *Handler) RegisterClient(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotImplemented)
|
ctx := r.Context()
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "invalid method", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
src := io.LimitReader(r.Body, maxClientRegistrationPayload)
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
data, err := io.ReadAll(src)
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Error().Err(err).Msg("failed to read request body")
|
||||||
|
http.Error(w, "failed to read request body", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
v := new(rfc7591v1.ClientRegistrationRequest)
|
||||||
|
err = protojson.Unmarshal(data, v)
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Error().Err(err).Msg("failed to unmarshal request body")
|
||||||
|
http.Error(w, "failed to unmarshal request body", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = protovalidate.Validate(v)
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Error().Err(err).Msg("failed to validate request body")
|
||||||
|
clientRegistrationError(w, err, rfc7591v1.ErrorCode_ERROR_CODE_INVALID_CLIENT_METADATA)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := srv.storage.RegisterClient(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Error().Err(err).Msg("failed to register client")
|
||||||
|
http.Error(w, "failed to register client", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err = protojson.MarshalOptions{
|
||||||
|
UseProtoNames: true,
|
||||||
|
}.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Error().Err(err).Msg("failed to marshal response")
|
||||||
|
http.Error(w, "failed to marshal response", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, err = w.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Ctx(ctx).Error().Err(err).Msg("failed to write response")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientRegistrationError(w http.ResponseWriter, err error, code rfc7591v1.ErrorCode) {
|
||||||
|
v := &rfc7591v1.ClientRegistrationErrorResponse{
|
||||||
|
Error: code,
|
||||||
|
ErrorDescription: proto.String(err.Error()),
|
||||||
|
}
|
||||||
|
data, _ := protojson.Marshal(v)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
_, _ = w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,16 @@
|
||||||
package mcp
|
package mcp
|
||||||
|
|
||||||
import "github.com/pomerium/pomerium/pkg/grpc/databroker"
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
|
rfc7591v1 "github.com/pomerium/pomerium/internal/rfc7591"
|
||||||
|
"github.com/pomerium/pomerium/pkg/grpc/databroker"
|
||||||
|
"github.com/pomerium/pomerium/pkg/protoutil"
|
||||||
|
)
|
||||||
|
|
||||||
type Storage struct {
|
type Storage struct {
|
||||||
client databroker.DataBrokerServiceClient
|
client databroker.DataBrokerServiceClient
|
||||||
|
@ -14,3 +24,59 @@ func NewStorage(
|
||||||
client: client,
|
client: client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (storage *Storage) RegisterClient(
|
||||||
|
ctx context.Context,
|
||||||
|
req *rfc7591v1.ClientRegistrationRequest,
|
||||||
|
) (*rfc7591v1.ClientInformationResponse, error) {
|
||||||
|
data := protoutil.NewAny(req)
|
||||||
|
id := uuid.NewString()
|
||||||
|
rec, err := storage.client.Put(ctx, &databroker.PutRequest{
|
||||||
|
Records: []*databroker.Record{{
|
||||||
|
Id: id,
|
||||||
|
Data: data,
|
||||||
|
Type: data.TypeUrl,
|
||||||
|
}},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(rec.Records) == 0 {
|
||||||
|
return nil, fmt.Errorf("no records returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
now := rec.Records[0].GetModifiedAt().Seconds
|
||||||
|
return getClientInformation(id, now, req), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClientInformation(
|
||||||
|
id string,
|
||||||
|
issuedAt int64,
|
||||||
|
req *rfc7591v1.ClientRegistrationRequest,
|
||||||
|
) *rfc7591v1.ClientInformationResponse {
|
||||||
|
return &rfc7591v1.ClientInformationResponse{
|
||||||
|
ClientId: id,
|
||||||
|
ClientIdIssuedAt: proto.Int64(issuedAt),
|
||||||
|
RedirectUris: req.RedirectUris,
|
||||||
|
TokenEndpointAuthMethod: req.TokenEndpointAuthMethod,
|
||||||
|
GrantTypes: req.GrantTypes,
|
||||||
|
ResponseTypes: req.ResponseTypes,
|
||||||
|
ClientName: req.ClientName,
|
||||||
|
ClientNameLocalized: req.ClientNameLocalized,
|
||||||
|
ClientUri: req.ClientUri,
|
||||||
|
ClientUriLocalized: req.ClientUriLocalized,
|
||||||
|
LogoUri: req.LogoUri,
|
||||||
|
LogoUriLocalized: req.LogoUriLocalized,
|
||||||
|
Scope: req.Scope,
|
||||||
|
Contacts: req.Contacts,
|
||||||
|
TosUri: req.TosUri,
|
||||||
|
TosUriLocalized: req.TosUriLocalized,
|
||||||
|
PolicyUri: req.PolicyUri,
|
||||||
|
PolicyUriLocalized: req.PolicyUriLocalized,
|
||||||
|
JwksUri: req.JwksUri,
|
||||||
|
Jwks: req.Jwks,
|
||||||
|
SoftwareId: req.SoftwareId,
|
||||||
|
SoftwareVersion: req.SoftwareVersion,
|
||||||
|
SoftwareStatement: req.SoftwareStatement,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue