summaryrefslogtreecommitdiff
path: root/internal/oidc
diff options
context:
space:
mode:
authorLibravatar Tobi Smethurst <31960611+tsmethurst@users.noreply.github.com>2021-07-23 10:36:28 +0200
committerLibravatar GitHub <noreply@github.com>2021-07-23 10:36:28 +0200
commit05e9af089c3041fa162e4dca3b1c5906496e8e90 (patch)
tree6972d56a2ab5b5216ba7ec7c951605a775ac1c18 /internal/oidc
parentlil webfingy fix (#106) (diff)
downloadgotosocial-05e9af089c3041fa162e4dca3b1c5906496e8e90.tar.xz
Oidc (#109)
* add oidc config * inching forward with oidc idp * lil webfingy fix * bit more progress * further oidc * oidc now working * document dex config * replace broken images * add additional credits * tiny doc update * update * add oidc config * inching forward with oidc idp * bit more progress * further oidc * oidc now working * document dex config * replace broken images * add additional credits * tiny doc update * update * document * docs + comments
Diffstat (limited to 'internal/oidc')
-rw-r--r--internal/oidc/claims.go27
-rw-r--r--internal/oidc/handlecallback.go65
-rw-r--r--internal/oidc/idp.go121
3 files changed, 213 insertions, 0 deletions
diff --git a/internal/oidc/claims.go b/internal/oidc/claims.go
new file mode 100644
index 000000000..b58c4662b
--- /dev/null
+++ b/internal/oidc/claims.go
@@ -0,0 +1,27 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package oidc
+
+// Claims represents claims as found in an id_token returned from an OIDC flow.
+type Claims struct {
+ Email string `json:"email"`
+ EmailVerified bool `json:"email_verified"`
+ Groups []string `json:"groups"`
+ Name string `json:"name"`
+}
diff --git a/internal/oidc/handlecallback.go b/internal/oidc/handlecallback.go
new file mode 100644
index 000000000..2fbdc9309
--- /dev/null
+++ b/internal/oidc/handlecallback.go
@@ -0,0 +1,65 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package oidc
+
+import (
+ "context"
+ "errors"
+ "fmt"
+)
+
+func (i *idp) HandleCallback(ctx context.Context, code string) (*Claims, error) {
+ l := i.log.WithField("func", "HandleCallback")
+ if code == "" {
+ return nil, errors.New("code was empty string")
+ }
+
+ l.Debug("exchanging code for oauth2token")
+ oauth2Token, err := i.oauth2Config.Exchange(ctx, code)
+ if err != nil {
+ return nil, fmt.Errorf("error exchanging code for oauth2token: %s", err)
+ }
+
+ l.Debug("extracting id_token")
+ rawIDToken, ok := oauth2Token.Extra("id_token").(string)
+ if !ok {
+ return nil, errors.New("no id_token in oauth2token")
+ }
+ l.Debugf("raw id token: %s", rawIDToken)
+
+ // Parse and verify ID Token payload.
+ l.Debug("verifying id_token")
+ idTokenVerifier := i.provider.Verifier(i.oidcConf)
+ idToken, err := idTokenVerifier.Verify(ctx, rawIDToken)
+ if err != nil {
+ return nil, fmt.Errorf("could not verify id token: %s", err)
+ }
+
+ l.Debug("extracting claims from id_token")
+ claims := &Claims{}
+ if err := idToken.Claims(claims); err != nil {
+ return nil, fmt.Errorf("could not parse claims from idToken: %s", err)
+ }
+
+ return claims, nil
+}
+
+func (i *idp) AuthCodeURL(state string) string {
+ return i.oauth2Config.AuthCodeURL(state)
+}
diff --git a/internal/oidc/idp.go b/internal/oidc/idp.go
new file mode 100644
index 000000000..8758f6aad
--- /dev/null
+++ b/internal/oidc/idp.go
@@ -0,0 +1,121 @@
+/*
+ GoToSocial
+ Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package oidc
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/coreos/go-oidc/v3/oidc"
+ "github.com/sirupsen/logrus"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+ "golang.org/x/oauth2"
+)
+
+const (
+ // CallbackPath is the API path for receiving callback tokens from external OIDC providers
+ CallbackPath = "/auth/callback"
+)
+
+// IDP contains logic for parsing an OIDC access code into a set of claims by calling an external OIDC provider.
+type IDP interface {
+ // HandleCallback accepts a context (pass the context from the http.Request), and an oauth2 code as returned from a successful
+ // login through an OIDC provider. It uses the code to request a token from the OIDC provider, which should contain an id_token
+ // with a set of claims.
+ //
+ // Note that this function *does not* verify state. That should be handled by the caller *before* this function is called.
+ HandleCallback(ctx context.Context, code string) (*Claims, error)
+ // AuthCodeURL returns the proper redirect URL for this IDP, for redirecting requesters to the correct OIDC endpoint.
+ AuthCodeURL(state string) string
+}
+
+type idp struct {
+ oauth2Config oauth2.Config
+ provider *oidc.Provider
+ oidcConf *oidc.Config
+ log *logrus.Logger
+}
+
+// NewIDP returns a new IDP configured with the given config and logger.
+// If the passed config contains a nil value for the OIDCConfig, or OIDCConfig.Enabled
+// is set to false, then nil, nil will be returned. If OIDCConfig.Enabled is true,
+// then the other OIDC config fields must also be set.
+func NewIDP(config *config.Config, log *logrus.Logger) (IDP, error) {
+
+ // oidc isn't enabled so we don't need to do anything
+ if config.OIDCConfig == nil || !config.OIDCConfig.Enabled {
+ return nil, nil
+ }
+
+ // validate config fields
+ if config.OIDCConfig.IDPName == "" {
+ return nil, fmt.Errorf("not set: IDPName")
+ }
+ if config.OIDCConfig.Issuer == "" {
+ return nil, fmt.Errorf("not set: Issuer")
+ }
+ if config.OIDCConfig.ClientID == "" {
+ return nil, fmt.Errorf("not set: ClientID")
+ }
+ if config.OIDCConfig.ClientSecret == "" {
+ return nil, fmt.Errorf("not set: ClientSecret")
+ }
+ if len(config.OIDCConfig.Scopes) == 0 {
+ return nil, fmt.Errorf("not set: Scopes")
+ }
+
+ provider, err := oidc.NewProvider(context.Background(), config.OIDCConfig.Issuer)
+ if err != nil {
+ return nil, err
+ }
+
+ oauth2Config := oauth2.Config{
+ // client_id and client_secret of the client.
+ ClientID: config.OIDCConfig.ClientID,
+ ClientSecret: config.OIDCConfig.ClientSecret,
+
+ // The redirectURL.
+ RedirectURL: fmt.Sprintf("%s://%s%s", config.Protocol, config.Host, CallbackPath),
+
+ // Discovery returns the OAuth2 endpoints.
+ Endpoint: provider.Endpoint(),
+
+ // "openid" is a required scope for OpenID Connect flows.
+ //
+ // Other scopes, such as "groups" can be requested.
+ Scopes: config.OIDCConfig.Scopes,
+ }
+
+ // create a config for verifier creation
+ oidcConf := &oidc.Config{
+ ClientID: config.OIDCConfig.ClientID,
+ }
+ if config.OIDCConfig.SkipVerification {
+ oidcConf.SkipClientIDCheck = true
+ oidcConf.SkipExpiryCheck = true
+ oidcConf.SkipIssuerCheck = true
+ }
+
+ return &idp{
+ oauth2Config: oauth2Config,
+ oidcConf: oidcConf,
+ provider: provider,
+ log: log,
+ }, nil
+}