From d647eed22ac5913882f60b33330b60aee6ac9238 Mon Sep 17 00:00:00 2001 From: Jack Merrill <8814123+jackmerrill@users.noreply.github.com> Date: Thu, 26 Sep 2024 01:06:36 -0400 Subject: [PATCH] feat: add OIDC WebFinger support (#3245) * feat: add WebFinger support * lint: used gofumpt * oidc: ensure webfinger rel is checked --- authz/authz.go | 1 + controllers/oidc_discovery.go | 34 ++++++++++++++++++++++- object/oidc_discovery.go | 52 +++++++++++++++++++++++++++++++++++ routers/router.go | 1 + 4 files changed, 87 insertions(+), 1 deletion(-) diff --git a/authz/authz.go b/authz/authz.go index aae7ed38..81389723 100644 --- a/authz/authz.go +++ b/authz/authz.go @@ -77,6 +77,7 @@ p, *, *, POST, /api/verify-code, *, * p, *, *, POST, /api/reset-email-or-phone, *, * p, *, *, POST, /api/upload-resource, *, * p, *, *, GET, /.well-known/openid-configuration, *, * +p, *, *, GET, /.well-known/webfinger, *, * p, *, *, *, /.well-known/jwks, *, * p, *, *, GET, /api/get-saml-login, *, * p, *, *, POST, /api/acs, *, * diff --git a/controllers/oidc_discovery.go b/controllers/oidc_discovery.go index 8986c80f..9598e1a3 100644 --- a/controllers/oidc_discovery.go +++ b/controllers/oidc_discovery.go @@ -14,7 +14,11 @@ package controllers -import "github.com/casdoor/casdoor/object" +import ( + "strings" + + "github.com/casdoor/casdoor/object" +) // GetOidcDiscovery // @Title GetOidcDiscovery @@ -42,3 +46,31 @@ func (c *RootController) GetJwks() { c.Data["json"] = jwks c.ServeJSON() } + +// GetWebFinger +// @Title GetWebFinger +// @Tag OIDC API +// @Param resource query string true "resource" +// @Success 200 {object} object.WebFinger +// @router /.well-known/webfinger [get] +func (c *RootController) GetWebFinger() { + resource := c.Input().Get("resource") + rels := []string{} + host := c.Ctx.Request.Host + + for key, value := range c.Input() { + if strings.HasPrefix(key, "rel") { + rels = append(rels, value...) + } + } + + webfinger, err := object.GetWebFinger(resource, rels, host) + if err != nil { + c.ResponseError(err.Error()) + return + } + + c.Data["json"] = webfinger + c.Ctx.Output.ContentType("application/jrd+json") + c.ServeJSON() +} diff --git a/object/oidc_discovery.go b/object/oidc_discovery.go index f6671f57..eb6695c9 100644 --- a/object/oidc_discovery.go +++ b/object/oidc_discovery.go @@ -44,6 +44,18 @@ type OidcDiscovery struct { EndSessionEndpoint string `json:"end_session_endpoint"` } +type WebFinger struct { + Subject string `json:"subject"` + Links []WebFingerLink `json:"links"` + Aliases *[]string `json:"aliases,omitempty"` + Properties *map[string]string `json:"properties,omitempty"` +} + +type WebFingerLink struct { + Rel string `json:"rel"` + Href string `json:"href"` +} + func isIpAddress(host string) bool { // Attempt to split the host and port, ignoring the error hostWithoutPort, _, err := net.SplitHostPort(host) @@ -160,3 +172,43 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) { return jwks, nil } + +func GetWebFinger(resource string, rels []string, host string) (WebFinger, error) { + wf := WebFinger{} + + resourceSplit := strings.Split(resource, ":") + + if len(resourceSplit) != 2 { + return wf, fmt.Errorf("invalid resource") + } + + resourceType := resourceSplit[0] + resourceValue := resourceSplit[1] + + oidcDiscovery := GetOidcDiscovery(host) + + switch resourceType { + case "acct": + user, err := GetUserByEmailOnly(resourceValue) + if err != nil { + return wf, err + } + + if user == nil { + return wf, fmt.Errorf("user not found") + } + + wf.Subject = resource + + for _, rel := range rels { + if rel == "http://openid.net/specs/connect/1.0/issuer" { + wf.Links = append(wf.Links, WebFingerLink{ + Rel: "http://openid.net/specs/connect/1.0/issuer", + Href: oidcDiscovery.Issuer, + }) + } + } + } + + return wf, nil +} diff --git a/routers/router.go b/routers/router.go index 09f18c80..37fba391 100644 --- a/routers/router.go +++ b/routers/router.go @@ -290,6 +290,7 @@ func initAPI() { beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery") beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks") + beego.Router("/.well-known/webfinger", &controllers.RootController{}, "GET:GetWebFinger") beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceValidate") beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")