zitadel/internal/api/oidc/integration_test/token_client_credentials_test.go
Livio Spring 5b40af79f0
fix: correctly check user state (#8631)
# Which Problems Are Solved

ZITADEL's user account deactivation mechanism did not work correctly
with service accounts. Deactivated service accounts retained the ability
to request tokens, which could lead to unauthorized access to
applications and resources.

# How the Problems Are Solved

Additionally to checking the user state on the session API and login UI,
the state is checked on all oidc session methods resulting in a new
token or when returning the user information (userinfo, introspection,
id_token / access_token and saml attributes)
2024-09-17 13:21:49 +00:00

179 lines
5.5 KiB
Go

//go:build integration
package oidc_test
import (
"testing"
"time"
"github.com/brianvoe/gofakeit/v6"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v3/pkg/client/rp"
"github.com/zitadel/oidc/v3/pkg/oidc"
oidc_api "github.com/zitadel/zitadel/internal/api/oidc"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/pkg/grpc/management"
"github.com/zitadel/zitadel/pkg/grpc/user"
)
func TestServer_ClientCredentialsExchange(t *testing.T) {
t.Parallel()
machine, name, clientID, clientSecret, err := Instance.CreateOIDCCredentialsClient(CTX)
require.NoError(t, err)
_, _, clientIDInactive, clientSecretInactive, err := Instance.CreateOIDCCredentialsClientInactive(CTX)
require.NoError(t, err)
type claims struct {
name string
username string
updated time.Time
resourceOwnerID any
resourceOwnerName any
resourceOwnerPrimaryDomain any
orgDomain any
}
tests := []struct {
name string
clientID string
clientSecret string
scope []string
wantClaims claims
wantErr bool
}{
{
name: "missing client ID error",
clientID: "",
clientSecret: clientSecret,
scope: []string{oidc.ScopeOpenID},
wantErr: true,
},
{
name: "client not found error",
clientID: "foo",
clientSecret: clientSecret,
scope: []string{oidc.ScopeOpenID},
wantErr: true,
},
{
name: "machine user without secret error",
clientID: func() string {
name := gofakeit.Username()
_, err := Instance.Client.Mgmt.AddMachineUser(CTX, &management.AddMachineUserRequest{
Name: name,
UserName: name,
AccessTokenType: user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT,
})
require.NoError(t, err)
return name
}(),
clientSecret: clientSecret,
scope: []string{oidc.ScopeOpenID},
wantErr: true,
},
{
name: "inactive machine user error",
clientID: clientIDInactive,
clientSecret: clientSecretInactive,
scope: []string{oidc.ScopeOpenID},
wantErr: true,
},
{
name: "wrong secret error",
clientID: clientID,
clientSecret: "bar",
scope: []string{oidc.ScopeOpenID},
wantErr: true,
},
{
name: "success",
clientID: clientID,
clientSecret: clientSecret,
scope: []string{oidc.ScopeOpenID},
},
{
name: "openid, profile, email",
clientID: clientID,
clientSecret: clientSecret,
scope: []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail},
wantClaims: claims{
name: name,
username: name,
updated: machine.GetDetails().GetChangeDate().AsTime(),
},
},
{
name: "org id and domain scope",
clientID: clientID,
clientSecret: clientSecret,
scope: []string{
oidc.ScopeOpenID,
domain.OrgIDScope + Instance.DefaultOrg.Id,
domain.OrgDomainPrimaryScope + Instance.DefaultOrg.PrimaryDomain,
},
wantClaims: claims{
resourceOwnerID: Instance.DefaultOrg.Id,
resourceOwnerName: Instance.DefaultOrg.Name,
resourceOwnerPrimaryDomain: Instance.DefaultOrg.PrimaryDomain,
orgDomain: Instance.DefaultOrg.PrimaryDomain,
},
},
{
name: "invalid org domain filtered",
clientID: clientID,
clientSecret: clientSecret,
scope: []string{
oidc.ScopeOpenID,
domain.OrgDomainPrimaryScope + Instance.DefaultOrg.PrimaryDomain,
domain.OrgDomainPrimaryScope + "foo"},
wantClaims: claims{
orgDomain: Instance.DefaultOrg.PrimaryDomain,
},
},
{
name: "invalid org id filtered",
clientID: clientID,
clientSecret: clientSecret,
scope: []string{oidc.ScopeOpenID,
domain.OrgIDScope + Instance.DefaultOrg.Id,
domain.OrgIDScope + "foo",
},
wantClaims: claims{
resourceOwnerID: Instance.DefaultOrg.Id,
resourceOwnerName: Instance.DefaultOrg.Name,
resourceOwnerPrimaryDomain: Instance.DefaultOrg.PrimaryDomain,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
provider, err := rp.NewRelyingPartyOIDC(CTX, Instance.OIDCIssuer(), tt.clientID, tt.clientSecret, redirectURI, tt.scope)
require.NoError(t, err)
tokens, err := rp.ClientCredentials(CTX, provider, nil)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.NotNil(t, tokens)
userinfo, err := rp.Userinfo[*oidc.UserInfo](CTX, tokens.AccessToken, oidc.BearerToken, machine.GetUserId(), provider)
require.NoError(t, err)
assert.Equal(t, tt.wantClaims.resourceOwnerID, userinfo.Claims[oidc_api.ClaimResourceOwnerID])
assert.Equal(t, tt.wantClaims.resourceOwnerName, userinfo.Claims[oidc_api.ClaimResourceOwnerName])
assert.Equal(t, tt.wantClaims.resourceOwnerPrimaryDomain, userinfo.Claims[oidc_api.ClaimResourceOwnerPrimaryDomain])
assert.Equal(t, tt.wantClaims.orgDomain, userinfo.Claims[domain.OrgDomainPrimaryClaim])
assert.Equal(t, tt.wantClaims.name, userinfo.Name)
assert.Equal(t, tt.wantClaims.username, userinfo.PreferredUsername)
assertOIDCTime(t, userinfo.UpdatedAt, tt.wantClaims.updated)
assert.Empty(t, userinfo.UserInfoProfile.FamilyName)
assert.Empty(t, userinfo.UserInfoProfile.GivenName)
assert.Empty(t, userinfo.UserInfoEmail)
assert.Empty(t, userinfo.UserInfoPhone)
assert.Empty(t, userinfo.Address)
})
}
}