Skip to content

Commit 07bdb10

Browse files
committed
integrate PR mxschmitt#132
1 parent 0d7fe2c commit 07bdb10

File tree

8 files changed

+131
-2
lines changed

8 files changed

+131
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
- Expirable Links
1616
- URL deletion
1717
- Multiple authorization strategies:
18-
- Local authorization via OAuth 2.0 (Google, GitHub, Microsoft, and Okta)
18+
- Local authorization via OAuth 2.0 (Google, GitHub, Microsoft, and Okta, or generic OIDC like Keycloak)
1919
- Proxy authorization for running behind e.g. [Google IAP](https://cloud.google.com/iap/)
2020
- Easy [ShareX](https://github.com/ShareX/ShareX) integration
2121
- Dockerizable

config/example.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ Okta: # only relevant when using the oauth authbackend
2222
ClientID: replace me
2323
ClientSecret: 'replace me'
2424
EndpointURL: # (MANDATORY) Issuer URL from the OAuth API => Authorization Servers in Okta
25+
GenericOIDC: # only relevant when using the oauth authbackend
26+
ClientID: replace me
27+
ClientSecret: 'replace me'
28+
EndpointURL: # (MANDATORY) Base URL, which will be auto-discovered with '.well-known/openid-configuration'
2529
Proxy: # only relevant when using the proxy authbackend
2630
RequireUserHeader: false # If true, will reject connections that do not have the UserHeader set
2731
UserHeader: "X-Goog-Authenticated-User-ID" # pull the unique user ID from this header

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.15
55
require (
66
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
77
github.com/boltdb/bolt v1.3.1
8+
github.com/coreos/go-oidc v2.2.1+incompatible
89
github.com/dgrijalva/jwt-go v3.2.0+incompatible
910
github.com/gin-contrib/sessions v0.0.3
1011
github.com/gin-gonic/gin v1.6.3
@@ -15,9 +16,11 @@ require (
1516
github.com/mxschmitt/golang-env-struct v0.0.0-20181017075525-0c54aeca8397
1617
github.com/pborman/uuid v1.2.1
1718
github.com/pkg/errors v0.8.0
19+
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect
1820
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18
1921
github.com/sirupsen/logrus v1.4.2
2022
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c
2123
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
24+
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
2225
gopkg.in/yaml.v2 v2.2.8
2326
)

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
1919
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
2020
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
2121
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
22+
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
23+
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
2224
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
2325
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
2426
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
@@ -148,6 +150,8 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
148150
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
149151
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
150152
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
153+
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac h1:jWKYCNlX4J5s8M0nHYkh7Y7c9gRVDEb3mq51j5J0F5M=
154+
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ=
151155
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
152156
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
153157
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@@ -184,6 +188,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
184188
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
185189
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
186190
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
191+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
187192
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
188193
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
189194
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
@@ -259,10 +264,13 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
259264
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
260265
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
261266
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
267+
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
268+
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
262269
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
263270
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
264271
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
265272
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
266273
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
267274
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
275+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
268276
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

internal/handlers/auth.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ func (h *Handler) initOAuth() {
4646
auth.WithAdapterWrapper(auth.NewOktaAdapter(okta.ClientID, okta.ClientSecret, okta.EndpointURL), h.engine.Group("/api/v1/auth/okta"))
4747
h.providers = append(h.providers, "okta")
4848
}
49+
genericOIDC := util.GetConfig().GenericOIDC
50+
if genericOIDC.Enabled() {
51+
auth.WithAdapterWrapper(auth.NewGenericOIDCAdapter(genericOIDC.ClientID, genericOIDC.ClientSecret, genericOIDC.EndpointURL), h.engine.Group("/api/v1/auth/generic_oidc"))
52+
h.providers = append(h.providers, "generic_oidc")
53+
}
4954

5055
h.engine.POST("/api/v1/auth/check", h.handleAuthCheck)
5156
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package auth
2+
3+
import (
4+
"context"
5+
"strings"
6+
7+
"github.com/mxschmitt/golang-url-shortener/internal/util"
8+
"github.com/sirupsen/logrus"
9+
10+
oidc "github.com/coreos/go-oidc"
11+
"github.com/pkg/errors"
12+
"golang.org/x/oauth2"
13+
)
14+
15+
type genericOIDCAdapter struct {
16+
config *oauth2.Config
17+
oidc *oidc.Config
18+
provider *oidc.Provider
19+
}
20+
21+
type claims struct {
22+
PreferredUsername string `json:"sub"`
23+
Name string `json:"name"`
24+
GivenName string `json:"given_name"`
25+
FamilyName string `json:"family_name"`
26+
ACR string `json:"acr"`
27+
}
28+
29+
// NewGenericOIDCAdapter creates an oAuth adapter out of the credentials and the baseURL
30+
func NewGenericOIDCAdapter(clientID, clientSecret, endpointURL string) Adapter {
31+
endpointURL = strings.TrimSuffix(endpointURL, "/")
32+
33+
if endpointURL == "" {
34+
logrus.Error("Configure GenericOIDC Endpoint")
35+
}
36+
37+
ctx := context.Background()
38+
provider, err := oidc.NewProvider(ctx, endpointURL)
39+
if err != nil {
40+
logrus.Error("Configure GenericOIDC Endpoint: " + err.Error())
41+
}
42+
43+
redirectURL := util.GetConfig().BaseURL + "/api/v1/auth/generic_oidc/callback"
44+
// Configure an OpenID Connect aware OAuth client.
45+
return &genericOIDCAdapter{
46+
config: &oauth2.Config{
47+
ClientID: clientID,
48+
ClientSecret: clientSecret,
49+
RedirectURL: redirectURL,
50+
// Discovery returns the OAuth endpoints.
51+
Endpoint: provider.Endpoint(),
52+
// "openid" is a required scope for OpenID Connect flows.
53+
Scopes: []string{
54+
"profile",
55+
"openid",
56+
"offline_access",
57+
},
58+
},
59+
oidc: &oidc.Config{
60+
ClientID: clientID,
61+
},
62+
provider: provider,
63+
}
64+
}
65+
66+
func (a *genericOIDCAdapter) GetRedirectURL(state string) string {
67+
return a.config.AuthCodeURL(state)
68+
}
69+
70+
func (a *genericOIDCAdapter) GetUserData(state, code string) (*user, error) {
71+
72+
logrus.Debugf("Getting User Data with state: %s, and code: %s", state, code)
73+
oAuthToken, err := a.config.Exchange(context.Background(), code)
74+
if err != nil {
75+
return nil, errors.Wrap(err, "could not exchange code")
76+
}
77+
78+
rawIDToken, ok := oAuthToken.Extra("id_token").(string)
79+
if !ok {
80+
return nil, errors.Wrap(err, "No id_token field in oauth2 token.")
81+
}
82+
83+
idToken, err := a.provider.Verifier(a.oidc).Verify(context.Background(), rawIDToken)
84+
if err != nil {
85+
return nil, errors.Wrap(err, "Something went wrong verifying the token: "+err.Error())
86+
}
87+
88+
var oUser claims
89+
if err = idToken.Claims(&oUser); err != nil {
90+
return nil, errors.Wrap(err, "Something went wrong verifying the token: "+err.Error())
91+
}
92+
93+
return &user{
94+
ID: string(oUser.PreferredUsername),
95+
Name: oUser.Name,
96+
Picture: util.GetConfig().BaseURL + "/images/generic_oidc_logo.png", // Default GenericOIDC Avatar
97+
}, nil
98+
}
99+
100+
func (a *genericOIDCAdapter) GetOAuthProviderName() string {
101+
return "generic_oidc"
102+
}

internal/util/config.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type Configuration struct {
2929
GitHub oAuthConf `yaml:"GitHub" env:"GITHUB"`
3030
Microsoft oAuthConf `yaml:"Microsoft" env:"MICROSOFT"`
3131
Okta oAuthConf `yaml:"Okta" env:"OKTA"`
32+
GenericOIDC oAuthConf `yaml:"GenericOIDC" env:"GENERIC_OIDC"`
3233
Proxy proxyAuthConf `yaml:"Proxy" env:"PROXY"`
3334
Redis redisConf `yaml:"Redis" env:"REDIS"`
3435
}
@@ -47,7 +48,7 @@ type redisConf struct {
4748
type oAuthConf struct {
4849
ClientID string `yaml:"ClientID" env:"CLIENT_ID"`
4950
ClientSecret string `yaml:"ClientSecret" env:"CLIENT_SECRET"`
50-
EndpointURL string `yaml:"EndpointURL" env:"ENDPOINT_URL"` // Optional for GitHub, mandatory for Okta
51+
EndpointURL string `yaml:"EndpointURL" env:"ENDPOINT_URL"` // Optional for GitHub, mandatory for Okta and GenericOIDC
5152
}
5253

5354
type proxyAuthConf struct {

web/src/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,12 @@ export default class BaseComponent extends Component {
137137
<Button style={{ color: "#007dc1" }} onClick={this.onOAuthClick.bind(this, "okta")}>
138138
<Image src='/images/okta_logo.png' style={{ width: "16px", height: "16px", marginBottom: ".15em" }} avatar /> Login with Okta
139139
</Button>
140+
{info.providers.includes("generic_oidc") && <div className="ui divider"></div>}
141+
</div>}
142+
{info.providers.includes("generic_oidc") && <div>
143+
<Button style={{ color: "#007dc1" }} onClick={this.onOAuthClick.bind(this, "generic_oidc")}>
144+
<Image src='/images/generic_oidc_logo.png' style={{ width: "16px", height: "16px", marginBottom: ".15em" }} avatar /> Login with OpenID Connect
145+
</Button>
140146
</div>}
141147
{info.providers.includes("microsoft") && <div>
142148
<div className="ui divider"></div>

0 commit comments

Comments
 (0)