Merge branch 'master' into signingkey
This commit is contained in:
commit
b2f23dc5b7
20 changed files with 240 additions and 134 deletions
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
|
@ -9,3 +9,7 @@ updates:
|
|||
commit-message:
|
||||
prefix: chore
|
||||
include: scope
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
@ -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:
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
4
go.mod
|
@ -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
6
go.sum
|
@ -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=
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -19,6 +19,8 @@ type Configuration interface {
|
|||
|
||||
AuthMethodPostSupported() bool
|
||||
CodeMethodS256Supported() bool
|
||||
GrantTypeTokenExchangeSupported() bool
|
||||
GrantTypeJWTAuthorizationSupported() bool
|
||||
}
|
||||
|
||||
func ValidateIssuer(issuer string) error {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
20
pkg/op/op.go
20
pkg/op/op.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue