From 98e473f95e34a5af36e3933110857925510dbb96 Mon Sep 17 00:00:00 2001 From: David Parrish Date: Mon, 15 Apr 2024 11:39:00 +1000 Subject: [PATCH] feat: Add OpenID Connect support --- .../configuration/idps/openid/_index.en.md | 36 ++++++++++++++++++ .../configuration/idps/openid/domain-auth.png | Bin 0 -> 26799 bytes internal/config/oauth.go | 22 ++++++++++- internal/config/secrets.go | 21 +++++----- internal/data/models.go | 8 ++-- swagger/swagger.yml | 2 + 6 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 docs/content/configuration/idps/openid/_index.en.md create mode 100644 docs/content/configuration/idps/openid/domain-auth.png diff --git a/docs/content/configuration/idps/openid/_index.en.md b/docs/content/configuration/idps/openid/_index.en.md new file mode 100644 index 00000000..170116e5 --- /dev/null +++ b/docs/content/configuration/idps/openid/_index.en.md @@ -0,0 +1,36 @@ +title: Login via OpenID Connect +description: How to configure OAuth2 login via a generic OpenID Connect provider +tags: + - configuration + - identity provider + - idp + - authentication + - OAuth2 + - OpenID + - OpenID Connect +seeAlso: + - /configuration/backend/secrets + - /configuration/frontend/domain/authentication +--- + +To let your users sign in with your OpenID Connect provider, follow the below steps. + + + +1. Create a client and secret with whatever your OpenID Connect provider is (for example Keycloak). +2. Update the [secrets configuration](/configuration/backend/secrets) with the above data (**API Key** and **API Key Secret**; Bearer Token is not used): +```yaml +... +idp: + openid: + key: HgcP46jXCRbouchsgqJgkHPZF + secret: Bqd7r8TaS5bcFReV7bcE2YQMt4xIrwrFSaUQ8KVJENk7JndSxV + callback_url: https://comentario.my.domain/api/oauth/openid/callback + discovery_url: https://myauthprovider.my.domain/auth/realms/master/.well-known/openid-configuration +... +``` +3. Restart Comentario. You should now see OpenID under **Configured federated identity providers** on the Static configuration page of the Administration UI. +4. Still in the Admin UI, navigate to the desired domain properties and tick off **OpenID** on the [Authentication tab](/configuration/frontend/domain/authentication), then click **Save**. + {{< imgfig "domain-auth.png" "" "border shadow" >}} + +That's it! Your users should now be able to login using the **OpenID Connect** button in the Login dialog. diff --git a/docs/content/configuration/idps/openid/domain-auth.png b/docs/content/configuration/idps/openid/domain-auth.png new file mode 100644 index 0000000000000000000000000000000000000000..ea51e728cfddd403bbbd20034962bb3875ea4873 GIT binary patch literal 26799 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 MboFyt=akR{05>Z_=l}o! literal 0 HcmV?d00001 diff --git a/internal/config/oauth.go b/internal/config/oauth.go index f800a8cb..0e884ea3 100644 --- a/internal/config/oauth.go +++ b/internal/config/oauth.go @@ -1,14 +1,16 @@ package config import ( + "strings" + "github.com/markbates/goth" "github.com/markbates/goth/providers/facebook" "github.com/markbates/goth/providers/github" "github.com/markbates/goth/providers/gitlab" "github.com/markbates/goth/providers/google" "github.com/markbates/goth/providers/linkedin" + "github.com/markbates/goth/providers/openidConnect" "github.com/markbates/goth/providers/twitter" - "strings" ) // oauthConfigure configures federated (OAuth) authentication @@ -19,6 +21,7 @@ func oauthConfigure() { googleOauthConfigure() linkedinOauthConfigure() twitterOauthConfigure() + openIdConnectConfiugure() } // facebookOauthConfigure configures federated authentication via Facebook @@ -137,3 +140,20 @@ func twitterOauthConfigure() { URLForAPI("oauth/twitter/callback", nil)), ) } + +// openIdConnectConfiugure configures federated authentication via a generic OAuth2 provider +func openIdConnectConfiugure() { + if !SecretsConfig.IdP.OpenIdConnect.Usable() { + logger.Debug("OpenID Connect auth isn't configured or enabled") + return + } + + logger.Infof("Registering OpenID Connect provider for client %s", SecretsConfig.IdP.OpenIdConnect.Key) + + openidConnect, err := openidConnect.New(SecretsConfig.IdP.OpenIdConnect.Key, SecretsConfig.IdP.OpenIdConnect.Secret, SecretsConfig.IdP.OpenIdConnect.CallbackUrl, SecretsConfig.IdP.OpenIdConnect.DiscoveryUrl) + if err != nil { + logger.Errorf("Error registering OpenID Connect provider: %v", err) + return + } + goth.UseProviders(openidConnect) +} diff --git a/internal/config/secrets.go b/internal/config/secrets.go index 7cbdbb28..f281a63d 100644 --- a/internal/config/secrets.go +++ b/internal/config/secrets.go @@ -11,9 +11,11 @@ const ( // KeySecret is a record containing a key and a secret type KeySecret struct { - Disable bool `yaml:"disable"` // Can be used to forcefully disable the corresponding functionality - Key string `yaml:"key"` // Public key - Secret string `yaml:"secret"` // Private key + Disable bool `yaml:"disable"` // Can be used to forcefully disable the corresponding functionality + Key string `yaml:"key"` // Public key + Secret string `yaml:"secret"` // Private key + CallbackUrl string `yaml:"callback_url"` // Callback URL + DiscoveryUrl string `yaml:"discovery_url"` // Discovery URL } // Usable returns whether the instance isn't disabled and the key and the secret are filled in @@ -56,12 +58,13 @@ var SecretsConfig = &struct { // Federated identity provider settings IdP struct { - Facebook KeySecret `yaml:"facebook"` // Facebook auth config - GitHub KeySecret `yaml:"github"` // GitHub auth config - GitLab KeySecret `yaml:"gitlab"` // GitLab auth config - Google KeySecret `yaml:"google"` // Google auth config - LinkedIn KeySecret `yaml:"linkedin"` // LinkedIn auth config - Twitter KeySecret `yaml:"twitter"` // Twitter auth config + Facebook KeySecret `yaml:"facebook"` // Facebook auth config + GitHub KeySecret `yaml:"github"` // GitHub auth config + GitLab KeySecret `yaml:"gitlab"` // GitLab auth config + Google KeySecret `yaml:"google"` // Google auth config + LinkedIn KeySecret `yaml:"linkedin"` // LinkedIn auth config + Twitter KeySecret `yaml:"twitter"` // Twitter auth config + OpenIdConnect KeySecret `yaml:"openid"` // OpenID Connect auth config } `yaml:"idp"` // Extension settings diff --git a/internal/data/models.go b/internal/data/models.go index fa161bab..9bb041ac 100644 --- a/internal/data/models.go +++ b/internal/data/models.go @@ -5,6 +5,10 @@ import ( "encoding/base64" "encoding/hex" "fmt" + "net/http" + "strings" + "time" + "github.com/avct/uasurfer" "github.com/doug-martin/goqu/v9" "github.com/doug-martin/goqu/v9/exp" @@ -15,9 +19,6 @@ import ( "gitlab.com/comentario/comentario/internal/api/models" "gitlab.com/comentario/comentario/internal/util" "golang.org/x/crypto/bcrypt" - "net/http" - "strings" - "time" ) // AnonymousUser is a predefined "anonymous" user, identified by a special UUID ('00000000-0000-0000-0000-000000000000') @@ -84,6 +85,7 @@ var FederatedIdProviders = map[models.FederatedIdpID]FederatedIdentityProvider{ models.FederatedIdpIDGoogle: {ID: models.FederatedIdpIDGoogle, Name: "Google", Icon: "google", GothID: "google"}, models.FederatedIdpIDLinkedin: {ID: models.FederatedIdpIDLinkedin, Name: "LinkedIn", Icon: "linkedin", GothID: "linkedin"}, models.FederatedIdpIDTwitter: {ID: models.FederatedIdpIDTwitter, Name: "Twitter", Icon: "twitter", GothID: "twitter"}, + models.FederatedIdpIDOpenid: {ID: models.FederatedIdpIDOpenid, Name: "OpenID Connect", Icon: "google", GothID: "openid-connect"}, } // GetFederatedIdP returns whether federated identity provider is known and configured, and if yes, its Provider diff --git a/swagger/swagger.yml b/swagger/swagger.yml index a92c8c2d..da0ea298 100644 --- a/swagger/swagger.yml +++ b/swagger/swagger.yml @@ -459,6 +459,7 @@ definitions: - google - linkedin - twitter + - openid x-isnullable: false host: @@ -1051,6 +1052,7 @@ parameters: - google - linkedin - twitter + - openid - sso # SSO is a special case of federated authentication, which requires additional configuration for a specific domain pathDailyMetric: -- GitLab