implement remaining server methods
This commit is contained in:
parent
2b08c53e49
commit
6993769f06
3 changed files with 82 additions and 42 deletions
|
@ -69,6 +69,11 @@ type Server interface {
|
||||||
// The recommended Response Data type is [oidc.DiscoveryConfiguration].
|
// The recommended Response Data type is [oidc.DiscoveryConfiguration].
|
||||||
Discovery(context.Context, *Request[struct{}]) (*Response, error)
|
Discovery(context.Context, *Request[struct{}]) (*Response, error)
|
||||||
|
|
||||||
|
// Keys serves the JWK set which the client can use verify signatures from the op.
|
||||||
|
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata `jwks_uri` key.
|
||||||
|
// The recommended Response Data type is [jose.JSOMWebKeySet].
|
||||||
|
Keys(context.Context, *Request[struct{}]) (*Response, error)
|
||||||
|
|
||||||
// Authorize initiates the authorization flow and redirects to a login page.
|
// Authorize initiates the authorization flow and redirects to a login page.
|
||||||
// See the various https://openid.net/specs/openid-connect-core-1_0.html
|
// See the various https://openid.net/specs/openid-connect-core-1_0.html
|
||||||
// authorize endpoint sections (one for each type of flow).
|
// authorize endpoint sections (one for each type of flow).
|
||||||
|
@ -147,17 +152,12 @@ type Server interface {
|
||||||
// Revocation handles token revocation using an access or refresh token.
|
// Revocation handles token revocation using an access or refresh token.
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7009
|
// https://datatracker.ietf.org/doc/html/rfc7009
|
||||||
// There are no response requirements. Data may remain empty.
|
// There are no response requirements. Data may remain empty.
|
||||||
Revocation(context.Context, *Request[oidc.RevocationRequest]) (*Response, error)
|
Revocation(context.Context, *ClientRequest[oidc.RevocationRequest]) (*Response, error)
|
||||||
|
|
||||||
// EndSession handles the OpenID Connect RP-Initiated Logout.
|
// EndSession handles the OpenID Connect RP-Initiated Logout.
|
||||||
// https://openid.net/specs/openid-connect-rpinitiated-1_0.html
|
// https://openid.net/specs/openid-connect-rpinitiated-1_0.html
|
||||||
// There are no response requirements. Data may remain empty.
|
// There are no response requirements. Data may remain empty.
|
||||||
EndSession(context.Context, *Request[oidc.EndSessionRequest]) (*Response, error)
|
EndSession(context.Context, *Request[oidc.EndSessionRequest]) (*Redirect, error)
|
||||||
|
|
||||||
// Keys serves the JWK set which the client can use verify signatures from the op.
|
|
||||||
// https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata `jwks_uri` key.
|
|
||||||
// The recommended Response Data type is [jose.JSOMWebKeySet].
|
|
||||||
Keys(context.Context, *Request[struct{}]) (*Response, error)
|
|
||||||
|
|
||||||
// mustImpl forces implementations to embed the UnimplementedServer for forward
|
// mustImpl forces implementations to embed the UnimplementedServer for forward
|
||||||
// compatibilty with the interface.
|
// compatibilty with the interface.
|
||||||
|
@ -268,70 +268,70 @@ func unimplementedGrantError(gt oidc.GrantType) StatusError {
|
||||||
|
|
||||||
func (UnimplementedServer) mustImpl() {}
|
func (UnimplementedServer) mustImpl() {}
|
||||||
|
|
||||||
func (UnimplementedServer) Health(_ context.Context, r *Request[struct{}]) (*Response, error) {
|
func (UnimplementedServer) Health(ctx context.Context, r *Request[struct{}]) (*Response, error) {
|
||||||
return nil, unimplementedError(r)
|
return nil, unimplementedError(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) Ready(_ context.Context, r *Request[struct{}]) (*Response, error) {
|
func (UnimplementedServer) Ready(ctx context.Context, r *Request[struct{}]) (*Response, error) {
|
||||||
return nil, unimplementedError(r)
|
return nil, unimplementedError(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) Discovery(_ context.Context, r *Request[struct{}]) (*Response, error) {
|
func (UnimplementedServer) Discovery(ctx context.Context, r *Request[struct{}]) (*Response, error) {
|
||||||
return nil, unimplementedError(r)
|
return nil, unimplementedError(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) Authorize(_ context.Context, r *Request[oidc.AuthRequest]) (*Redirect, error) {
|
func (UnimplementedServer) Keys(ctx context.Context, r *Request[struct{}]) (*Response, error) {
|
||||||
return nil, unimplementedError(r)
|
return nil, unimplementedError(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) DeviceAuthorization(_ context.Context, r *ClientRequest[oidc.DeviceAuthorizationRequest]) (*Response, error) {
|
func (UnimplementedServer) Authorize(ctx context.Context, r *Request[oidc.AuthRequest]) (*Redirect, error) {
|
||||||
|
return nil, unimplementedError(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UnimplementedServer) DeviceAuthorization(ctx context.Context, r *ClientRequest[oidc.DeviceAuthorizationRequest]) (*Response, error) {
|
||||||
return nil, unimplementedError(r.Request)
|
return nil, unimplementedError(r.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) VerifyClient(_ context.Context, r *Request[ClientCredentials]) (Client, error) {
|
func (UnimplementedServer) VerifyClient(ctx context.Context, r *Request[ClientCredentials]) (Client, error) {
|
||||||
return nil, unimplementedError(r)
|
return nil, unimplementedError(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) CodeExchange(_ context.Context, r *ClientRequest[oidc.AccessTokenRequest]) (*Response, error) {
|
func (UnimplementedServer) CodeExchange(ctx context.Context, r *ClientRequest[oidc.AccessTokenRequest]) (*Response, error) {
|
||||||
return nil, unimplementedGrantError(oidc.GrantTypeCode)
|
return nil, unimplementedGrantError(oidc.GrantTypeCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) RefreshToken(_ context.Context, r *ClientRequest[oidc.RefreshTokenRequest]) (*Response, error) {
|
func (UnimplementedServer) RefreshToken(ctx context.Context, r *ClientRequest[oidc.RefreshTokenRequest]) (*Response, error) {
|
||||||
return nil, unimplementedGrantError(oidc.GrantTypeRefreshToken)
|
return nil, unimplementedGrantError(oidc.GrantTypeRefreshToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) JWTProfile(_ context.Context, r *Request[oidc.JWTProfileGrantRequest]) (*Response, error) {
|
func (UnimplementedServer) JWTProfile(ctx context.Context, r *Request[oidc.JWTProfileGrantRequest]) (*Response, error) {
|
||||||
return nil, unimplementedGrantError(oidc.GrantTypeBearer)
|
return nil, unimplementedGrantError(oidc.GrantTypeBearer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) TokenExchange(_ context.Context, r *ClientRequest[oidc.TokenExchangeRequest]) (*Response, error) {
|
func (UnimplementedServer) TokenExchange(ctx context.Context, r *ClientRequest[oidc.TokenExchangeRequest]) (*Response, error) {
|
||||||
return nil, unimplementedGrantError(oidc.GrantTypeTokenExchange)
|
return nil, unimplementedGrantError(oidc.GrantTypeTokenExchange)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) ClientCredentialsExchange(_ context.Context, r *ClientRequest[oidc.ClientCredentialsRequest]) (*Response, error) {
|
func (UnimplementedServer) ClientCredentialsExchange(ctx context.Context, r *ClientRequest[oidc.ClientCredentialsRequest]) (*Response, error) {
|
||||||
return nil, unimplementedGrantError(oidc.GrantTypeClientCredentials)
|
return nil, unimplementedGrantError(oidc.GrantTypeClientCredentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) DeviceToken(_ context.Context, r *ClientRequest[oidc.DeviceAccessTokenRequest]) (*Response, error) {
|
func (UnimplementedServer) DeviceToken(ctx context.Context, r *ClientRequest[oidc.DeviceAccessTokenRequest]) (*Response, error) {
|
||||||
return nil, unimplementedGrantError(oidc.GrantTypeDeviceCode)
|
return nil, unimplementedGrantError(oidc.GrantTypeDeviceCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) Introspect(_ context.Context, r *ClientRequest[oidc.IntrospectionRequest]) (*Response, error) {
|
func (UnimplementedServer) Introspect(ctx context.Context, r *ClientRequest[oidc.IntrospectionRequest]) (*Response, error) {
|
||||||
return nil, unimplementedError(r.Request)
|
return nil, unimplementedError(r.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) UserInfo(_ context.Context, r *Request[oidc.UserInfoRequest]) (*Response, error) {
|
func (UnimplementedServer) UserInfo(ctx context.Context, r *Request[oidc.UserInfoRequest]) (*Response, error) {
|
||||||
return nil, unimplementedError(r)
|
return nil, unimplementedError(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) Revocation(_ context.Context, r *Request[oidc.RevocationRequest]) (*Response, error) {
|
func (UnimplementedServer) Revocation(ctx context.Context, r *ClientRequest[oidc.RevocationRequest]) (*Response, error) {
|
||||||
return nil, unimplementedError(r)
|
return nil, unimplementedError(r.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedServer) EndSession(_ context.Context, r *Request[oidc.EndSessionRequest]) (*Response, error) {
|
func (UnimplementedServer) EndSession(ctx context.Context, r *Request[oidc.EndSessionRequest]) (*Redirect, error) {
|
||||||
return nil, unimplementedError(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedServer) Keys(_ context.Context, r *Request[struct{}]) (*Response, error) {
|
|
||||||
return nil, unimplementedError(r)
|
return nil, unimplementedError(r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,11 @@ type LegacyServer struct {
|
||||||
readyProbes []ProbesFn
|
readyProbes []ProbesFn
|
||||||
}
|
}
|
||||||
|
|
||||||
type none = struct{}
|
func (s *LegacyServer) Health(_ context.Context, r *Request[struct{}]) (*Response, error) {
|
||||||
|
|
||||||
func (s *LegacyServer) Health(_ context.Context, r *Request[none]) (*Response, error) {
|
|
||||||
return NewResponse(Status{Status: "ok"}), nil
|
return NewResponse(Status{Status: "ok"}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LegacyServer) Ready(ctx context.Context, r *Request[none]) (*Response, error) {
|
func (s *LegacyServer) Ready(ctx context.Context, r *Request[struct{}]) (*Response, error) {
|
||||||
for _, probe := range s.readyProbes {
|
for _, probe := range s.readyProbes {
|
||||||
// shouldn't we run probes in Go routines?
|
// shouldn't we run probes in Go routines?
|
||||||
if err := probe(ctx); err != nil {
|
if err := probe(ctx); err != nil {
|
||||||
|
@ -32,12 +30,20 @@ func (s *LegacyServer) Ready(ctx context.Context, r *Request[none]) (*Response,
|
||||||
return NewResponse(Status{Status: "ok"}), nil
|
return NewResponse(Status{Status: "ok"}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LegacyServer) Discovery(ctx context.Context, r *Request[none]) (*Response, error) {
|
func (s *LegacyServer) Discovery(ctx context.Context, r *Request[struct{}]) (*Response, error) {
|
||||||
return NewResponse(
|
return NewResponse(
|
||||||
CreateDiscoveryConfig(ctx, s.provider, s.provider.Storage()),
|
CreateDiscoveryConfig(ctx, s.provider, s.provider.Storage()),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LegacyServer) Keys(ctx context.Context, r *Request[struct{}]) (*Response, error) {
|
||||||
|
keys, err := s.provider.Storage().KeySet(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewStatusError(err, http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return NewResponse(jsonWebKeySet(keys)), nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrAuthReqMissingClientID = errors.New("auth request is missing client_id")
|
ErrAuthReqMissingClientID = errors.New("auth request is missing client_id")
|
||||||
ErrAuthReqMissingRedirectURI = errors.New("auth request is missing redirect_uri")
|
ErrAuthReqMissingRedirectURI = errors.New("auth request is missing redirect_uri")
|
||||||
|
@ -264,14 +270,43 @@ func (s *LegacyServer) UserInfo(ctx context.Context, r *Request[oidc.UserInfoReq
|
||||||
return NewResponse(info), nil
|
return NewResponse(info), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LegacyServer) Revocation(_ context.Context, r *Request[oidc.RevocationRequest]) (*Response, error) {
|
func (s *LegacyServer) Revocation(ctx context.Context, r *ClientRequest[oidc.RevocationRequest]) (*Response, error) {
|
||||||
return nil, unimplementedError(r)
|
var subject string
|
||||||
|
doDecrypt := true
|
||||||
|
if r.Data.TokenTypeHint != "access_token" {
|
||||||
|
userID, tokenID, err := s.provider.Storage().GetRefreshTokenInfo(ctx, r.Client.GetID(), r.Data.Token)
|
||||||
|
if err != nil {
|
||||||
|
// An invalid refresh token means that we'll try other things (leaving doDecrypt==true)
|
||||||
|
if !errors.Is(err, ErrInvalidRefreshToken) {
|
||||||
|
return nil, RevocationError(oidc.ErrServerError().WithParent(err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.Data.Token = tokenID
|
||||||
|
subject = userID
|
||||||
|
doDecrypt = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if doDecrypt {
|
||||||
|
tokenID, userID, ok := getTokenIDAndSubjectForRevocation(ctx, s.provider, r.Data.Token)
|
||||||
|
if ok {
|
||||||
|
r.Data.Token = tokenID
|
||||||
|
subject = userID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := s.provider.Storage().RevokeToken(ctx, r.Data.Token, subject, r.Client.GetID()); err != nil {
|
||||||
|
return nil, RevocationError(err)
|
||||||
|
}
|
||||||
|
return NewResponse(nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LegacyServer) EndSession(_ context.Context, r *Request[oidc.EndSessionRequest]) (*Response, error) {
|
func (s *LegacyServer) EndSession(ctx context.Context, r *Request[oidc.EndSessionRequest]) (*Redirect, error) {
|
||||||
return nil, unimplementedError(r)
|
session, err := ValidateEndSessionRequest(ctx, r.Data, s.provider)
|
||||||
}
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
func (s *LegacyServer) Keys(_ context.Context, r *Request[struct{}]) (*Response, error) {
|
}
|
||||||
return nil, unimplementedError(r)
|
err = s.provider.Storage().TerminateSession(ctx, session.UserID, session.ClientID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewRedirect(session.RedirectURI), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,6 +131,11 @@ func ParseTokenRevocationRequest(r *http.Request, revoker Revoker) (token, token
|
||||||
}
|
}
|
||||||
|
|
||||||
func RevocationRequestError(w http.ResponseWriter, r *http.Request, err error) {
|
func RevocationRequestError(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
statusErr := RevocationError(err)
|
||||||
|
httphelper.MarshalJSONWithStatus(w, statusErr.parent, statusErr.statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RevocationError(err error) StatusError {
|
||||||
e := oidc.DefaultToServerError(err, err.Error())
|
e := oidc.DefaultToServerError(err, err.Error())
|
||||||
status := http.StatusBadRequest
|
status := http.StatusBadRequest
|
||||||
switch e.ErrorType {
|
switch e.ErrorType {
|
||||||
|
@ -139,7 +144,7 @@ func RevocationRequestError(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
case oidc.ServerError:
|
case oidc.ServerError:
|
||||||
status = 500
|
status = 500
|
||||||
}
|
}
|
||||||
httphelper.MarshalJSONWithStatus(w, e, status)
|
return NewStatusError(e, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTokenIDAndSubjectForRevocation(ctx context.Context, userinfoProvider UserinfoProvider, accessToken string) (string, string, bool) {
|
func getTokenIDAndSubjectForRevocation(ctx context.Context, userinfoProvider UserinfoProvider, accessToken string) (string, string, bool) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue