fix: support issuer host with path

This commit is contained in:
Sebastiao Pamplona 2022-02-10 12:57:54 +00:00
parent 219ba4e038
commit 648fe9c11d
6 changed files with 183 additions and 16 deletions

View file

@ -26,7 +26,7 @@ Check the `/example` folder where example code for different scenarios is locate
# oidc discovery http://localhost:9998/.well-known/openid-configuration # oidc discovery http://localhost:9998/.well-known/openid-configuration
CAOS_OIDC_DEV=1 go run github.com/caos/oidc/example/server/default CAOS_OIDC_DEV=1 go run github.com/caos/oidc/example/server/default
# start oidc web client # start oidc web client
CLIENT_ID=web CLIENT_SECRET=web ISSUER=http://localhost:9998/ SCOPES=openid PORT=5556 go run github.com/caos/oidc/example/client/app CLIENT_ID=web CLIENT_SECRET=web ISSUER=http://localhost:9998/path/to/my/server SCOPES=openid PORT=5556 go run github.com/caos/oidc/example/client/app
``` ```
- browser http://localhost:5556/login will redirect to op server - browser http://localhost:5556/login will redirect to op server

View file

@ -17,16 +17,16 @@ func main() {
ctx := context.Background() ctx := context.Background()
port := "9998" port := "9998"
config := &op.Config{ config := &op.Config{
Issuer: "http://localhost:9998/", Issuer: "http://localhost:9998/path/to/my/server",
CryptoKey: sha256.Sum256([]byte("test")), CryptoKey: sha256.Sum256([]byte("test")),
} }
storage := mock.NewAuthStorage() storage := mock.NewAuthStorage()
handler, err := op.NewOpenIDProvider(ctx, config, storage, op.WithCustomTokenEndpoint(op.NewEndpoint("test"))) handler, err := op.NewOpenIDProvider(ctx, config, storage, op.WithCustomTokenEndpoint(op.NewEndpointWithIssuersPath(config.Issuer, "test")))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
router := handler.HttpHandler().(*mux.Router) router := handler.HttpHandler().(*mux.Router)
router.Methods("GET").Path("/login").HandlerFunc(HandleLogin) router.Methods("GET").Path("/path/to/my/server/login").HandlerFunc(HandleLogin)
router.Methods("POST").Path("/login").HandlerFunc(HandleCallback) router.Methods("POST").Path("/login").HandlerFunc(HandleCallback)
server := &http.Server{ server := &http.Server{
Addr: ":" + port, Addr: ":" + port,
@ -68,5 +68,5 @@ func HandleLogin(w http.ResponseWriter, r *http.Request) {
func HandleCallback(w http.ResponseWriter, r *http.Request) { func HandleCallback(w http.ResponseWriter, r *http.Request) {
r.ParseForm() r.ParseForm()
client := r.FormValue("client") client := r.FormValue("client")
http.Redirect(w, r, "/authorize/callback?id="+client, http.StatusFound) http.Redirect(w, r, "/path/to/my/server/authorize/callback?id="+client, http.StatusFound)
} }

View file

@ -20,13 +20,13 @@ func Discover(w http.ResponseWriter, config *oidc.DiscoveryConfiguration) {
func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfiguration { func CreateDiscoveryConfig(c Configuration, s Signer) *oidc.DiscoveryConfiguration {
return &oidc.DiscoveryConfiguration{ return &oidc.DiscoveryConfiguration{
Issuer: c.Issuer(), Issuer: c.Issuer(),
AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(c.Issuer()), AuthorizationEndpoint: c.AuthorizationEndpoint().Absolute(getIssuerHost(c.Issuer())),
TokenEndpoint: c.TokenEndpoint().Absolute(c.Issuer()), TokenEndpoint: c.TokenEndpoint().Absolute(getIssuerHost(c.Issuer())),
IntrospectionEndpoint: c.IntrospectionEndpoint().Absolute(c.Issuer()), IntrospectionEndpoint: c.IntrospectionEndpoint().Absolute(getIssuerHost(c.Issuer())),
UserinfoEndpoint: c.UserinfoEndpoint().Absolute(c.Issuer()), UserinfoEndpoint: c.UserinfoEndpoint().Absolute(getIssuerHost(c.Issuer())),
RevocationEndpoint: c.RevocationEndpoint().Absolute(c.Issuer()), RevocationEndpoint: c.RevocationEndpoint().Absolute(getIssuerHost(c.Issuer())),
EndSessionEndpoint: c.EndSessionEndpoint().Absolute(c.Issuer()), EndSessionEndpoint: c.EndSessionEndpoint().Absolute(getIssuerHost(c.Issuer())),
JwksURI: c.KeysEndpoint().Absolute(c.Issuer()), JwksURI: c.KeysEndpoint().Absolute(getIssuerHost(c.Issuer())),
ScopesSupported: Scopes(c), ScopesSupported: Scopes(c),
ResponseTypesSupported: ResponseTypes(c), ResponseTypesSupported: ResponseTypes(c),
GrantTypesSupported: GrantTypes(c), GrantTypesSupported: GrantTypes(c),

View file

@ -15,6 +15,15 @@ func NewEndpointWithURL(path, url string) Endpoint {
return Endpoint{path: path, url: url} return Endpoint{path: path, url: url}
} }
func NewEndpointWithIssuersPath(issuer, path string) Endpoint {
issuerPath := getIssuerPath(issuer)
if len(issuerPath) > 0 {
issuerPath = issuerPath + "/"
}
return Endpoint{path: issuerPath + path}
}
func (e Endpoint) Relative() string { func (e Endpoint) Relative() string {
return relativeEndpoint(e.path) return relativeEndpoint(e.path)
} }

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"time" "time"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
@ -68,9 +69,15 @@ func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router
handlers.AllowedHeaders([]string{"authorization", "content-type"}), handlers.AllowedHeaders([]string{"authorization", "content-type"}),
handlers.AllowedOriginValidator(allowAllOrigins), handlers.AllowedOriginValidator(allowAllOrigins),
)) ))
router.HandleFunc(healthEndpoint, healthHandler)
router.HandleFunc(readinessEndpoint, readyHandler(o.Probes())) issuerPath := getIssuerPath(o.Issuer())
router.HandleFunc(oidc.DiscoveryEndpoint, discoveryHandler(o, o.Signer())) if len(issuerPath) > 0 {
issuerPath = "/" + issuerPath
}
router.HandleFunc(issuerPath+healthEndpoint, healthHandler)
router.HandleFunc(issuerPath+readinessEndpoint, readyHandler(o.Probes()))
router.HandleFunc(issuerPath+oidc.DiscoveryEndpoint, discoveryHandler(o, o.Signer()))
router.Handle(o.AuthorizationEndpoint().Relative(), intercept(authorizeHandler(o))) router.Handle(o.AuthorizationEndpoint().Relative(), intercept(authorizeHandler(o)))
router.NewRoute().Path(o.AuthorizationEndpoint().Relative()+"/callback").Queries("id", "{id}").Handler(intercept(authorizeCallbackHandler(o))) router.NewRoute().Path(o.AuthorizationEndpoint().Relative()+"/callback").Queries("id", "{id}").Handler(intercept(authorizeCallbackHandler(o)))
router.Handle(o.TokenEndpoint().Relative(), intercept(tokenHandler(o))) router.Handle(o.TokenEndpoint().Relative(), intercept(tokenHandler(o)))
@ -82,6 +89,36 @@ func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router
return router return router
} }
func NewDefaultEndpoints(issuer string) *endpoints {
return &endpoints{
Authorization: NewEndpointWithIssuersPath(issuer, defaultAuthorizationEndpoint),
Token: NewEndpointWithIssuersPath(issuer, defaultTokenEndpoint),
Introspection: NewEndpointWithIssuersPath(issuer, defaultIntrospectEndpoint),
Userinfo: NewEndpointWithIssuersPath(issuer, defaultUserinfoEndpoint),
Revocation: NewEndpointWithIssuersPath(issuer, defaultRevocationEndpoint),
EndSession: NewEndpointWithIssuersPath(issuer, defaultEndSessionEndpoint),
JwksURI: NewEndpointWithIssuersPath(issuer, defaultKeysEndpoint),
}
}
func getIssuerHost(issuer string) string {
split := strings.Split(issuer, "/")
if len(split) > 3 {
return strings.Join(split[:3], "/")
} else {
return issuer
}
}
func getIssuerPath(issuer string) string {
split := strings.Split(issuer, "/")
if len(split) > 3 && len(split[3]) > 0 {
return strings.Join(split[3:], "/")
} else {
return ""
}
}
type Config struct { type Config struct {
Issuer string Issuer string
CryptoKey [32]byte CryptoKey [32]byte
@ -114,7 +151,7 @@ func NewOpenIDProvider(ctx context.Context, config *Config, storage Storage, opO
o := &openidProvider{ o := &openidProvider{
config: config, config: config,
storage: storage, storage: storage,
endpoints: DefaultEndpoints, endpoints: NewDefaultEndpoints(config.Issuer),
timer: make(<-chan time.Time), timer: make(<-chan time.Time),
} }

121
pkg/op/op_test.go Normal file
View file

@ -0,0 +1,121 @@
package op
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetIssuerHost(t *testing.T) {
type args struct {
issuer string
}
type res struct {
path string
}
tests := []struct {
name string
args args
res res
}{
{
name: "just domain, without trailing forward slash",
args: args{
issuer: "https://localhost",
},
res: res{
path: "https://localhost",
},
},
{
name: "just domain, with trailing forward slash",
args: args{
issuer: "https://localhost/",
},
res: res{
path: "https://localhost",
},
},
{
name: "domain with path 1",
args: args{
issuer: "https://localhost/custompath",
},
res: res{
path: "https://localhost",
},
},
{
name: "domain with path 2",
args: args{
issuer: "https://localhost/custom/path",
},
res: res{
path: "https://localhost",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotPath := getIssuerHost(tt.args.issuer)
assert.Equal(t, tt.res.path, gotPath)
})
}
}
func TestGetIssuerPath(t *testing.T) {
type args struct {
issuer string
}
type res struct {
path string
}
tests := []struct {
name string
args args
res res
}{
{
name: "just domain, without trailing forward slash",
args: args{
issuer: "https://localhost",
},
res: res{
path: "",
},
},
{
name: "just domain, with trailing forward slash",
args: args{
issuer: "https://localhost/",
},
res: res{
path: "",
},
},
{
name: "domain with path 1",
args: args{
issuer: "https://localhost/custompath",
},
res: res{
path: "custompath",
},
},
{
name: "domain with path 2",
args: args{
issuer: "https://localhost/custom/path",
},
res: res{
path: "custom/path",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotPath := getIssuerPath(tt.args.issuer)
assert.Equal(t, tt.res.path, gotPath)
})
}
}