Skip to content

Commit

Permalink
Merge pull request #14 from caos/feat/end-session
Browse files Browse the repository at this point in the history
feat: terminate session (front channel logout)
  • Loading branch information
livio-a authored Mar 3, 2020
2 parents 4cf6c6d + e8f3010 commit 3d46c17
Show file tree
Hide file tree
Showing 16 changed files with 208 additions and 14 deletions.
6 changes: 6 additions & 0 deletions example/internal/mock/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ func (s *AuthStorage) AuthRequestByID(_ context.Context, id string) (op.AuthRequ
func (s *AuthStorage) CreateToken(_ context.Context, authReq op.AuthRequest) (string, time.Time, error) {
return authReq.GetID(), time.Now().UTC().Add(5 * time.Minute), nil
}
func (s *AuthStorage) TerminateSession(_ context.Context, userID, clientID string) error {
return nil
}
func (s *AuthStorage) GetSigningKey(_ context.Context, keyCh chan<- jose.SigningKey, _ chan<- error, _ <-chan time.Time) {
keyCh <- jose.SigningKey{Algorithm: jose.RS256, Key: s.key}
}
Expand Down Expand Up @@ -233,6 +236,9 @@ func (c *ConfClient) RedirectURIs() []string {
"https://op.certification.openid.net:62064/authz_post",
}
}
func (c *ConfClient) PostLogoutRedirectURIs() []string {
return []string{}
}

func (c *ConfClient) LoginURL(id string) string {
return "login?id=" + id
Expand Down
7 changes: 7 additions & 0 deletions pkg/oidc/session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package oidc

type EndSessionRequest struct {
IdTokenHint string `schema:"id_token_hint"`
PostLogoutRedirectURI string `schema:"post_logout_redirect_uri"`
State string `schema:"state"`
}
1 change: 1 addition & 0 deletions pkg/op/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
type Client interface {
GetID() string
RedirectURIs() []string
PostLogoutRedirectURIs() []string
ApplicationType() ApplicationType
GetAuthMethod() AuthMethod
LoginURL(string) string
Expand Down
1 change: 1 addition & 0 deletions pkg/op/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Configuration interface {
AuthorizationEndpoint() Endpoint
TokenEndpoint() Endpoint
UserinfoEndpoint() Endpoint
EndSessionEndpoint() Endpoint
KeysEndpoint() Endpoint

AuthMethodPostSupported() bool
Expand Down
46 changes: 43 additions & 3 deletions pkg/op/default_op.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package op

import (
"context"
"errors"
"net/http"
"time"

Expand All @@ -10,13 +11,15 @@ import (

"github.com/caos/logging"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/rp"
)

const (
defaultAuthorizationEndpoint = "authorize"
defaulTokenEndpoint = "oauth/token"
defaultIntrospectEndpoint = "introspect"
defaultUserinfoEndpoint = "userinfo"
defaultEndSessionEndpoint = "end_session"
defaultKeysEndpoint = "keys"

AuthMethodBasic AuthMethod = "client_secret_basic"
Expand All @@ -30,6 +33,7 @@ var (
Token: NewEndpoint(defaulTokenEndpoint),
IntrospectionEndpoint: NewEndpoint(defaultIntrospectEndpoint),
Userinfo: NewEndpoint(defaultUserinfoEndpoint),
EndSessionEndpoint: NewEndpoint(defaultEndSessionEndpoint),
JwksURI: NewEndpoint(defaultKeysEndpoint),
}
)
Expand All @@ -39,6 +43,7 @@ type DefaultOP struct {
endpoints *endpoints
storage Storage
signer Signer
verifier rp.Verifier
crypto Crypto
http *http.Server
decoder *schema.Decoder
Expand All @@ -49,8 +54,9 @@ type DefaultOP struct {
}

type Config struct {
Issuer string
CryptoKey [32]byte
Issuer string
CryptoKey [32]byte
DefaultLogoutRedirectURI string
// ScopesSupported: oidc.SupportedScopes,
// ResponseTypesSupported: responseTypes,
// GrantTypesSupported: oidc.SupportedGrantTypes,
Expand Down Expand Up @@ -164,6 +170,8 @@ func NewDefaultOP(ctx context.Context, config *Config, storage Storage, opOpts .
p.signer = NewDefaultSigner(ctx, storage, keyCh)
go p.ensureKey(ctx, storage, keyCh, p.timer)

p.verifier = rp.NewDefaultVerifier(config.Issuer, "", p, rp.WithIgnoreAudience())

router := CreateRouter(p, p.interceptor)
p.http = &http.Server{
Addr: ":" + config.Port,
Expand Down Expand Up @@ -195,6 +203,10 @@ func (p *DefaultOP) UserinfoEndpoint() Endpoint {
return Endpoint(p.endpoints.Userinfo)
}

func (p *DefaultOP) EndSessionEndpoint() Endpoint {
return Endpoint(p.endpoints.EndSessionEndpoint)
}

func (p *DefaultOP) KeysEndpoint() Endpoint {
return Endpoint(p.endpoints.JwksURI)
}
Expand All @@ -215,6 +227,23 @@ func (p *DefaultOP) HandleDiscovery(w http.ResponseWriter, r *http.Request) {
Discover(w, CreateDiscoveryConfig(p, p.Signer()))
}

func (p *DefaultOP) VerifySignature(ctx context.Context, jws *jose.JSONWebSignature) ([]byte, error) {
keyID := ""
for _, sig := range jws.Signatures {
keyID = sig.Header.KeyID
break
}
keySet, err := p.Storage().GetKeySet(ctx)
if err != nil {
return nil, errors.New("error fetching keys")
}
payload, err, ok := rp.CheckKey(keyID, keySet.Keys, jws)
if !ok {
return nil, errors.New("invalid kid")
}
return payload, err
}

func (p *DefaultOP) Decoder() *schema.Decoder {
return p.decoder
}
Expand Down Expand Up @@ -257,7 +286,7 @@ func (p *DefaultOP) HandleAuthorizeCallback(w http.ResponseWriter, r *http.Reque
func (p *DefaultOP) HandleExchange(w http.ResponseWriter, r *http.Request) {
reqType := r.FormValue("grant_type")
if reqType == "" {
ExchangeRequestError(w, r, ErrInvalidRequest("grant_type missing"))
RequestError(w, r, ErrInvalidRequest("grant_type missing"))
return
}
if reqType == string(oidc.GrantTypeCode) {
Expand All @@ -271,6 +300,17 @@ func (p *DefaultOP) HandleUserinfo(w http.ResponseWriter, r *http.Request) {
Userinfo(w, r, p)
}

func (p *DefaultOP) HandleEndSession(w http.ResponseWriter, r *http.Request) {
EndSession(w, r, p)
}

func (p *DefaultOP) DefaultLogoutRedirectURI() string {
return p.config.DefaultLogoutRedirectURI
}
func (p *DefaultOP) IDTokenVerifier() rp.Verifier {
return p.verifier
}

func (p *DefaultOP) ensureKey(ctx context.Context, storage Storage, keyCh chan<- jose.SigningKey, timer <-chan time.Time) {
count := 0
timer = time.After(0)
Expand Down
4 changes: 2 additions & 2 deletions pkg/op/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfigurati
AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()),
TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()),
// IntrospectionEndpoint: c.Intro().Absolute(c.Issuer()),
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
// EndSessionEndpoint: c.TokenEndpoint().Absolute(c.Issuer())(c.EndSessionEndpoint),
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()),
EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()),
// CheckSessionIframe: c.TokenEndpoint().Absolute(c.Issuer())(c.CheckSessionIframe),
JwksURI: c.KeysEndpoint().Absolute(c.Issuer()),
ScopesSupported: Scopes(c),
Expand Down
2 changes: 1 addition & 1 deletion pkg/op/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func AuthRequestError(w http.ResponseWriter, r *http.Request, authReq ErrAuthReq
http.Redirect(w, r, url, http.StatusFound)
}

func ExchangeRequestError(w http.ResponseWriter, r *http.Request, err error) {
func RequestError(w http.ResponseWriter, r *http.Request, err error) {
e, ok := err.(*OAuthError)
if !ok {
e = new(OAuthError)
Expand Down
14 changes: 14 additions & 0 deletions pkg/op/mock/client.mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions pkg/op/mock/configuration.mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions pkg/op/mock/storage.mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions pkg/op/mock/storage.mock.impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ func (c *ConfClient) RedirectURIs() []string {
"custom://callback",
}
}
func (c *ConfClient) PostLogoutRedirectURIs() []string {
return []string{}
}

func (c *ConfClient) LoginURL(id string) string {
return "login?id=" + id
Expand Down
2 changes: 2 additions & 0 deletions pkg/op/op.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type OpenIDProvider interface {
HandleAuthorizeCallback(w http.ResponseWriter, r *http.Request)
HandleExchange(w http.ResponseWriter, r *http.Request)
HandleUserinfo(w http.ResponseWriter, r *http.Request)
HandleEndSession(w http.ResponseWriter, r *http.Request)
HandleKeys(w http.ResponseWriter, r *http.Request)
HttpHandler() *http.Server
}
Expand All @@ -49,6 +50,7 @@ func CreateRouter(o OpenIDProvider, h HttpInterceptor) *mux.Router {
router.HandleFunc(o.AuthorizationEndpoint().Relative()+"/{id}", h(o.HandleAuthorizeCallback))
router.HandleFunc(o.TokenEndpoint().Relative(), h(o.HandleExchange))
router.HandleFunc(o.UserinfoEndpoint().Relative(), o.HandleUserinfo)
router.HandleFunc(o.EndSessionEndpoint().Relative(), h(o.HandleEndSession))
router.HandleFunc(o.KeysEndpoint().Relative(), o.HandleKeys)
return router
}
Expand Down
72 changes: 71 additions & 1 deletion pkg/op/session.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,76 @@
package op

import "github.com/caos/oidc/pkg/oidc"
import (
"context"
"net/http"

"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/rp"
"github.com/gorilla/schema"
)

type SessionEnder interface {
Decoder() *schema.Decoder
Storage() Storage
IDTokenVerifier() rp.Verifier
DefaultLogoutRedirectURI() string
}

func EndSession(w http.ResponseWriter, r *http.Request, ender SessionEnder) {
req, err := ParseEndSessionRequest(r, ender.Decoder())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
session, err := ValidateEndSessionRequest(r.Context(), req, ender)
if err != nil {
RequestError(w, r, err)
return
}
err = ender.Storage().TerminateSession(r.Context(), session.UserID, session.Client.GetID())
if err != nil {
RequestError(w, r, ErrServerError("error terminating session"))
return
}
http.Redirect(w, r, session.RedirectURI, http.StatusFound)
}

func ParseEndSessionRequest(r *http.Request, decoder *schema.Decoder) (*oidc.EndSessionRequest, error) {
err := r.ParseForm()
if err != nil {
return nil, ErrInvalidRequest("error parsing form")
}
req := new(oidc.EndSessionRequest)
err = decoder.Decode(req, r.Form)
if err != nil {
return nil, ErrInvalidRequest("error decoding form")
}
return req, nil
}

func ValidateEndSessionRequest(ctx context.Context, req *oidc.EndSessionRequest, ender SessionEnder) (*EndSessionRequest, error) {
session := new(EndSessionRequest)
claims, err := ender.IDTokenVerifier().Verify(ctx, "", req.IdTokenHint)
if err != nil {
return nil, ErrInvalidRequest("id_token_hint invalid")
}
session.UserID = claims.Subject
session.Client, err = ender.Storage().GetClientByClientID(ctx, claims.AuthorizedParty)
if err != nil {
return nil, ErrServerError("")
}
if req.PostLogoutRedirectURI == "" {
session.RedirectURI = ender.DefaultLogoutRedirectURI()
return session, nil
}
for _, uri := range session.Client.PostLogoutRedirectURIs() {
if uri == req.PostLogoutRedirectURI {
session.RedirectURI = uri + "?state=" + req.State
return session, nil
}
}
return nil, ErrInvalidRequest("post_logout_redirect_uri invalid")
}

func NeedsExistingSession(authRequest *oidc.AuthRequest) bool {
if authRequest == nil {
Expand Down
8 changes: 8 additions & 0 deletions pkg/op/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type AuthStorage interface {

CreateToken(context.Context, AuthRequest) (string, time.Time, error)

TerminateSession(context.Context, string, string) error

GetSigningKey(context.Context, chan<- jose.SigningKey, chan<- error, <-chan time.Time)
GetKeySet(context.Context) (*jose.JSONWebKeySet, error)
SaveNewKeyPair(context.Context) error
Expand Down Expand Up @@ -53,3 +55,9 @@ type AuthRequest interface {
GetSubject() string
Done() bool
}

type EndSessionRequest struct {
UserID string
Client Client
RedirectURI string
}
Loading

0 comments on commit 3d46c17

Please sign in to comment.