Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
D
dex
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
go
dex
Commits
b31dedc2
Commit
b31dedc2
authored
Aug 08, 2016
by
Eric Chiang
Committed by
GitHub
Aug 08, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #38 from coreos/oidc-connector
Add OpenID Connect connector
parents
95a61454
b08780e6
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
388 additions
and
115 deletions
+388
-115
config.go
cmd/poke/config.go
+11
-9
oidc.go
connector/oidc/oidc.go
+131
-0
config-dev.yaml
example/config-dev.yaml
+8
-0
glide.lock
glide.lock
+3
-3
glide.yaml
glide.yaml
+1
-1
.travis.yml
vendor/github.com/ericchiang/oidc/.travis.yml
+13
-0
README.md
vendor/github.com/ericchiang/oidc/README.md
+11
-4
doc.go
vendor/github.com/ericchiang/oidc/doc.go
+5
-5
app.go
vendor/github.com/ericchiang/oidc/examples/idtoken/app.go
+7
-4
app.go
vendor/github.com/ericchiang/oidc/examples/nonce/app.go
+10
-5
nonce.go
vendor/github.com/ericchiang/oidc/nonce.go
+1
-9
oidc.go
vendor/github.com/ericchiang/oidc/oidc.go
+141
-51
oidc_test.go
vendor/github.com/ericchiang/oidc/oidc_test.go
+43
-21
main.go
vendor/github.com/ericchiang/oidc/oidcproxy/main.go
+3
-3
No files found.
cmd/poke/config.go
View file @
b31dedc2
...
...
@@ -7,6 +7,7 @@ import (
"github.com/coreos/poke/connector/github"
"github.com/coreos/poke/connector/ldap"
"github.com/coreos/poke/connector/mock"
"github.com/coreos/poke/connector/oidc"
"github.com/coreos/poke/storage"
"github.com/coreos/poke/storage/kubernetes"
"github.com/coreos/poke/storage/memory"
...
...
@@ -100,33 +101,34 @@ func (c *Connector) UnmarshalYAML(unmarshal func(interface{}) error) error {
c
.
Name
=
connectorMetadata
.
Name
c
.
ID
=
connectorMetadata
.
ID
var
err
error
switch
c
.
Type
{
case
"mock"
:
var
config
struct
{
Config
mock
.
Config
`yaml:"config"`
}
if
err
:=
unmarshal
(
&
config
);
err
!=
nil
{
return
err
}
err
=
unmarshal
(
&
config
)
c
.
Config
=
&
config
.
Config
case
"ldap"
:
var
config
struct
{
Config
ldap
.
Config
`yaml:"config"`
}
if
err
:=
unmarshal
(
&
config
);
err
!=
nil
{
return
err
}
err
=
unmarshal
(
&
config
)
c
.
Config
=
&
config
.
Config
case
"github"
:
var
config
struct
{
Config
github
.
Config
`yaml:"config"`
}
if
err
:=
unmarshal
(
&
config
);
err
!=
nil
{
return
err
err
=
unmarshal
(
&
config
)
c
.
Config
=
&
config
.
Config
case
"oidc"
:
var
config
struct
{
Config
oidc
.
Config
`yaml:"config"`
}
err
=
unmarshal
(
&
config
)
c
.
Config
=
&
config
.
Config
default
:
return
fmt
.
Errorf
(
"unknown connector type %q"
,
c
.
Type
)
}
return
nil
return
err
}
connector/oidc/oidc.go
View file @
b31dedc2
// Package oidc implements logging in through OpenID Connect providers.
package
oidc
import
(
"errors"
"fmt"
"net/http"
"os"
"github.com/ericchiang/oidc"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"github.com/coreos/poke/connector"
)
// Config holds configuration options for OpenID Connect logins.
type
Config
struct
{
Issuer
string
`yaml:"issuer"`
ClientID
string
`yaml:"clientID"`
ClientSecret
string
`yaml:"clientSecret"`
RedirectURI
string
`yaml:"redirectURI"`
Scopes
[]
string
`yaml:"scopes"`
// defaults to "profile" and "email"
}
// Open returns a connector which can be used to login users through an upstream
// OpenID Connect provider.
func
(
c
*
Config
)
Open
()
(
conn
connector
.
Connector
,
err
error
)
{
ctx
,
cancel
:=
context
.
WithCancel
(
context
.
Background
())
provider
,
err
:=
oidc
.
NewProvider
(
ctx
,
c
.
Issuer
)
if
err
!=
nil
{
cancel
()
return
nil
,
fmt
.
Errorf
(
"failed to get provider: %v"
,
err
)
}
scopes
:=
[]
string
{
oidc
.
ScopeOpenID
}
if
len
(
c
.
Scopes
)
>
0
{
scopes
=
append
(
scopes
,
c
.
Scopes
...
)
}
else
{
scopes
=
append
(
scopes
,
"profile"
,
"email"
)
}
clientID
:=
os
.
ExpandEnv
(
c
.
ClientID
)
return
&
oidcConnector
{
redirectURI
:
c
.
RedirectURI
,
oauth2Config
:
&
oauth2
.
Config
{
ClientID
:
clientID
,
ClientSecret
:
os
.
ExpandEnv
(
c
.
ClientSecret
),
Endpoint
:
provider
.
Endpoint
(),
Scopes
:
scopes
,
RedirectURL
:
c
.
RedirectURI
,
},
verifier
:
provider
.
NewVerifier
(
ctx
,
oidc
.
VerifyExpiry
(),
oidc
.
VerifyAudience
(
clientID
),
),
},
nil
}
var
(
_
connector
.
CallbackConnector
=
(
*
oidcConnector
)(
nil
)
)
type
oidcConnector
struct
{
redirectURI
string
oauth2Config
*
oauth2
.
Config
verifier
*
oidc
.
IDTokenVerifier
ctx
context
.
Context
cancel
context
.
CancelFunc
}
func
(
c
*
oidcConnector
)
Close
()
error
{
c
.
cancel
()
return
nil
}
func
(
c
*
oidcConnector
)
LoginURL
(
callbackURL
,
state
string
)
(
string
,
error
)
{
if
c
.
redirectURI
!=
callbackURL
{
return
""
,
fmt
.
Errorf
(
"expected callback URL did not match the URL in the config"
)
}
return
c
.
oauth2Config
.
AuthCodeURL
(
state
),
nil
}
type
oauth2Error
struct
{
error
string
errorDescription
string
}
func
(
e
*
oauth2Error
)
Error
()
string
{
if
e
.
errorDescription
==
""
{
return
e
.
error
}
return
e
.
error
+
": "
+
e
.
errorDescription
}
func
(
c
*
oidcConnector
)
HandleCallback
(
r
*
http
.
Request
)
(
identity
connector
.
Identity
,
state
string
,
err
error
)
{
q
:=
r
.
URL
.
Query
()
if
errType
:=
q
.
Get
(
"error"
);
errType
!=
""
{
return
identity
,
""
,
&
oauth2Error
{
errType
,
q
.
Get
(
"error_description"
)}
}
token
,
err
:=
c
.
oauth2Config
.
Exchange
(
c
.
ctx
,
q
.
Get
(
"code"
))
if
err
!=
nil
{
return
identity
,
""
,
fmt
.
Errorf
(
"oidc: failed to get token: %v"
,
err
)
}
rawIDToken
,
ok
:=
token
.
Extra
(
"id_token"
)
.
(
string
)
if
!
ok
{
return
identity
,
""
,
errors
.
New
(
"oidc: no id_token in token response"
)
}
idToken
,
err
:=
c
.
verifier
.
Verify
(
rawIDToken
)
if
err
!=
nil
{
return
identity
,
""
,
fmt
.
Errorf
(
"oidc: failed to verify ID Token: %v"
,
err
)
}
var
claims
struct
{
Username
string
`json:"name"`
Email
string
`json:"email"`
EmailVerified
bool
`json:"email_verified"`
}
if
err
:=
idToken
.
Claims
(
&
claims
);
err
!=
nil
{
return
identity
,
""
,
fmt
.
Errorf
(
"oidc: failed to decode claims: %v"
,
err
)
}
identity
=
connector
.
Identity
{
UserID
:
idToken
.
Subject
,
Username
:
claims
.
Username
,
Email
:
claims
.
Email
,
EmailVerified
:
claims
.
EmailVerified
,
}
return
identity
,
q
.
Get
(
"state"
),
nil
}
example/config-dev.yaml
View file @
b31dedc2
...
...
@@ -18,6 +18,14 @@ connectors:
clientSecret
:
"
$GITHUB_CLIENT_SECRET"
redirectURI
:
http://127.0.0.1:5556/callback/github
org
:
kubernetes
-
type
:
oidc
id
:
google
name
:
Google Account
config
:
issuer
:
https://accounts.google.com
clientID
:
"
$GOOGLE_OAUTH2_CLIENT_ID"
clientSecret
:
"
$GOOGLE_OAUTH2_CLIENT_SECRET"
redirectURI
:
http://127.0.0.1:5556/callback/google
staticClients
:
-
id
:
example-app
...
...
glide.lock
View file @
b31dedc2
hash:
4442a097b81856345ae5f80101ad1a692a0b4e5d9b7627f5ad09cd20926122f4
updated: 2016-08-0
5T09:58:15.61704222
-07:00
hash:
2af4a276277d2ab2ba9de9b0fd67ab7d6b70c07f4171a9efb225f30306d6f3eb
updated: 2016-08-0
8T11:20:44.300140564
-07:00
imports:
- name: github.com/ericchiang/oidc
version:
69fec81d167d815f4f455c741b2a94ffaf547ed2
version:
1907f0e61549f9081f26bdf269f11603496c9dee
- name: github.com/golang/protobuf
version: 874264fbbb43f4d91e999fecb4b40143ed611400
subpackages:
...
...
glide.yaml
View file @
b31dedc2
...
...
@@ -43,7 +43,7 @@ import:
-
bcrypt
-
package
:
github.com/ericchiang/oidc
version
:
69fec81d167d815f4f455c741b2a94ffaf547ed2
version
:
1907f0e61549f9081f26bdf269f11603496c9dee
-
package
:
github.com/pquerna/cachecontrol
version
:
c97913dcbd76de40b051a9b4cd827f7eaeb7a868
-
package
:
gopkg.in/square/go-jose.v1
...
...
vendor/github.com/ericchiang/oidc/.travis.yml
0 → 100644
View file @
b31dedc2
language
:
go
go
:
-
1.5.4
-
1.6.3
-
tip
notifications
:
email
:
false
matrix
:
allow_failures
:
-
go
:
tip
vendor/github.com/ericchiang/oidc/README.md
View file @
b31dedc2
...
...
@@ -58,6 +58,13 @@ Or the provider can be used to verify and inspect the OpenID Connect
verifier
:=
provider
.
NewVerifier
(
ctx
)
```
The verifier itself can be constructed with addition checks, such as verifing a
token was issued for a specific client or hasn't expired.
```
go
verifier
:=
provier
.
NewVerifier
(
ctx
,
oidc
.
VerifyAudience
(
clientID
),
oidc
.
VerifyExpiry
())
```
The returned verifier can be used to ensure the ID Token (a JWT) is signed by the provider.
```
go
...
...
@@ -78,19 +85,19 @@ func handleOAuth2Callback(w http.ResponseWriter, r *http.Request) {
}
// Verify that the ID Token is signed by the provider.
payload
,
err
:=
verifier
.
Verify
(
rawIDToken
)
idToken
,
err
:=
verifier
.
Verify
(
rawIDToken
)
if
err
!=
nil
{
http
.
Error
(
w
,
"Failed to verify ID Token: "
+
err
.
Error
(),
http
.
StatusInternalServerError
)
return
}
// Unmarshal ID Token for expected custom claims.
var
idToken
struct
{
var
claims
struct
{
Email
string
`json:"email"`
EmailVerified
bool
`json:"email_verified"`
}
if
err
:=
json
.
Unmarshal
(
payload
,
&
idToken
);
err
!=
nil
{
http
.
Error
(
w
,
"Failed to unmarshal ID Token: "
+
err
.
Error
(),
http
.
StatusInternalServerError
)
if
err
:=
idToken
.
Claims
(
&
claims
);
err
!=
nil
{
http
.
Error
(
w
,
"Failed to unmarshal ID Token
claims
: "
+
err
.
Error
(),
http
.
StatusInternalServerError
)
return
}
...
...
vendor/github.com/ericchiang/oidc/doc.go
View file @
b31dedc2
...
...
@@ -65,19 +65,19 @@ including verifying the JWT signature. It then returns the payload.
}
// Verify that the ID Token is signed by the provider.
payload
, err := verifier.Verify(rawIDToken)
idToken
, err := verifier.Verify(rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
}
// Unmarshal ID Token for expected custom claims.
var
idToken
struct {
var
claims
struct {
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
}
if err :=
json.Unmarshal(payload, &idToken
); err != nil {
http.Error(w, "Failed to unmarshal ID Token: "+err.Error(), http.StatusInternalServerError)
if err :=
idToken.Claims(&claims
); err != nil {
http.Error(w, "Failed to unmarshal ID Token
custom claims
: "+err.Error(), http.StatusInternalServerError)
return
}
...
...
@@ -123,7 +123,7 @@ The nonce enabled verifier can then be used to verify the nonce while unpacking
}
// Verify that the ID Token is signed by the provider and verify the nonce.
payload
, err := nonceEnabledVerifier.Verify(rawIDToken)
idToken
, err := nonceEnabledVerifier.Verify(rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusInternalServerError)
return
...
...
vendor/github.com/ericchiang/oidc/examples/idtoken/app.go
View file @
b31dedc2
...
...
@@ -59,8 +59,7 @@ func main() {
http
.
Error
(
w
,
"No id_token field in oauth2 token."
,
http
.
StatusInternalServerError
)
return
}
log
.
Println
(
rawIDToken
)
idTokenPayload
,
err
:=
verifier
.
Verify
(
rawIDToken
)
idToken
,
err
:=
verifier
.
Verify
(
rawIDToken
)
if
err
!=
nil
{
http
.
Error
(
w
,
"Failed to verify ID Token: "
+
err
.
Error
(),
http
.
StatusInternalServerError
)
return
...
...
@@ -68,11 +67,15 @@ func main() {
oauth2Token
.
AccessToken
=
"*REDACTED*"
rawMessage
:=
json
.
RawMessage
(
idTokenPayload
)
resp
:=
struct
{
OAuth2Token
*
oauth2
.
Token
IDTokenClaims
*
json
.
RawMessage
// ID Token payload is just JSON.
}{
oauth2Token
,
&
rawMessage
}
}{
oauth2Token
,
new
(
json
.
RawMessage
)}
if
err
:=
idToken
.
Claims
(
&
resp
.
IDTokenClaims
);
err
!=
nil
{
http
.
Error
(
w
,
err
.
Error
(),
http
.
StatusInternalServerError
)
return
}
data
,
err
:=
json
.
MarshalIndent
(
resp
,
""
,
" "
)
if
err
!=
nil
{
http
.
Error
(
w
,
err
.
Error
(),
http
.
StatusInternalServerError
)
...
...
vendor/github.com/ericchiang/oidc/examples/nonce/app.go
View file @
b31dedc2
...
...
@@ -69,23 +69,28 @@ func main() {
http
.
Error
(
w
,
"Failed to exchange token: "
+
err
.
Error
(),
http
.
StatusInternalServerError
)
return
}
rawIDToken
,
ok
:=
oauth2Token
.
Extra
(
"id_token"
)
.
(
string
)
if
!
ok
{
http
.
Error
(
w
,
"No id_token field in oauth2 token."
,
http
.
StatusInternalServerError
)
return
}
// Verify the ID Token signature and nonce.
idToken
Payload
,
err
:=
nonceEnabledVerifier
.
Verify
(
rawIDToken
)
idToken
,
err
:=
nonceEnabledVerifier
.
Verify
(
rawIDToken
)
if
err
!=
nil
{
http
.
Error
(
w
,
"Failed to verify ID Token: "
+
err
.
Error
(),
http
.
StatusInternalServerError
)
return
}
rawMessage
:=
json
.
RawMessage
(
idTokenPayload
)
resp
:=
struct
{
OAuth2Token
*
oauth2
.
Token
IDToken
*
json
.
RawMessage
// ID Token payload is just JSON.
}{
oauth2Token
,
&
rawMessage
}
OAuth2Token
*
oauth2
.
Token
IDTokenClaims
*
json
.
RawMessage
// ID Token payload is just JSON.
}{
oauth2Token
,
new
(
json
.
RawMessage
)}
if
err
:=
idToken
.
Claims
(
&
resp
.
IDTokenClaims
);
err
!=
nil
{
http
.
Error
(
w
,
err
.
Error
(),
http
.
StatusInternalServerError
)
return
}
data
,
err
:=
json
.
MarshalIndent
(
resp
,
""
,
" "
)
if
err
!=
nil
{
http
.
Error
(
w
,
err
.
Error
(),
http
.
StatusInternalServerError
)
...
...
vendor/github.com/ericchiang/oidc/nonce.go
View file @
b31dedc2
package
oidc
import
(
"encoding/json"
"errors"
"fmt"
"golang.org/x/oauth2"
)
...
...
@@ -29,13 +27,7 @@ type nonceVerifier struct {
nonceSource
NonceSource
}
func
(
n
nonceVerifier
)
verifyIDTokenPayload
(
payload
[]
byte
)
error
{
var
token
struct
{
Nonce
string
`json:"nonce"`
}
if
err
:=
json
.
Unmarshal
(
payload
,
&
token
);
err
!=
nil
{
return
fmt
.
Errorf
(
"oidc: failed to unmarshal nonce: %v"
,
err
)
}
func
(
n
nonceVerifier
)
verifyIDToken
(
token
*
IDToken
)
error
{
if
token
.
Nonce
==
""
{
return
errors
.
New
(
"oidc: no nonce present in ID Token"
)
}
...
...
vendor/github.com/ericchiang/oidc/oidc.go
View file @
b31dedc2
...
...
@@ -15,9 +15,9 @@ import (
var
(
// ErrTokenExpired indicates that a token parsed by a verifier has expired.
ErrTokenExpired
=
errors
.
New
(
"ID Token expired"
)
ErrTokenExpired
=
errors
.
New
(
"
oidc:
ID Token expired"
)
// ErrNotSupported indicates that the requested optional OpenID Connect endpoint is not supported by the provider.
ErrNotSupported
=
errors
.
New
(
"endpoint not supported"
)
ErrNotSupported
=
errors
.
New
(
"
oidc:
endpoint not supported"
)
)
const
(
...
...
@@ -44,8 +44,8 @@ type Provider struct {
JWKSURL
string
`json:"jwks_uri"`
UserInfoURL
string
`json:"userinfo_endpoint"`
//
Optionally contains extra claims
.
raw
map
[
string
]
interface
{}
//
Raw claims returned by the server
.
raw
Claims
[]
byte
}
// NewProvider uses the OpenID Connect disovery mechanism to construct a Provider.
...
...
@@ -67,20 +67,19 @@ func NewProvider(ctx context.Context, issuer string) (*Provider, error) {
if
err
:=
json
.
Unmarshal
(
body
,
&
p
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"oidc: failed to decode provider discovery object: %v"
,
err
)
}
// raw claims do not get error checks
json
.
Unmarshal
(
body
,
&
p
.
raw
)
p
.
rawClaims
=
body
if
p
.
Issuer
!=
issuer
{
return
nil
,
fmt
.
Errorf
(
"oidc: issuer did not match the issuer returned by provider, expected %q got %q"
,
issuer
,
p
.
Issuer
)
}
return
&
p
,
nil
}
//
Extra
returns additional fields returned by the server during discovery.
func
(
p
*
Provider
)
Extra
(
key
string
)
interface
{}
{
if
p
.
raw
!
=
nil
{
return
p
.
raw
[
key
]
//
Claims
returns additional fields returned by the server during discovery.
func
(
p
*
Provider
)
Claims
(
v
interface
{})
error
{
if
p
.
raw
Claims
=
=
nil
{
return
errors
.
New
(
"oidc: claims not set"
)
}
return
nil
return
json
.
Unmarshal
(
p
.
rawClaims
,
v
)
}
// Endpoint returns the OAuth2 auth and token endpoints for the given provider.
...
...
@@ -95,16 +94,15 @@ type UserInfo struct {
Email
string
`json:"email"`
EmailVerified
bool
`json:"email_verified"`
// Optionally contains extra claims.
raw
map
[
string
]
interface
{}
claims
[]
byte
}
//
Extra returns additional claims returned by the server
.
func
(
u
*
UserInfo
)
Extra
(
key
string
)
interface
{}
{
if
u
.
raw
!
=
nil
{
return
u
.
raw
[
key
]
//
Claims unmarshals the raw JSON object claims into the provided object
.
func
(
u
*
UserInfo
)
Claims
(
v
interface
{})
error
{
if
u
.
claims
=
=
nil
{
return
errors
.
New
(
"oidc: claims not set"
)
}
return
nil
return
json
.
Unmarshal
(
u
.
claims
,
v
)
}
// UserInfo uses the token source to query the provider's user info endpoint.
...
...
@@ -130,11 +128,101 @@ func (p *Provider) UserInfo(ctx context.Context, tokenSource oauth2.TokenSource)
if
err
:=
json
.
Unmarshal
(
body
,
&
userInfo
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"oidc: failed to decode userinfo: %v"
,
err
)
}
// raw claims do not get error checks
json
.
Unmarshal
(
body
,
&
userInfo
.
raw
)
userInfo
.
claims
=
body
return
&
userInfo
,
nil
}
// IDToken is an OpenID Connect extension that provides a predictable representation
// of an authorization event.
//
// The ID Token only holds fields OpenID Connect requires. To access additional
// claims returned by the server, use the Claims method.
//
// idToken, err := idTokenVerifier.Verify(rawIDToken)
// if err != nil {
// // handle error
// }
// var claims struct {
// Email string `json:"email"`
// EmailVerified bool `json:"email_verified"`
// }
// if err := idToken.Claims(&claims); err != nil {
// // handle error
// }
//
type
IDToken
struct
{
// The URL of the server which issued this token. This will always be the same
// as the URL used for initial discovery.
Issuer
string
// The client, or set of clients, that this token is issued for.
Audience
[]
string
// A unique string which identifies the end user.
Subject
string
IssuedAt
time
.
Time
Expiry
time
.
Time
Nonce
string
claims
[]
byte
}
// Claims unmarshals the raw JSON payload of the ID Token into a provided struct.
func
(
i
*
IDToken
)
Claims
(
v
interface
{})
error
{
if
i
.
claims
==
nil
{
return
errors
.
New
(
"oidc: claims not set"
)
}
return
json
.
Unmarshal
(
i
.
claims
,
v
)
}
type
audience
[]
string
func
(
a
*
audience
)
UnmarshalJSON
(
b
[]
byte
)
error
{
var
s
string
if
json
.
Unmarshal
(
b
,
&
s
)
==
nil
{
*
a
=
audience
{
s
}
return
nil
}
var
auds
[]
string
if
err
:=
json
.
Unmarshal
(
b
,
&
auds
);
err
!=
nil
{
return
err
}
*
a
=
audience
(
auds
)
return
nil
}
type
jsonTime
time
.
Time
func
(
j
*
jsonTime
)
UnmarshalJSON
(
b
[]
byte
)
error
{
var
n
json
.
Number
if
err
:=
json
.
Unmarshal
(
b
,
&
n
);
err
!=
nil
{
return
err
}
var
unix
int64
if
t
,
err
:=
n
.
Int64
();
err
==
nil
{
unix
=
t
}
else
{
f
,
err
:=
n
.
Float64
()
if
err
!=
nil
{
return
err
}
unix
=
int64
(
f
)
}
*
j
=
jsonTime
(
time
.
Unix
(
unix
,
0
))
return
nil
}
type
idToken
struct
{
Issuer
string
`json:"iss"`
Subject
string
`json:"sub"`
Audience
audience
`json:"aud"`
Expiry
jsonTime
`json:"exp"`
IssuedAt
jsonTime
`json:"iat"`
Nonce
string
`json:"nonce"`
}
// IDTokenVerifier provides verification for ID Tokens.
type
IDTokenVerifier
struct
{
issuer
string
...
...
@@ -143,31 +231,34 @@ type IDTokenVerifier struct {
}
// Verify parse the raw ID Token, verifies it's been signed by the provider, preforms
// additional verification,
such as checking the expiration,
and returns the claims.
func
(
v
*
IDTokenVerifier
)
Verify
(
rawIDToken
string
)
(
payload
[]
byte
,
err
error
)
{
payload
,
err
=
v
.
keySet
.
verifyJWT
(
rawIDToken
)
// additional verification, and returns the claims.
func
(
v
*
IDTokenVerifier
)
Verify
(
rawIDToken
string
)
(
*
IDToken
,
error
)
{
payload
,
err
:
=
v
.
keySet
.
verifyJWT
(
rawIDToken
)
if
err
!=
nil
{
return
nil
,
err
}
var
token
struct
{
Exp
float64
`json:"exp"`
// JSON numbers are always float64s.
Issuer
string
`json:"iss"`
}
var
token
idToken
if
err
:=
json
.
Unmarshal
(
payload
,
&
token
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"oidc: failed to unmarshal claims: %v"
,
err
)
}
if
v
.
issuer
!=
token
.
Issuer
{
return
nil
,
fmt
.
Errorf
(
"oidc: iss field did not match provider issuer"
)
}
if
time
.
Unix
(
int64
(
token
.
Exp
),
0
)
.
Before
(
time
.
Now
()
.
Round
(
time
.
Second
))
{
return
nil
,
ErrTokenExpired
t
:=
&
IDToken
{
Issuer
:
token
.
Issuer
,
Subject
:
token
.
Subject
,
Audience
:
[]
string
(
token
.
Audience
),
Expiry
:
time
.
Time
(
token
.
Expiry
),
IssuedAt
:
time
.
Time
(
token
.
Expiry
),
Nonce
:
token
.
Nonce
,
claims
:
payload
,
}
for
_
,
option
:=
range
v
.
options
{
if
err
:=
option
.
verifyIDToken
Payload
(
payload
);
err
!=
nil
{
if
err
:=
option
.
verifyIDToken
(
t
);
err
!=
nil
{
return
nil
,
err
}
}
return
payload
,
nil
return
t
,
nil
}
// NewVerifier returns an IDTokenVerifier that uses the provider's key set to verify JWTs.
...
...
@@ -184,7 +275,7 @@ func (p *Provider) NewVerifier(ctx context.Context, options ...VerificationOptio
// VerificationOption is an option provided to Provider.NewVerifier.
type
VerificationOption
interface
{
verifyIDToken
Payload
(
raw
[]
byte
)
error
verifyIDToken
(
token
*
IDToken
)
error
}
// VerifyAudience ensures that an ID Token was issued for the specific client.
...
...
@@ -199,25 +290,8 @@ type clientVerifier struct {
clientID
string
}
func
(
c
clientVerifier
)
verifyIDTokenPayload
(
payload
[]
byte
)
error
{
var
token
struct
{
Aud
string
`json:"aud"`
}
if
err
:=
json
.
Unmarshal
(
payload
,
&
token
);
err
==
nil
{
if
token
.
Aud
!=
c
.
clientID
{
return
errors
.
New
(
"oidc: id token aud field did not match client_id"
)
}
return
nil
}
// Aud can optionally be an array of strings
var
token2
struct
{
Aud
[]
string
`json:"aud"`
}
if
err
:=
json
.
Unmarshal
(
payload
,
&
token2
);
err
!=
nil
{
return
fmt
.
Errorf
(
"oidc: failed to unmarshal aud claim: %v"
,
err
)
}
for
_
,
aud
:=
range
token2
.
Aud
{
func
(
c
clientVerifier
)
verifyIDToken
(
token
*
IDToken
)
error
{
for
_
,
aud
:=
range
token
.
Audience
{
if
aud
==
c
.
clientID
{
return
nil
}
...
...
@@ -225,6 +299,22 @@ func (c clientVerifier) verifyIDTokenPayload(payload []byte) error {
return
errors
.
New
(
"oidc: id token aud field did not match client_id"
)
}
// VerifyExpiry ensures that an ID Token has not expired.
func
VerifyExpiry
()
VerificationOption
{
return
expiryVerifier
{
time
.
Now
}
}
type
expiryVerifier
struct
{
now
func
()
time
.
Time
}
func
(
e
expiryVerifier
)
verifyIDToken
(
token
*
IDToken
)
error
{
if
e
.
now
()
.
After
(
token
.
Expiry
)
{
return
ErrTokenExpired
}
return
nil
}
// This method is internal to golang.org/x/oauth2. Just copy it.
func
contextClient
(
ctx
context
.
Context
)
*
http
.
Client
{
if
ctx
!=
nil
{
...
...
vendor/github.com/ericchiang/oidc/oidc_test.go
View file @
b31dedc2
package
oidc
import
"testing"
import
(
"encoding/json"
"reflect"
"testing"
)
func
TestClientVerifier
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
clientID
string
payload
string
aud
[]
string
wantErr
bool
}{
{
clientID
:
"1"
,
payload
:
`{"aud":"1"}`
,
aud
:
[]
string
{
"1"
}
,
},
{
clientID
:
"1"
,
payload
:
`{"aud":"2"}`
,
aud
:
[]
string
{
"2"
}
,
wantErr
:
true
,
},
{
clientID
:
"1"
,
payload
:
`{"aud":["1"]}`
,
},
{
clientID
:
"1"
,
payload
:
`{"aud":["1", "2"]}`
,
},
{
clientID
:
"3"
,
payload
:
`{"aud":["1", "2"]}`
,
wantErr
:
true
,
aud
:
[]
string
{
"2"
,
"1"
},
},
{
clientID
:
"3"
,
payload
:
`{"aud":}`
,
// invalid JSON
wantErr
:
true
,
},
{
clientID
:
"1"
,
payload
:
`{}`
,
aud
:
[]
string
{
"1"
,
"2"
},
wantErr
:
true
,
},
}
for
i
,
tc
:=
range
tests
{
err
:=
(
clientVerifier
{
tc
.
clientID
})
.
verifyIDTokenPayload
([]
byte
(
tc
.
payload
))
token
:=
IDToken
{
Audience
:
tc
.
aud
}
err
:=
(
clientVerifier
{
tc
.
clientID
})
.
verifyIDToken
(
&
token
)
if
err
!=
nil
&&
!
tc
.
wantErr
{
t
.
Errorf
(
"case %d: %v"
,
i
)
}
...
...
@@ -52,3 +43,34 @@ func TestClientVerifier(t *testing.T) {
}
}
}
func
TestUnmarshalAudience
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
data
string
want
audience
wantErr
bool
}{
{
`"foo"`
,
audience
{
"foo"
},
false
},
{
`["foo","bar"]`
,
audience
{
"foo"
,
"bar"
},
false
},
{
"foo"
,
nil
,
true
},
// invalid JSON
}
for
_
,
tc
:=
range
tests
{
var
a
audience
if
err
:=
json
.
Unmarshal
([]
byte
(
tc
.
data
),
&
a
);
err
!=
nil
{
if
!
tc
.
wantErr
{
t
.
Errorf
(
"failed to unmarshal %q: %v"
,
tc
.
data
,
err
)
}
continue
}
if
tc
.
wantErr
{
t
.
Errorf
(
"did not expected to be able to unmarshal %q"
,
tc
.
data
)
continue
}
if
!
reflect
.
DeepEqual
(
tc
.
want
,
a
)
{
t
.
Errorf
(
"from %q expected %q got %q"
,
tc
.
data
,
tc
.
want
,
a
)
}
}
}
vendor/github.com/ericchiang/oidc/oidcproxy/main.go
View file @
b31dedc2
...
...
@@ -3,7 +3,6 @@ package main
import
(
"crypto/rand"
"encoding/gob"
"encoding/json"
"flag"
"fmt"
"io"
...
...
@@ -192,7 +191,7 @@ func handleCallback(w http.ResponseWriter, r *http.Request) {
return
httpError
(
http
.
StatusInternalServerError
,
"Authentication failed"
)
}
payload
,
err
:=
verifier
.
Verify
(
rawIDToken
)
idToken
,
err
:=
verifier
.
Verify
(
rawIDToken
)
if
err
!=
nil
{
log
.
Printf
(
"Failed to verify token: %v"
,
err
)
return
httpError
(
http
.
StatusInternalServerError
,
"Authentication failed"
)
...
...
@@ -201,7 +200,8 @@ func handleCallback(w http.ResponseWriter, r *http.Request) {
Email
string
`json:"email"`
EmailVerified
bool
`json:"email_verified"`
}
if
err
:=
json
.
Unmarshal
(
payload
,
&
claims
);
err
!=
nil
{
if
err
:=
idToken
.
Claims
(
&
claims
);
err
!=
nil
{
log
.
Printf
(
"Failed to decode claims: %v"
,
err
)
return
httpError
(
http
.
StatusInternalServerError
,
"Authentication failed"
)
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment