perf(oidc): optimize client verification (#6999)

* fix some spelling errors

* client credential auth

* implementation of client auth

* improve error handling

* unit test command package

* unit test database package

* unit test query package

* cleanup unused tracing func

* fix integration tests

* errz to zerrors

* fix linting and import issues

* fix another linting error

* integration test with client secret

* Revert "integration test with client secret"

This reverts commit 0814ba522f.

* add integration tests

* client credentials integration test

* resolve comments

* pin oidc v3.5.0
This commit is contained in:
Tim Möhlmann
2023-12-05 19:01:03 +02:00
committed by GitHub
parent 51cfb9564a
commit ec03340b67
46 changed files with 1666 additions and 781 deletions

View File

@@ -1,6 +1,7 @@
package oidc
import (
"slices"
"strings"
"time"
@@ -9,43 +10,40 @@ import (
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query"
)
type Client struct {
app *query.App
defaultLoginURL string
defaultLoginURLV2 string
defaultAccessTokenLifetime time.Duration
defaultIdTokenLifetime time.Duration
allowedScopes []string
client *query.OIDCClient
defaultLoginURL string
defaultLoginURLV2 string
allowedScopes []string
}
func ClientFromBusiness(app *query.App, defaultLoginURL, defaultLoginURLV2 string, defaultAccessTokenLifetime, defaultIdTokenLifetime time.Duration, allowedScopes []string) (op.Client, error) {
if app.OIDCConfig == nil {
return nil, errors.ThrowInvalidArgument(nil, "OIDC-d5bhD", "client is not a proper oidc application")
func ClientFromBusiness(client *query.OIDCClient, defaultLoginURL, defaultLoginURLV2 string) op.Client {
allowedScopes := make([]string, len(client.ProjectRoleKeys))
for i, roleKey := range client.ProjectRoleKeys {
allowedScopes[i] = ScopeProjectRolePrefix + roleKey
}
return &Client{
app: app,
defaultLoginURL: defaultLoginURL,
defaultLoginURLV2: defaultLoginURLV2,
defaultAccessTokenLifetime: defaultAccessTokenLifetime,
defaultIdTokenLifetime: defaultIdTokenLifetime,
allowedScopes: allowedScopes},
nil
client: client,
defaultLoginURL: defaultLoginURL,
defaultLoginURLV2: defaultLoginURLV2,
allowedScopes: allowedScopes,
}
}
func (c *Client) ApplicationType() op.ApplicationType {
return op.ApplicationType(c.app.OIDCConfig.AppType)
return op.ApplicationType(c.client.ApplicationType)
}
func (c *Client) AuthMethod() oidc.AuthMethod {
return authMethodToOIDC(c.app.OIDCConfig.AuthMethodType)
return authMethodToOIDC(c.client.AuthMethodType)
}
func (c *Client) GetID() string {
return c.app.OIDCConfig.ClientID
return c.client.ClientID
}
func (c *Client) LoginURL(id string) string {
@@ -56,28 +54,28 @@ func (c *Client) LoginURL(id string) string {
}
func (c *Client) RedirectURIs() []string {
return c.app.OIDCConfig.RedirectURIs
return c.client.RedirectURIs
}
func (c *Client) PostLogoutRedirectURIs() []string {
return c.app.OIDCConfig.PostLogoutRedirectURIs
return c.client.PostLogoutRedirectURIs
}
func (c *Client) ResponseTypes() []oidc.ResponseType {
return responseTypesToOIDC(c.app.OIDCConfig.ResponseTypes)
return responseTypesToOIDC(c.client.ResponseTypes)
}
func (c *Client) GrantTypes() []oidc.GrantType {
return grantTypesToOIDC(c.app.OIDCConfig.GrantTypes)
return grantTypesToOIDC(c.client.GrantTypes)
}
func (c *Client) DevMode() bool {
return c.app.OIDCConfig.IsDevMode
return c.client.IsDevMode
}
func (c *Client) RestrictAdditionalIdTokenScopes() func(scopes []string) []string {
return func(scopes []string) []string {
if c.app.OIDCConfig.AssertIDTokenRole {
if c.client.IDTokenRoleAssertion {
return scopes
}
return removeScopeWithPrefix(scopes, ScopeProjectRolePrefix)
@@ -86,7 +84,7 @@ func (c *Client) RestrictAdditionalIdTokenScopes() func(scopes []string) []strin
func (c *Client) RestrictAdditionalAccessTokenScopes() func(scopes []string) []string {
return func(scopes []string) []string {
if c.app.OIDCConfig.AssertAccessTokenRole {
if c.client.AccessTokenRoleAssertion {
return scopes
}
return removeScopeWithPrefix(scopes, ScopeProjectRolePrefix)
@@ -94,15 +92,15 @@ func (c *Client) RestrictAdditionalAccessTokenScopes() func(scopes []string) []s
}
func (c *Client) AccessTokenLifetime() time.Duration {
return c.defaultAccessTokenLifetime //PLANNED: impl from real client
return c.client.AccessTokenLifetime
}
func (c *Client) IDTokenLifetime() time.Duration {
return c.defaultIdTokenLifetime //PLANNED: impl from real client
return c.client.IDTokenLifetime
}
func (c *Client) AccessTokenType() op.AccessTokenType {
return accessTokenTypeToOIDC(c.app.OIDCConfig.AccessTokenType)
return accessTokenTypeToOIDC(c.client.AccessTokenType)
}
func (c *Client) IsScopeAllowed(scope string) bool {
@@ -127,20 +125,15 @@ func (c *Client) IsScopeAllowed(scope string) bool {
if scope == ScopeProjectsRoles {
return true
}
for _, allowedScope := range c.allowedScopes {
if scope == allowedScope {
return true
}
}
return false
return slices.Contains(c.allowedScopes, scope)
}
func (c *Client) ClockSkew() time.Duration {
return c.app.OIDCConfig.ClockSkew
return c.client.ClockSkew
}
func (c *Client) IDTokenUserinfoClaimsAssertion() bool {
return c.app.OIDCConfig.AssertIDTokenUserinfo
return c.client.IDTokenUserinfoAssertion
}
func accessTokenTypeToOIDC(tokenType domain.OIDCTokenType) op.AccessTokenType {
@@ -229,3 +222,14 @@ func removeScopeWithPrefix(scopes []string, scopePrefix ...string) []string {
}
return newScopeList
}
func clientIDFromCredentials(cc *op.ClientCredentials) (clientID string, assertion bool, err error) {
if cc.ClientAssertion != "" {
claims := new(oidc.JWTTokenRequest)
if _, err := oidc.ParseToken(cc.ClientAssertion, claims); err != nil {
return "", false, oidc.ErrInvalidClient().WithParent(err)
}
return claims.Issuer, true, nil
}
return cc.ClientID, false, nil
}