Merge branch 'master' into signingkey

This commit is contained in:
Livio Amstutz 2020-12-16 08:01:37 +01:00
commit b2f23dc5b7
20 changed files with 240 additions and 134 deletions

View file

@ -9,3 +9,7 @@ updates:
commit-message:
prefix: chore
include: scope
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: weekly

View file

@ -10,7 +10,7 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Setup go
uses: actions/setup-go@v2-beta
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}
- run: go test -race -v -coverprofile=profile.cov ./pkg/...
@ -25,9 +25,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Source checkout
uses: actions/checkout@v1
with:
fetch-depth: 1
uses: actions/checkout@v2
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v2
with:

View file

@ -8,6 +8,7 @@ import (
"io/ioutil"
"net/http"
"os"
"strings"
"time"
"github.com/google/uuid"
@ -28,11 +29,11 @@ func main() {
clientSecret := os.Getenv("CLIENT_SECRET")
issuer := os.Getenv("ISSUER")
port := os.Getenv("PORT")
scopes := strings.Split(os.Getenv("SCOPES"), " ")
ctx := context.Background()
redirectURI := fmt.Sprintf("http://localhost:%v%v", port, callbackPath)
scopes := []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopeAddress}
cookieHandler := utils.NewCookieHandler(key, key, utils.WithUnsecure())
provider, err := rp.NewRelayingPartyOIDC(issuer, clientID, clientSecret, redirectURI, scopes,
rp.WithPKCE(cookieHandler),

View file

@ -281,10 +281,26 @@ func (c *ConfClient) AllowedScopes() []string {
return nil
}
func (c *ConfClient) AssertAdditionalIdTokenScopes() bool {
func (c *ConfClient) RestrictAdditionalIdTokenScopes() func(scopes []string) []string {
return func(scopes []string) []string {
return scopes
}
}
func (c *ConfClient) RestrictAdditionalAccessTokenScopes() func(scopes []string) []string {
return func(scopes []string) []string {
return scopes
}
}
func (c *ConfClient) IsScopeAllowed(scope string) bool {
return false
}
func (c *ConfClient) AssertAdditionalAccessTokenScopes() bool {
func (c *ConfClient) IDTokenUserinfoClaimsAssertion() bool {
return false
}
func (c *ConfClient) ClockSkew() time.Duration {
return 0
}

View file

@ -68,5 +68,5 @@ func HandleLogin(w http.ResponseWriter, r *http.Request) {
func HandleCallback(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
client := r.FormValue("client")
http.Redirect(w, r, "/authorize/"+client, http.StatusFound)
http.Redirect(w, r, "/authorize/callback?id="+client, http.StatusFound)
}

4
go.mod
View file

@ -15,9 +15,9 @@ require (
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/sirupsen/logrus v1.7.0
github.com/stretchr/testify v1.6.1
golang.org/x/net v0.0.0-20200904194848-62affa334b73
golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
golang.org/x/text v0.3.3
golang.org/x/text v0.3.4
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/square/go-jose.v2 v2.5.1
)

6
go.sum
View file

@ -126,8 +126,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -141,8 +139,6 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -280,6 +276,8 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View file

@ -48,8 +48,11 @@ func EmptyAccessTokenClaims() AccessTokenClaims {
return new(accessTokenClaims)
}
func NewAccessTokenClaims(issuer, subject string, audience []string, expiration time.Time, id string) AccessTokenClaims {
now := time.Now().UTC()
func NewAccessTokenClaims(issuer, subject string, audience []string, expiration time.Time, id, clientID string, skew time.Duration) AccessTokenClaims {
now := time.Now().UTC().Add(-skew)
if len(audience) == 0 {
audience = append(audience, clientID)
}
return &accessTokenClaims{
Issuer: issuer,
Subject: subject,
@ -200,13 +203,14 @@ func EmptyIDTokenClaims() IDTokenClaims {
return new(idTokenClaims)
}
func NewIDTokenClaims(issuer, subject string, audience []string, expiration, authTime time.Time, nonce string, acr string, amr []string, clientID string) IDTokenClaims {
func NewIDTokenClaims(issuer, subject string, audience []string, expiration, authTime time.Time, nonce string, acr string, amr []string, clientID string, skew time.Duration) IDTokenClaims {
audience = AppendClientIDToAudience(clientID, audience)
return &idTokenClaims{
Issuer: issuer,
Audience: audience,
Expiration: Time(expiration),
IssuedAt: Time(time.Now().UTC()),
AuthTime: Time(authTime),
IssuedAt: Time(time.Now().UTC().Add(-skew)),
AuthTime: Time(authTime.Add(-skew)),
Nonce: nonce,
AuthenticationContextClassReference: acr,
AuthenticationMethodsReferences: amr,
@ -441,3 +445,12 @@ func ClaimHash(claim string, sigAlgorithm jose.SignatureAlgorithm) (string, erro
return utils.HashString(hash, claim, true), nil
}
func AppendClientIDToAudience(clientID string, audience []string) []string {
for _, aud := range audience {
if aud == clientID {
return audience
}
}
return append(audience, clientID)
}

View file

@ -9,8 +9,12 @@ import (
const (
//GrantTypeCode defines the grant_type `authorization_code` used for the Token Request in the Authorization Code Flow
GrantTypeCode GrantType = "authorization_code"
//GrantTypeBearer define the grant_type `urn:ietf:params:oauth:grant-type:jwt-bearer` used for the JWT Authorization Grant
//GrantTypeBearer defines the grant_type `urn:ietf:params:oauth:grant-type:jwt-bearer` used for the JWT Authorization Grant
GrantTypeBearer GrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
//GrantTypeTokenExchange defines the grant_type `urn:ietf:params:oauth:grant-type:token-exchange` used for the OAuth Token Exchange Grant
GrantTypeTokenExchange GrantType = "urn:ietf:params:oauth:grant-type:token-exchange"
)
type GrantType string

View file

@ -121,7 +121,7 @@ func ValidateAuthReqScopes(client Client, scopes []string) ([]string, error) {
scope == oidc.ScopePhone ||
scope == oidc.ScopeAddress ||
scope == oidc.ScopeOfflineAccess) &&
!utils.Contains(client.AllowedScopes(), scope) {
!client.IsScopeAllowed(scope) {
scopes[i] = scopes[len(scopes)-1]
scopes[len(scopes)-1] = ""
scopes = scopes[:len(scopes)-1]

View file

@ -34,9 +34,11 @@ type Client interface {
AccessTokenType() AccessTokenType
IDTokenLifetime() time.Duration
DevMode() bool
AllowedScopes() []string
AssertAdditionalIdTokenScopes() bool
AssertAdditionalAccessTokenScopes() bool
RestrictAdditionalIdTokenScopes() func(scopes []string) []string
RestrictAdditionalAccessTokenScopes() func(scopes []string) []string
IsScopeAllowed(scope string) bool
IDTokenUserinfoClaimsAssertion() bool
ClockSkew() time.Duration
}
func ContainsResponseType(types []oidc.ResponseType, responseType oidc.ResponseType) bool {

View file

@ -19,6 +19,8 @@ type Configuration interface {
AuthMethodPostSupported() bool
CodeMethodS256Supported() bool
GrantTypeTokenExchangeSupported() bool
GrantTypeJWTAuthorizationSupported() bool
}
func ValidateIssuer(issuer string) error {

View file

@ -52,22 +52,23 @@ func Scopes(c Configuration) []string {
func ResponseTypes(c Configuration) []string {
return []string{
"code",
"id_token",
// "code token",
// "code id_token",
"id_token token",
// "code id_token token"
}
string(oidc.ResponseTypeCode),
string(oidc.ResponseTypeIDTokenOnly),
string(oidc.ResponseTypeIDToken),
} //TODO: ok for now, check later if dynamic needed
}
func GrantTypes(c Configuration) []string {
return []string{
"client_credentials",
"authorization_code",
// "password",
"urn:ietf:params:oauth:grant-type:token-exchange",
grantTypes := []string{
string(oidc.GrantTypeCode),
}
if c.GrantTypeTokenExchangeSupported() {
grantTypes = append(grantTypes, string(oidc.GrantTypeTokenExchange))
}
if c.GrantTypeJWTAuthorizationSupported() {
grantTypes = append(grantTypes, string(oidc.GrantTypeBearer))
}
return grantTypes
}
func SupportedClaims(c Configuration) []string {

View file

@ -26,7 +26,7 @@ func NewClientExpectAny(t *testing.T, appType op.ApplicationType) op.Client {
func(id string) string {
return "login?id=" + id
})
m.EXPECT().AllowedScopes().AnyTimes().Return(nil)
m.EXPECT().IsScopeAllowed(gomock.Any()).AnyTimes().Return(false)
return c
}

View file

@ -49,20 +49,6 @@ func (mr *MockClientMockRecorder) AccessTokenType() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessTokenType", reflect.TypeOf((*MockClient)(nil).AccessTokenType))
}
// AllowedScopes mocks base method
func (m *MockClient) AllowedScopes() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AllowedScopes")
ret0, _ := ret[0].([]string)
return ret0
}
// AllowedScopes indicates an expected call of AllowedScopes
func (mr *MockClientMockRecorder) AllowedScopes() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllowedScopes", reflect.TypeOf((*MockClient)(nil).AllowedScopes))
}
// ApplicationType mocks base method
func (m *MockClient) ApplicationType() op.ApplicationType {
m.ctrl.T.Helper()
@ -77,34 +63,6 @@ func (mr *MockClientMockRecorder) ApplicationType() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ApplicationType", reflect.TypeOf((*MockClient)(nil).ApplicationType))
}
// AssertAdditionalAccessTokenScopes mocks base method
func (m *MockClient) AssertAdditionalAccessTokenScopes() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AssertAdditionalAccessTokenScopes")
ret0, _ := ret[0].(bool)
return ret0
}
// AssertAdditionalAccessTokenScopes indicates an expected call of AssertAdditionalAccessTokenScopes
func (mr *MockClientMockRecorder) AssertAdditionalAccessTokenScopes() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssertAdditionalAccessTokenScopes", reflect.TypeOf((*MockClient)(nil).AssertAdditionalAccessTokenScopes))
}
// AssertAdditionalIdTokenScopes mocks base method
func (m *MockClient) AssertAdditionalIdTokenScopes() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AssertAdditionalIdTokenScopes")
ret0, _ := ret[0].(bool)
return ret0
}
// AssertAdditionalIdTokenScopes indicates an expected call of AssertAdditionalIdTokenScopes
func (mr *MockClientMockRecorder) AssertAdditionalIdTokenScopes() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssertAdditionalIdTokenScopes", reflect.TypeOf((*MockClient)(nil).AssertAdditionalIdTokenScopes))
}
// AuthMethod mocks base method
func (m *MockClient) AuthMethod() op.AuthMethod {
m.ctrl.T.Helper()
@ -119,6 +77,20 @@ func (mr *MockClientMockRecorder) AuthMethod() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthMethod", reflect.TypeOf((*MockClient)(nil).AuthMethod))
}
// ClockSkew mocks base method
func (m *MockClient) ClockSkew() time.Duration {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ClockSkew")
ret0, _ := ret[0].(time.Duration)
return ret0
}
// ClockSkew indicates an expected call of ClockSkew
func (mr *MockClientMockRecorder) ClockSkew() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClockSkew", reflect.TypeOf((*MockClient)(nil).ClockSkew))
}
// DevMode mocks base method
func (m *MockClient) DevMode() bool {
m.ctrl.T.Helper()
@ -161,6 +133,34 @@ func (mr *MockClientMockRecorder) IDTokenLifetime() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDTokenLifetime", reflect.TypeOf((*MockClient)(nil).IDTokenLifetime))
}
// IDTokenUserinfoClaimsAssertion mocks base method
func (m *MockClient) IDTokenUserinfoClaimsAssertion() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IDTokenUserinfoClaimsAssertion")
ret0, _ := ret[0].(bool)
return ret0
}
// IDTokenUserinfoClaimsAssertion indicates an expected call of IDTokenUserinfoClaimsAssertion
func (mr *MockClientMockRecorder) IDTokenUserinfoClaimsAssertion() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IDTokenUserinfoClaimsAssertion", reflect.TypeOf((*MockClient)(nil).IDTokenUserinfoClaimsAssertion))
}
// IsScopeAllowed mocks base method
func (m *MockClient) IsScopeAllowed(arg0 string) bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsScopeAllowed", arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// IsScopeAllowed indicates an expected call of IsScopeAllowed
func (mr *MockClientMockRecorder) IsScopeAllowed(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsScopeAllowed", reflect.TypeOf((*MockClient)(nil).IsScopeAllowed), arg0)
}
// LoginURL mocks base method
func (m *MockClient) LoginURL(arg0 string) string {
m.ctrl.T.Helper()
@ -216,3 +216,31 @@ func (mr *MockClientMockRecorder) ResponseTypes() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResponseTypes", reflect.TypeOf((*MockClient)(nil).ResponseTypes))
}
// RestrictAdditionalAccessTokenScopes mocks base method
func (m *MockClient) RestrictAdditionalAccessTokenScopes() func([]string) []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RestrictAdditionalAccessTokenScopes")
ret0, _ := ret[0].(func([]string) []string)
return ret0
}
// RestrictAdditionalAccessTokenScopes indicates an expected call of RestrictAdditionalAccessTokenScopes
func (mr *MockClientMockRecorder) RestrictAdditionalAccessTokenScopes() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RestrictAdditionalAccessTokenScopes", reflect.TypeOf((*MockClient)(nil).RestrictAdditionalAccessTokenScopes))
}
// RestrictAdditionalIdTokenScopes mocks base method
func (m *MockClient) RestrictAdditionalIdTokenScopes() func([]string) []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RestrictAdditionalIdTokenScopes")
ret0, _ := ret[0].(func([]string) []string)
return ret0
}
// RestrictAdditionalIdTokenScopes indicates an expected call of RestrictAdditionalIdTokenScopes
func (mr *MockClientMockRecorder) RestrictAdditionalIdTokenScopes() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RestrictAdditionalIdTokenScopes", reflect.TypeOf((*MockClient)(nil).RestrictAdditionalIdTokenScopes))
}

View file

@ -89,6 +89,34 @@ func (mr *MockConfigurationMockRecorder) EndSessionEndpoint() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EndSessionEndpoint", reflect.TypeOf((*MockConfiguration)(nil).EndSessionEndpoint))
}
// GrantTypeJWTAuthorizationSupported mocks base method
func (m *MockConfiguration) GrantTypeJWTAuthorizationSupported() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GrantTypeJWTAuthorizationSupported")
ret0, _ := ret[0].(bool)
return ret0
}
// GrantTypeJWTAuthorizationSupported indicates an expected call of GrantTypeJWTAuthorizationSupported
func (mr *MockConfigurationMockRecorder) GrantTypeJWTAuthorizationSupported() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrantTypeJWTAuthorizationSupported", reflect.TypeOf((*MockConfiguration)(nil).GrantTypeJWTAuthorizationSupported))
}
// GrantTypeTokenExchangeSupported mocks base method
func (m *MockConfiguration) GrantTypeTokenExchangeSupported() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GrantTypeTokenExchangeSupported")
ret0, _ := ret[0].(bool)
return ret0
}
// GrantTypeTokenExchangeSupported indicates an expected call of GrantTypeTokenExchangeSupported
func (mr *MockConfigurationMockRecorder) GrantTypeTokenExchangeSupported() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GrantTypeTokenExchangeSupported", reflect.TypeOf((*MockConfiguration)(nil).GrantTypeTokenExchangeSupported))
}
// Issuer mocks base method
func (m *MockConfiguration) Issuer() string {
m.ctrl.T.Helper()

View file

@ -156,9 +156,24 @@ func (c *ConfClient) DevMode() bool {
func (c *ConfClient) AllowedScopes() []string {
return nil
}
func (c *ConfClient) AssertAdditionalIdTokenScopes() bool {
func (c *ConfClient) RestrictAdditionalIdTokenScopes() func(scopes []string) []string {
return func(scopes []string) []string {
return scopes
}
}
func (c *ConfClient) RestrictAdditionalAccessTokenScopes() func(scopes []string) []string {
return func(scopes []string) []string {
return scopes
}
}
func (c *ConfClient) IsScopeAllowed(scope string) bool {
return false
}
func (c *ConfClient) AssertAdditionalAccessTokenScopes() bool {
func (c *ConfClient) IDTokenUserinfoClaimsAssertion() bool {
return false
}
func (c *ConfClient) ClockSkew() time.Duration {
return 0
}

View file

@ -49,7 +49,6 @@ type OpenIDProvider interface {
Decoder() utils.Decoder
Encoder() utils.Encoder
IDTokenHintVerifier() IDTokenHintVerifier
JWTProfileVerifier() JWTProfileVerifier
AccessTokenVerifier() AccessTokenVerifier
Crypto() Crypto
DefaultLogoutRedirectURI() string
@ -76,7 +75,7 @@ func CreateRouter(o OpenIDProvider, interceptors ...HttpInterceptor) *mux.Router
router.HandleFunc(readinessEndpoint, readyHandler(o.Probes()))
router.HandleFunc(oidc.DiscoveryEndpoint, discoveryHandler(o, o.Signer()))
router.Handle(o.AuthorizationEndpoint().Relative(), intercept(authorizeHandler(o)))
router.Handle(o.AuthorizationEndpoint().Relative()+"/{id}", intercept(authorizeCallbackHandler(o)))
router.NewRoute().Path(o.AuthorizationEndpoint().Relative()+"/callback").Queries("id", "{id}").Handler(intercept(authorizeCallbackHandler(o)))
router.Handle(o.TokenEndpoint().Relative(), intercept(tokenHandler(o)))
router.HandleFunc(o.UserinfoEndpoint().Relative(), userinfoHandler(o))
router.Handle(o.EndSessionEndpoint().Relative(), intercept(endSessionHandler(o)))
@ -89,15 +88,6 @@ type Config struct {
CryptoKey [32]byte
DefaultLogoutRedirectURI string
CodeMethodS256 bool
//TODO: add to config after updating Configuration interface for DiscoveryConfig
// ScopesSupported: oidc.SupportedScopes,
// ResponseTypesSupported: responseTypes,
// GrantTypesSupported: oidc.SupportedGrantTypes,
// ClaimsSupported: oidc.SupportedClaims,
// IdTokenSigningAlgValuesSupported: []string{keys.SigningAlgorithm},
// SubjectTypesSupported: []string{"public"},
// TokenEndpointAuthMethodsSupported:
}
type endpoints struct {
@ -195,6 +185,14 @@ func (o *openidProvider) CodeMethodS256Supported() bool {
return o.config.CodeMethodS256
}
func (o *openidProvider) GrantTypeTokenExchangeSupported() bool {
return false
}
func (o *openidProvider) GrantTypeJWTAuthorizationSupported() bool {
return true
}
func (o *openidProvider) Storage() Storage {
return o.storage
}

View file

@ -31,7 +31,7 @@ func CreateTokenResponse(ctx context.Context, authReq AuthRequest, client Client
return nil, err
}
}
idToken, err := CreateIDToken(ctx, creator.Issuer(), authReq, client.IDTokenLifetime(), accessToken, code, creator.Storage(), creator.Signer(), client.AssertAdditionalIdTokenScopes())
idToken, err := CreateIDToken(ctx, creator.Issuer(), authReq, client.IDTokenLifetime(), accessToken, code, creator.Storage(), creator.Signer(), client)
if err != nil {
return nil, err
}
@ -69,7 +69,7 @@ func CreateAccessToken(ctx context.Context, tokenRequest TokenRequest, accessTok
if err != nil {
return "", 0, err
}
validity = exp.Sub(time.Now().UTC())
validity = exp.Add(client.ClockSkew()).Sub(time.Now().UTC())
if accessTokenType == AccessTokenTypeJWT {
token, err = CreateJWT(ctx, creator.Issuer(), tokenRequest, exp, id, creator.Signer(), client, creator.Storage())
return
@ -83,9 +83,10 @@ func CreateBearerToken(tokenID, subject string, crypto Crypto) (string, error) {
}
func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, exp time.Time, id string, signer Signer, client Client, storage Storage) (string, error) {
claims := oidc.NewAccessTokenClaims(issuer, tokenRequest.GetSubject(), tokenRequest.GetAudience(), exp, id)
if client != nil && client.AssertAdditionalAccessTokenScopes() {
privateClaims, err := storage.GetPrivateClaimsFromScopes(ctx, tokenRequest.GetSubject(), client.GetID(), removeUserinfoScopes(tokenRequest.GetScopes()))
claims := oidc.NewAccessTokenClaims(issuer, tokenRequest.GetSubject(), tokenRequest.GetAudience(), exp, id, client.GetID(), client.ClockSkew())
if client != nil {
restrictedScopes := client.RestrictAdditionalAccessTokenScopes()(tokenRequest.GetScopes())
privateClaims, err := storage.GetPrivateClaimsFromScopes(ctx, tokenRequest.GetSubject(), client.GetID(), removeUserinfoScopes(restrictedScopes))
if err != nil {
return "", err
}
@ -94,21 +95,19 @@ func CreateJWT(ctx context.Context, issuer string, tokenRequest TokenRequest, ex
return utils.Sign(claims, signer.Signer())
}
func CreateIDToken(ctx context.Context, issuer string, authReq AuthRequest, validity time.Duration, accessToken, code string, storage Storage, signer Signer, additonalScopes bool) (string, error) {
exp := time.Now().UTC().Add(validity)
claims := oidc.NewIDTokenClaims(issuer, authReq.GetSubject(), authReq.GetAudience(), exp, authReq.GetAuthTime(), authReq.GetNonce(), authReq.GetACR(), authReq.GetAMR(), authReq.GetClientID())
scopes := authReq.GetScopes()
func CreateIDToken(ctx context.Context, issuer string, authReq AuthRequest, validity time.Duration, accessToken, code string, storage Storage, signer Signer, client Client) (string, error) {
exp := time.Now().UTC().Add(client.ClockSkew()).Add(validity)
claims := oidc.NewIDTokenClaims(issuer, authReq.GetSubject(), authReq.GetAudience(), exp, authReq.GetAuthTime(), authReq.GetNonce(), authReq.GetACR(), authReq.GetAMR(), authReq.GetClientID(), client.ClockSkew())
scopes := client.RestrictAdditionalIdTokenScopes()(authReq.GetScopes())
if accessToken != "" {
atHash, err := oidc.ClaimHash(accessToken, signer.SignatureAlgorithm())
if err != nil {
return "", err
}
claims.SetAccessTokenHash(atHash)
scopes = removeUserinfoScopes(scopes)
}
if !additonalScopes {
scopes = removeAdditionalScopes(scopes)
if !client.IDTokenUserinfoClaimsAssertion() {
scopes = removeUserinfoScopes(scopes)
}
}
if len(scopes) > 0 {
userInfo, err := storage.GetUserinfoFromScopes(ctx, authReq.GetSubject(), authReq.GetClientID(), scopes)
@ -142,19 +141,3 @@ func removeUserinfoScopes(scopes []string) []string {
}
return scopes
}
func removeAdditionalScopes(scopes []string) []string {
for i := len(scopes) - 1; i >= 0; i-- {
if !(scopes[i] == oidc.ScopeOpenID ||
scopes[i] == oidc.ScopeProfile ||
scopes[i] == oidc.ScopeEmail ||
scopes[i] == oidc.ScopeAddress ||
scopes[i] == oidc.ScopePhone) {
scopes[i] = scopes[len(scopes)-1]
scopes[len(scopes)-1] = ""
scopes = scopes[:len(scopes)-1]
}
}
return scopes
}

View file

@ -4,6 +4,7 @@ import (
"context"
"errors"
"net/http"
"net/url"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/oidc/grants/tokenexchange"
@ -17,6 +18,12 @@ type Exchanger interface {
Signer() Signer
Crypto() Crypto
AuthMethodPostSupported() bool
GrantTypeTokenExchangeSupported() bool
GrantTypeJWTAuthorizationSupported() bool
}
type JWTAuthorizationGrantExchanger interface {
Exchanger
JWTProfileVerifier() JWTProfileVerifier
}
@ -27,17 +34,20 @@ func tokenHandler(exchanger Exchanger) func(w http.ResponseWriter, r *http.Reque
CodeExchange(w, r, exchanger)
return
case string(oidc.GrantTypeBearer):
JWTProfile(w, r, exchanger)
return
case "exchange":
TokenExchange(w, r, exchanger)
if ex, ok := exchanger.(JWTAuthorizationGrantExchanger); ok && exchanger.GrantTypeJWTAuthorizationSupported() {
JWTProfile(w, r, ex)
return
}
case string(oidc.GrantTypeTokenExchange):
if exchanger.GrantTypeTokenExchangeSupported() {
TokenExchange(w, r, exchanger)
return
}
case "":
RequestError(w, r, ErrInvalidRequest("grant_type missing"))
return
default:
RequestError(w, r, ErrInvalidRequest("grant_type not supported"))
return
}
RequestError(w, r, ErrInvalidRequest("grant_type not supported"))
}
}
@ -75,9 +85,14 @@ func ParseAccessTokenRequest(r *http.Request, decoder utils.Decoder) (*oidc.Acce
}
clientID, clientSecret, ok := r.BasicAuth()
if ok {
tokenReq.ClientID = clientID
tokenReq.ClientSecret = clientSecret
tokenReq.ClientID, err = url.QueryUnescape(clientID)
if err != nil {
return nil, ErrInvalidRequest("invalid basic auth header")
}
tokenReq.ClientSecret, err = url.QueryUnescape(clientSecret)
if err != nil {
return nil, ErrInvalidRequest("invalid basic auth header")
}
}
return tokenReq, nil
}
@ -106,7 +121,7 @@ func AuthorizeClient(ctx context.Context, tokenReq *oidc.AccessTokenRequest, exc
return authReq, client, err
}
if client.AuthMethod() == AuthMethodPost && !exchanger.AuthMethodPostSupported() {
return nil, nil, errors.New("basic not supported")
return nil, nil, errors.New("auth_method post not supported")
}
err = AuthorizeClientIDSecret(ctx, tokenReq.ClientID, tokenReq.ClientSecret, exchanger.Storage())
if err != nil {
@ -137,7 +152,7 @@ func AuthorizeCodeChallenge(ctx context.Context, tokenReq *oidc.AccessTokenReque
return authReq, nil
}
func JWTProfile(w http.ResponseWriter, r *http.Request, exchanger Exchanger) {
func JWTProfile(w http.ResponseWriter, r *http.Request, exchanger JWTAuthorizationGrantExchanger) {
profileRequest, err := ParseJWTProfileRequest(r, exchanger.Decoder())
if err != nil {
RequestError(w, r, err)