diff options
author | 2021-07-23 10:36:28 +0200 | |
---|---|---|
committer | 2021-07-23 10:36:28 +0200 | |
commit | 05e9af089c3041fa162e4dca3b1c5906496e8e90 (patch) | |
tree | 6972d56a2ab5b5216ba7ec7c951605a775ac1c18 /internal/oidc | |
parent | lil webfingy fix (#106) (diff) | |
download | gotosocial-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.go | 27 | ||||
-rw-r--r-- | internal/oidc/handlecallback.go | 65 | ||||
-rw-r--r-- | internal/oidc/idp.go | 121 |
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 +} |